chore: add lint rule and format (#367)
* chore: add lint rule and format * fix ci * add typescript * yarn install * add `@cloudflare/workers-types`pull/373/head
parent
fce4d55b9a
commit
e8b494b207
|
@ -21,8 +21,5 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- run: yarn install --frozen-lockfile
|
- run: yarn install --frozen-lockfile
|
||||||
- name: Build zod-validator in root directory
|
|
||||||
run: yarn build:zod-validator
|
|
||||||
working-directory: .
|
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
|
|
|
@ -13,7 +13,7 @@ export class ClientSessionError extends AuthError {}
|
||||||
export interface AuthClientConfig {
|
export interface AuthClientConfig {
|
||||||
baseUrl: string
|
baseUrl: string
|
||||||
basePath: string
|
basePath: string
|
||||||
credentials?: RequestCredentials,
|
credentials?: RequestCredentials
|
||||||
/** Stores last session response */
|
/** Stores last session response */
|
||||||
_session?: Session | null | undefined
|
_session?: Session | null | undefined
|
||||||
/** Used for timestamp since last sycned (in seconds) */
|
/** Used for timestamp since last sycned (in seconds) */
|
||||||
|
@ -33,9 +33,7 @@ export interface UseSessionOptions<R extends boolean> {
|
||||||
|
|
||||||
// Util type that matches some strings literally, but allows any other string as well.
|
// Util type that matches some strings literally, but allows any other string as well.
|
||||||
// @source https://github.com/microsoft/TypeScript/issues/29729#issuecomment-832522611
|
// @source https://github.com/microsoft/TypeScript/issues/29729#issuecomment-832522611
|
||||||
export type LiteralUnion<T extends U, U = string> =
|
export type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>)
|
||||||
| T
|
|
||||||
| (U & Record<never, never>)
|
|
||||||
|
|
||||||
export interface ClientSafeProvider {
|
export interface ClientSafeProvider {
|
||||||
id: LiteralUnion<BuiltInProviderType>
|
id: LiteralUnion<BuiltInProviderType>
|
||||||
|
@ -137,7 +135,9 @@ export async function fetchData<T = any>(
|
||||||
|
|
||||||
const res = await fetch(url, options)
|
const res = await fetch(url, options)
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (!res.ok) throw data
|
if (!res.ok) {
|
||||||
|
throw data
|
||||||
|
}
|
||||||
return data as T
|
return data as T
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(new ClientFetchError((error as Error).message, error as any))
|
logger.error(new ClientFetchError((error as Error).message, error as any))
|
||||||
|
|
|
@ -68,8 +68,7 @@ export async function getAuthUser(c: Context): Promise<AuthUser | null> {
|
||||||
...config.callbacks,
|
...config.callbacks,
|
||||||
async session(...args) {
|
async session(...args) {
|
||||||
authUser = args[0]
|
authUser = args[0]
|
||||||
const session =
|
const session = (await config.callbacks?.session?.(...args)) ?? args[0].session
|
||||||
(await config.callbacks?.session?.(...args)) ?? args[0].session
|
|
||||||
const user = args[0].user ?? args[0].token
|
const user = args[0].user ?? args[0].token
|
||||||
return { user, ...session } satisfies Session
|
return { user, ...session } satisfies Session
|
||||||
},
|
},
|
||||||
|
@ -90,7 +89,9 @@ export function verifyAuth(): MiddlewareHandler {
|
||||||
status: 401,
|
status: 401,
|
||||||
})
|
})
|
||||||
throw new HTTPException(401, { res })
|
throw new HTTPException(401, { res })
|
||||||
} else c.set('authUser', authUser)
|
} else {
|
||||||
|
c.set('authUser', authUser)
|
||||||
|
}
|
||||||
|
|
||||||
await next()
|
await next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,8 +112,11 @@ export function useSession<R extends boolean>(
|
||||||
error: 'SessionRequired',
|
error: 'SessionRequired',
|
||||||
callbackUrl: window.location.href,
|
callbackUrl: window.location.href,
|
||||||
})}`
|
})}`
|
||||||
if (onUnauthenticated) onUnauthenticated()
|
if (onUnauthenticated) {
|
||||||
else window.location.href = url
|
onUnauthenticated()
|
||||||
|
} else {
|
||||||
|
window.location.href = url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [requiredAndNotLoading, onUnauthenticated])
|
}, [requiredAndNotLoading, onUnauthenticated])
|
||||||
|
|
||||||
|
@ -153,7 +156,11 @@ export async function getSession(params?: GetSessionParams) {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export async function getCsrfToken() {
|
export async function getCsrfToken() {
|
||||||
const response = await fetchData<{ csrfToken: string }>('csrf', authConfigManager.getConfig(), logger)
|
const response = await fetchData<{ csrfToken: string }>(
|
||||||
|
'csrf',
|
||||||
|
authConfigManager.getConfig(),
|
||||||
|
logger
|
||||||
|
)
|
||||||
return response?.csrfToken ?? ''
|
return response?.csrfToken ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +222,9 @@ export async function signIn<P extends RedirectableProviderType | undefined = un
|
||||||
const url = (data as any).url ?? callbackUrl
|
const url = (data as any).url ?? callbackUrl
|
||||||
window.location.href = url
|
window.location.href = url
|
||||||
// If url contains a hash, the browser does not reload the page. We reload manually
|
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||||
if (url.includes('#')) window.location.reload()
|
if (url.includes('#')) {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +270,9 @@ export async function signOut<R extends boolean = true>(
|
||||||
const url = (data as any).url ?? callbackUrl
|
const url = (data as any).url ?? callbackUrl
|
||||||
window.location.href = url
|
window.location.href = url
|
||||||
// If url contains a hash, the browser does not reload the page. We reload manually
|
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||||
if (url.includes('#')) window.location.reload()
|
if (url.includes('#')) {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
// @ts-expect-error TODO: Fix this
|
// @ts-expect-error TODO: Fix this
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -285,11 +296,12 @@ export function SessionProvider(props: SessionProviderProps) {
|
||||||
__AUTHJS._lastSync = hasInitialSession ? now() : 0
|
__AUTHJS._lastSync = hasInitialSession ? now() : 0
|
||||||
|
|
||||||
const [session, setSession] = React.useState(() => {
|
const [session, setSession] = React.useState(() => {
|
||||||
if (hasInitialSession) __AUTHJS._session = props.session
|
if (hasInitialSession) {
|
||||||
|
__AUTHJS._session = props.session
|
||||||
|
}
|
||||||
return props.session
|
return props.session
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const [loading, setLoading] = React.useState(!hasInitialSession)
|
const [loading, setLoading] = React.useState(!hasInitialSession)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -363,9 +375,10 @@ export function SessionProvider(props: SessionProviderProps) {
|
||||||
// and makes our tab visible again, re-fetch the session, but only if
|
// and makes our tab visible again, re-fetch the session, but only if
|
||||||
// this feature is not disabled.
|
// this feature is not disabled.
|
||||||
const visibilityHandler = () => {
|
const visibilityHandler = () => {
|
||||||
if (refetchOnWindowFocus && document.visibilityState === 'visible')
|
if (refetchOnWindowFocus && document.visibilityState === 'visible') {
|
||||||
__AUTHJS._getSession({ event: 'visibilitychange' })
|
__AUTHJS._getSession({ event: 'visibilitychange' })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
document.addEventListener('visibilitychange', visibilityHandler, false)
|
document.addEventListener('visibilitychange', visibilityHandler, false)
|
||||||
return () => document.removeEventListener('visibilitychange', visibilityHandler, false)
|
return () => document.removeEventListener('visibilitychange', visibilityHandler, false)
|
||||||
}, [props.refetchOnWindowFocus])
|
}, [props.refetchOnWindowFocus])
|
||||||
|
@ -390,7 +403,9 @@ export function SessionProvider(props: SessionProviderProps) {
|
||||||
data: session,
|
data: session,
|
||||||
status: loading ? 'loading' : session ? 'authenticated' : 'unauthenticated',
|
status: loading ? 'loading' : session ? 'authenticated' : 'unauthenticated',
|
||||||
async update(data: any) {
|
async update(data: any) {
|
||||||
if (loading || !session) return
|
if (loading || !session) {
|
||||||
|
return
|
||||||
|
}
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const newSession = await fetchData<Session>(
|
const newSession = await fetchData<Session>(
|
||||||
'session',
|
'session',
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { Hono } from 'hono'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { authHandler, verifyAuth, initAuthConfig } from '../src'
|
import { authHandler, verifyAuth, initAuthConfig } from '../src'
|
||||||
|
|
||||||
|
|
||||||
// @ts-expect-error - global crypto
|
// @ts-expect-error - global crypto
|
||||||
//needed for node 18 and below but should work in node 20 and above
|
//needed for node 18 and below but should work in node 20 and above
|
||||||
global.crypto = webcrypto
|
global.crypto = webcrypto
|
||||||
|
@ -37,7 +36,7 @@ describe('Auth.js Adapter Middleware', () => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
app.use('/*', (c, next) => {
|
app.use('/*', (c, next) => {
|
||||||
c.env = {'AUTH_SECRET':'secret'}
|
c.env = { AUTH_SECRET: 'secret' }
|
||||||
return next()
|
return next()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -60,7 +59,7 @@ describe('Auth.js Adapter Middleware', () => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
app.use('/*', (c, next) => {
|
app.use('/*', (c, next) => {
|
||||||
c.env = {'AUTH_SECRET':'secret'}
|
c.env = { AUTH_SECRET: 'secret' }
|
||||||
return next()
|
return next()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -39,5 +39,8 @@
|
||||||
"hono": "^3.11.7",
|
"hono": "^3.11.7",
|
||||||
"tsup": "^8.0.1",
|
"tsup": "^8.0.1",
|
||||||
"vitest": "^1.0.4"
|
"vitest": "^1.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.14.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,9 @@ export const bunTranspiler = (options?: BunTranspilerOptions) => {
|
||||||
const extensions = options?.extensions ?? defaultOptions.extensions
|
const extensions = options?.extensions ?? defaultOptions.extensions
|
||||||
const headers = options?.headers ?? defaultOptions.headers
|
const headers = options?.headers ?? defaultOptions.headers
|
||||||
|
|
||||||
if (extensions?.every((ext) => !url.pathname.endsWith(ext))) return
|
if (extensions?.every((ext) => !url.pathname.endsWith(ext))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const loader = url.pathname.split('.').pop() as Bun.TranspilerOptions['loader']
|
const loader = url.pathname.split('.').pop() as Bun.TranspilerOptions['loader']
|
||||||
|
|
|
@ -25,7 +25,9 @@ export const esbuildTranspiler = (options?: EsbuildTranspilerOptions) => {
|
||||||
const url = new URL(c.req.url)
|
const url = new URL(c.req.url)
|
||||||
const extensions = options?.extensions ?? ['.ts', '.tsx']
|
const extensions = options?.extensions ?? ['.ts', '.tsx']
|
||||||
|
|
||||||
if (extensions.every((ext) => !url.pathname.endsWith(ext))) return
|
if (extensions.every((ext) => !url.pathname.endsWith(ext))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const headers = { 'content-type': options?.contentType ?? 'text/javascript' }
|
const headers = { 'content-type': options?.contentType ?? 'text/javascript' }
|
||||||
const script = await c.res.text()
|
const script = await c.res.text()
|
||||||
|
|
|
@ -85,6 +85,8 @@ const setFirebaseToken = (c: Context, idToken: FirebaseIdToken) => c.set(idToken
|
||||||
|
|
||||||
export const getFirebaseToken = (c: Context): FirebaseIdToken | null => {
|
export const getFirebaseToken = (c: Context): FirebaseIdToken | null => {
|
||||||
const idToken = c.get(idTokenContextKey)
|
const idToken = c.get(idTokenContextKey)
|
||||||
if (!idToken) return null
|
if (!idToken) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return idToken
|
return idToken
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,9 @@ const TestSchema = new GraphQLSchema({
|
||||||
|
|
||||||
const urlString = (query?: Record<string, string>): string => {
|
const urlString = (query?: Record<string, string>): string => {
|
||||||
const base = 'http://localhost/graphql'
|
const base = 'http://localhost/graphql'
|
||||||
if (!query) return base
|
if (!query) {
|
||||||
|
return base
|
||||||
|
}
|
||||||
const queryString = new URLSearchParams(query).toString()
|
const queryString = new URLSearchParams(query).toString()
|
||||||
return `${base}?${queryString}`
|
return `${base}?${queryString}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,7 +159,9 @@ const TestSchema = new GraphQLSchema({
|
||||||
|
|
||||||
const urlString = (query?: Record<string, string>): string => {
|
const urlString = (query?: Record<string, string>): string => {
|
||||||
const base = 'http://localhost/graphql'
|
const base = 'http://localhost/graphql'
|
||||||
if (!query) return base
|
if (!query) {
|
||||||
|
return base
|
||||||
|
}
|
||||||
const queryString = new URLSearchParams(query).toString()
|
const queryString = new URLSearchParams(query).toString()
|
||||||
return `${base}?${queryString}`
|
return `${base}?${queryString}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,10 +84,15 @@ export class AuthFlow {
|
||||||
body: parsedOptions,
|
body: parsedOptions,
|
||||||
}).then((res) => res.json())) as DiscordTokenResponse | DiscordErrorResponse
|
}).then((res) => res.json())) as DiscordTokenResponse | DiscordErrorResponse
|
||||||
|
|
||||||
if ('error_description' in response)
|
if ('error_description' in response) {
|
||||||
throw new HTTPException(400, { message: response.error_description })
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error })
|
}
|
||||||
if ('message' in response) throw new HTTPException(400, { message: response.message })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error })
|
||||||
|
}
|
||||||
|
if ('message' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.message })
|
||||||
|
}
|
||||||
|
|
||||||
if ('access_token' in response) {
|
if ('access_token' in response) {
|
||||||
this.token = {
|
this.token = {
|
||||||
|
@ -103,7 +108,9 @@ export class AuthFlow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('scope' in response) this.granted_scopes = response.scope.split(' ')
|
if ('scope' in response) {
|
||||||
|
this.granted_scopes = response.scope.split(' ')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserData() {
|
async getUserData() {
|
||||||
|
@ -114,11 +121,18 @@ export class AuthFlow {
|
||||||
},
|
},
|
||||||
}).then((res) => res.json())) as DiscordMeResponse | DiscordErrorResponse
|
}).then((res) => res.json())) as DiscordMeResponse | DiscordErrorResponse
|
||||||
|
|
||||||
if ('error_description' in response)
|
if ('error_description' in response) {
|
||||||
throw new HTTPException(400, { message: response.error_description })
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error })
|
}
|
||||||
if ('message' in response) throw new HTTPException(400, { message: response.message })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error })
|
||||||
|
}
|
||||||
|
if ('message' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.message })
|
||||||
|
}
|
||||||
|
|
||||||
if ('user' in response) this.user = response.user
|
if ('user' in response) {
|
||||||
|
this.user = response.user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,9 @@ export async function refreshToken(
|
||||||
body: params,
|
body: params,
|
||||||
}).then((res) => res.json())) as DiscordTokenResponse | { error: string }
|
}).then((res) => res.json())) as DiscordTokenResponse | { error: string }
|
||||||
|
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error })
|
||||||
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,9 @@ export async function revokeToken(
|
||||||
body: params,
|
body: params,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.status !== 200) throw new HTTPException(400, { message: 'Something went wrong' })
|
if (response.status !== 200) {
|
||||||
|
throw new HTTPException(400, { message: 'Something went wrong' })
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,9 @@ export class AuthFlow {
|
||||||
| FacebookTokenResponse
|
| FacebookTokenResponse
|
||||||
| FacebookErrorResponse
|
| FacebookErrorResponse
|
||||||
|
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error?.message })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error?.message })
|
||||||
|
}
|
||||||
|
|
||||||
if ('access_token' in response) {
|
if ('access_token' in response) {
|
||||||
this.token = {
|
this.token = {
|
||||||
|
@ -93,9 +95,13 @@ export class AuthFlow {
|
||||||
`https://graph.facebook.com/v18.0/me?access_token=${this.token?.token}`
|
`https://graph.facebook.com/v18.0/me?access_token=${this.token?.token}`
|
||||||
).then((res) => res.json())) as FacebookMeResponse | FacebookErrorResponse
|
).then((res) => res.json())) as FacebookMeResponse | FacebookErrorResponse
|
||||||
|
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error?.message })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error?.message })
|
||||||
|
}
|
||||||
|
|
||||||
if ('id' in response) this.user = response
|
if ('id' in response) {
|
||||||
|
this.user = response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserData() {
|
async getUserData() {
|
||||||
|
@ -107,8 +113,12 @@ export class AuthFlow {
|
||||||
`https://graph.facebook.com/${this.user?.id}?fields=${parsedFields}&access_token=${this.token?.token}`
|
`https://graph.facebook.com/${this.user?.id}?fields=${parsedFields}&access_token=${this.token?.token}`
|
||||||
).then((res) => res.json())) as FacebookUser | FacebookErrorResponse
|
).then((res) => res.json())) as FacebookUser | FacebookErrorResponse
|
||||||
|
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error?.message })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error?.message })
|
||||||
|
}
|
||||||
|
|
||||||
if ('id' in response) this.user = response
|
if ('id' in response) {
|
||||||
|
this.user = response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,8 +65,9 @@ export class AuthFlow {
|
||||||
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
||||||
}).then((res) => res.json())) as GitHubTokenResponse | GitHubErrorResponse
|
}).then((res) => res.json())) as GitHubTokenResponse | GitHubErrorResponse
|
||||||
|
|
||||||
if ('error_description' in response)
|
if ('error_description' in response) {
|
||||||
throw new HTTPException(400, { message: response.error_description })
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
|
}
|
||||||
|
|
||||||
if ('access_token' in response) {
|
if ('access_token' in response) {
|
||||||
this.token = {
|
this.token = {
|
||||||
|
@ -85,7 +86,9 @@ export class AuthFlow {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserData() {
|
async getUserData() {
|
||||||
if (!this.token?.token) await this.getTokenFromCode()
|
if (!this.token?.token) {
|
||||||
|
await this.getTokenFromCode()
|
||||||
|
}
|
||||||
|
|
||||||
const response = (await fetch('https://api.github.com/user', {
|
const response = (await fetch('https://api.github.com/user', {
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -96,7 +99,9 @@ export class AuthFlow {
|
||||||
},
|
},
|
||||||
}).then((res) => res.json())) as GitHubUser | GitHubErrorResponse
|
}).then((res) => res.json())) as GitHubUser | GitHubErrorResponse
|
||||||
|
|
||||||
if ('message' in response) throw new HTTPException(400, { message: response.message })
|
if ('message' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.message })
|
||||||
|
}
|
||||||
|
|
||||||
if ('id' in response) {
|
if ('id' in response) {
|
||||||
this.user = response
|
this.user = response
|
||||||
|
|
|
@ -91,7 +91,9 @@ export class AuthFlow {
|
||||||
}),
|
}),
|
||||||
}).then((res) => res.json())) as GoogleTokenResponse | GoogleErrorResponse
|
}).then((res) => res.json())) as GoogleTokenResponse | GoogleErrorResponse
|
||||||
|
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error_description })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
|
}
|
||||||
|
|
||||||
if ('access_token' in response) {
|
if ('access_token' in response) {
|
||||||
this.token = {
|
this.token = {
|
||||||
|
@ -112,8 +114,12 @@ export class AuthFlow {
|
||||||
},
|
},
|
||||||
}).then((res) => res.json())) as GoogleUser | GoogleErrorResponse
|
}).then((res) => res.json())) as GoogleUser | GoogleErrorResponse
|
||||||
|
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error?.message })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error?.message })
|
||||||
|
}
|
||||||
|
|
||||||
if ('id' in response) this.user = response
|
if ('id' in response) {
|
||||||
|
this.user = response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,9 @@ export class AuthFlow {
|
||||||
},
|
},
|
||||||
}).then((res) => res.json())) as LinkedInTokenResponse | LinkedInErrorResponse
|
}).then((res) => res.json())) as LinkedInTokenResponse | LinkedInErrorResponse
|
||||||
|
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error_description })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
|
}
|
||||||
|
|
||||||
if ('access_token' in response) {
|
if ('access_token' in response) {
|
||||||
this.token = {
|
this.token = {
|
||||||
|
@ -106,9 +108,13 @@ export class AuthFlow {
|
||||||
},
|
},
|
||||||
}).then((res) => res.json())) as LinkedInUser | LinkedInErrorResponse
|
}).then((res) => res.json())) as LinkedInUser | LinkedInErrorResponse
|
||||||
|
|
||||||
if ('message' in response) throw new HTTPException(400, { message: response.message })
|
if ('message' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.message })
|
||||||
|
}
|
||||||
|
|
||||||
if ('sub' in response) this.user = response
|
if ('sub' in response) {
|
||||||
|
this.user = response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAppToken() {
|
async getAppToken() {
|
||||||
|
@ -122,7 +128,9 @@ export class AuthFlow {
|
||||||
(res) => res.json()
|
(res) => res.json()
|
||||||
)) as LinkedInTokenResponse | LinkedInErrorResponse
|
)) as LinkedInTokenResponse | LinkedInErrorResponse
|
||||||
|
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error_description })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
|
}
|
||||||
|
|
||||||
if ('access_token' in response) {
|
if ('access_token' in response) {
|
||||||
this.token = {
|
this.token = {
|
||||||
|
|
|
@ -93,7 +93,9 @@ export class AuthFlow {
|
||||||
},
|
},
|
||||||
}).then((res) => res.json())) as XTokenResponse | XErrorResponse
|
}).then((res) => res.json())) as XTokenResponse | XErrorResponse
|
||||||
|
|
||||||
if ('error' in response) throw new HTTPException(400, { message: response.error_description })
|
if ('error' in response) {
|
||||||
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
|
}
|
||||||
|
|
||||||
if ('access_token' in response) {
|
if ('access_token' in response) {
|
||||||
this.token = {
|
this.token = {
|
||||||
|
@ -122,9 +124,12 @@ export class AuthFlow {
|
||||||
},
|
},
|
||||||
}).then((res) => res.json())) as XMeResponse | XErrorResponse
|
}).then((res) => res.json())) as XMeResponse | XErrorResponse
|
||||||
|
|
||||||
if ('error_description' in response)
|
if ('error_description' in response) {
|
||||||
throw new HTTPException(400, { message: response.error_description })
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
|
}
|
||||||
|
|
||||||
if ('data' in response) this.user = response.data
|
if ('data' in response) {
|
||||||
|
this.user = response.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,9 @@ export async function refreshToken(
|
||||||
},
|
},
|
||||||
}).then((res) => res.json())) as XTokenResponse | XErrorResponse
|
}).then((res) => res.json())) as XTokenResponse | XErrorResponse
|
||||||
|
|
||||||
if ('error_description' in response)
|
if ('error_description' in response) {
|
||||||
throw new HTTPException(400, { message: response.error_description })
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,9 @@ export async function revokeToken(
|
||||||
},
|
},
|
||||||
}).then((res) => res.json())) as XRevokeResponse | XErrorResponse
|
}).then((res) => res.json())) as XRevokeResponse | XErrorResponse
|
||||||
|
|
||||||
if ('error_description' in response)
|
if ('error_description' in response) {
|
||||||
throw new HTTPException(400, { message: response.error_description })
|
throw new HTTPException(400, { message: response.error_description })
|
||||||
|
}
|
||||||
|
|
||||||
return response.revoked
|
return response.revoked
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type { DefaultBodyType, StrictResponse } from 'msw'
|
import type { DefaultBodyType, StrictResponse } from 'msw'
|
||||||
import { HttpResponse, http } from 'msw'
|
import { HttpResponse, http } from 'msw'
|
||||||
|
|
||||||
|
import type { DiscordErrorResponse, DiscordTokenResponse } from '../src/providers/discord'
|
||||||
import type {
|
import type {
|
||||||
FacebookErrorResponse,
|
FacebookErrorResponse,
|
||||||
FacebookTokenResponse,
|
FacebookTokenResponse,
|
||||||
|
@ -14,7 +15,6 @@ import type {
|
||||||
} from '../src/providers/google/types'
|
} from '../src/providers/google/types'
|
||||||
import type { LinkedInErrorResponse, LinkedInTokenResponse } from '../src/providers/linkedin'
|
import type { LinkedInErrorResponse, LinkedInTokenResponse } from '../src/providers/linkedin'
|
||||||
import type { XErrorResponse, XRevokeResponse, XTokenResponse } from '../src/providers/x'
|
import type { XErrorResponse, XRevokeResponse, XTokenResponse } from '../src/providers/x'
|
||||||
import { DiscordErrorResponse, DiscordTokenResponse } from '../src/providers/discord'
|
|
||||||
|
|
||||||
export const handlers = [
|
export const handlers = [
|
||||||
// Google
|
// Google
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { setupServer } from 'msw/node'
|
import { setupServer } from 'msw/node'
|
||||||
|
import type { DiscordUser } from '../src/providers/discord'
|
||||||
|
import {
|
||||||
|
refreshToken as discordRefresh,
|
||||||
|
revokeToken as discordRevoke,
|
||||||
|
discordAuth,
|
||||||
|
} from '../src/providers/discord'
|
||||||
import { facebookAuth } from '../src/providers/facebook'
|
import { facebookAuth } from '../src/providers/facebook'
|
||||||
import type { FacebookUser } from '../src/providers/facebook'
|
import type { FacebookUser } from '../src/providers/facebook'
|
||||||
import { githubAuth } from '../src/providers/github'
|
import { githubAuth } from '../src/providers/github'
|
||||||
|
@ -38,13 +44,6 @@ import {
|
||||||
discordRefreshTokenError,
|
discordRefreshTokenError,
|
||||||
} from './handlers'
|
} from './handlers'
|
||||||
|
|
||||||
import {
|
|
||||||
refreshToken as discordRefresh,
|
|
||||||
revokeToken as discordRevoke,
|
|
||||||
discordAuth,
|
|
||||||
DiscordUser,
|
|
||||||
} from '../src/providers/discord'
|
|
||||||
|
|
||||||
const server = setupServer(...handlers)
|
const server = setupServer(...handlers)
|
||||||
server.listen()
|
server.listen()
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,12 @@ describe('Prometheus middleware', () => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
const registry = new Registry()
|
const registry = new Registry()
|
||||||
|
|
||||||
app.use('*', prometheus({
|
app.use(
|
||||||
|
'*',
|
||||||
|
prometheus({
|
||||||
registry,
|
registry,
|
||||||
}).registerMetrics)
|
}).registerMetrics
|
||||||
|
)
|
||||||
|
|
||||||
app.get('/', (c) => c.text('hello'))
|
app.get('/', (c) => c.text('hello'))
|
||||||
app.get('/user/:id', (c) => c.text(c.req.param('id')))
|
app.get('/user/:id', (c) => c.text(c.req.param('id')))
|
||||||
|
@ -21,10 +24,13 @@ describe('Prometheus middleware', () => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
const registry = new Registry()
|
const registry = new Registry()
|
||||||
|
|
||||||
app.use('*', prometheus({
|
app.use(
|
||||||
|
'*',
|
||||||
|
prometheus({
|
||||||
registry,
|
registry,
|
||||||
prefix: 'myprefix_',
|
prefix: 'myprefix_',
|
||||||
}).registerMetrics)
|
}).registerMetrics
|
||||||
|
)
|
||||||
|
|
||||||
expect(await registry.metrics()).toMatchInlineSnapshot(`
|
expect(await registry.metrics()).toMatchInlineSnapshot(`
|
||||||
"# HELP myprefix_http_request_duration_seconds Duration of HTTP requests in seconds
|
"# HELP myprefix_http_request_duration_seconds Duration of HTTP requests in seconds
|
||||||
|
@ -40,17 +46,20 @@ describe('Prometheus middleware', () => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
const registry = new Registry()
|
const registry = new Registry()
|
||||||
|
|
||||||
app.use('*', prometheus({
|
app.use(
|
||||||
|
'*',
|
||||||
|
prometheus({
|
||||||
registry,
|
registry,
|
||||||
metricOptions: {
|
metricOptions: {
|
||||||
requestsTotal: {
|
requestsTotal: {
|
||||||
customLabels: {
|
customLabels: {
|
||||||
id: (c) => c.req.query('id') ?? 'unknown',
|
id: (c) => c.req.query('id') ?? 'unknown',
|
||||||
contentType: (c) => c.res.headers.get('content-type') ?? 'unknown',
|
contentType: (c) => c.res.headers.get('content-type') ?? 'unknown',
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}).registerMetrics)
|
},
|
||||||
|
}).registerMetrics
|
||||||
|
)
|
||||||
|
|
||||||
app.get('/', (c) => c.text('hello'))
|
app.get('/', (c) => c.text('hello'))
|
||||||
|
|
||||||
|
@ -80,7 +89,7 @@ describe('Prometheus middleware', () => {
|
||||||
ok: 'true',
|
ok: 'true',
|
||||||
},
|
},
|
||||||
value: 1,
|
value: 1,
|
||||||
}
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -98,7 +107,7 @@ describe('Prometheus middleware', () => {
|
||||||
ok: 'false',
|
ok: 'false',
|
||||||
},
|
},
|
||||||
value: 1,
|
value: 1,
|
||||||
}
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -107,10 +116,13 @@ describe('Prometheus middleware', () => {
|
||||||
it('updates the http_requests_duration metric with the correct labels on successful responses', async () => {
|
it('updates the http_requests_duration metric with the correct labels on successful responses', async () => {
|
||||||
await app.request('http://localhost/')
|
await app.request('http://localhost/')
|
||||||
|
|
||||||
const { values } = await (registry.getSingleMetric('http_request_duration_seconds') as Histogram)!.get()!
|
const { values } = await (registry.getSingleMetric(
|
||||||
|
'http_request_duration_seconds'
|
||||||
|
) as Histogram)!.get()!
|
||||||
|
|
||||||
const countMetric = values.find(
|
const countMetric = values.find(
|
||||||
(v) => v.metricName === 'http_request_duration_seconds_count' &&
|
(v) =>
|
||||||
|
v.metricName === 'http_request_duration_seconds_count' &&
|
||||||
v.labels.method === 'GET' &&
|
v.labels.method === 'GET' &&
|
||||||
v.labels.route === '/' &&
|
v.labels.route === '/' &&
|
||||||
v.labels.status === '200'
|
v.labels.status === '200'
|
||||||
|
@ -122,10 +134,13 @@ describe('Prometheus middleware', () => {
|
||||||
it('updates the http_requests_duration metric with the correct labels on errors', async () => {
|
it('updates the http_requests_duration metric with the correct labels on errors', async () => {
|
||||||
await app.request('http://localhost/notfound')
|
await app.request('http://localhost/notfound')
|
||||||
|
|
||||||
const { values } = await (registry.getSingleMetric('http_request_duration_seconds') as Histogram)!.get()!
|
const { values } = await (registry.getSingleMetric(
|
||||||
|
'http_request_duration_seconds'
|
||||||
|
) as Histogram)!.get()!
|
||||||
|
|
||||||
const countMetric = values.find(
|
const countMetric = values.find(
|
||||||
(v) => v.metricName === 'http_request_duration_seconds_count' &&
|
(v) =>
|
||||||
|
v.metricName === 'http_request_duration_seconds_count' &&
|
||||||
v.labels.method === 'GET' &&
|
v.labels.method === 'GET' &&
|
||||||
v.labels.route === '/*' &&
|
v.labels.route === '/*' &&
|
||||||
v.labels.status === '404'
|
v.labels.status === '404'
|
||||||
|
@ -146,8 +161,8 @@ describe('Prometheus middleware', () => {
|
||||||
metricOptions: {
|
metricOptions: {
|
||||||
requestDuration: {
|
requestDuration: {
|
||||||
disabled: true, // Disable duration metrics to make the test result more predictable
|
disabled: true, // Disable duration metrics to make the test result more predictable
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use('*', registerMetrics)
|
app.use('*', registerMetrics)
|
||||||
|
|
|
@ -2,19 +2,20 @@ import type { Context } from 'hono'
|
||||||
import { createMiddleware } from 'hono/factory'
|
import { createMiddleware } from 'hono/factory'
|
||||||
import type { DefaultMetricsCollectorConfiguration, RegistryContentType } from 'prom-client'
|
import type { DefaultMetricsCollectorConfiguration, RegistryContentType } from 'prom-client'
|
||||||
import { Registry, collectDefaultMetrics as promCollectDefaultMetrics } from 'prom-client'
|
import { Registry, collectDefaultMetrics as promCollectDefaultMetrics } from 'prom-client'
|
||||||
import { type MetricOptions, type CustomMetricsOptions, createStandardMetrics } from './standardMetrics'
|
import {
|
||||||
|
type MetricOptions,
|
||||||
|
type CustomMetricsOptions,
|
||||||
|
createStandardMetrics,
|
||||||
|
} from './standardMetrics'
|
||||||
|
|
||||||
interface PrometheusOptions {
|
interface PrometheusOptions {
|
||||||
registry?: Registry;
|
registry?: Registry
|
||||||
collectDefaultMetrics?: boolean | DefaultMetricsCollectorConfiguration<RegistryContentType>;
|
collectDefaultMetrics?: boolean | DefaultMetricsCollectorConfiguration<RegistryContentType>
|
||||||
prefix?: string;
|
prefix?: string
|
||||||
metricOptions?: Omit<CustomMetricsOptions, 'prefix' | 'register'>;
|
metricOptions?: Omit<CustomMetricsOptions, 'prefix' | 'register'>
|
||||||
}
|
}
|
||||||
|
|
||||||
const evaluateCustomLabels = (
|
const evaluateCustomLabels = (customLabels: MetricOptions['customLabels'], context: Context) => {
|
||||||
customLabels: MetricOptions['customLabels'],
|
|
||||||
context: Context,
|
|
||||||
) => {
|
|
||||||
const labels: Record<string, string> = {}
|
const labels: Record<string, string> = {}
|
||||||
|
|
||||||
for (const [key, fn] of Object.entries(customLabels ?? {})) {
|
for (const [key, fn] of Object.entries(customLabels ?? {})) {
|
||||||
|
@ -36,7 +37,7 @@ export const prometheus = (options?: PrometheusOptions) => {
|
||||||
promCollectDefaultMetrics({
|
promCollectDefaultMetrics({
|
||||||
prefix,
|
prefix,
|
||||||
register: registry,
|
register: registry,
|
||||||
...(typeof collectDefaultMetrics === 'object' && collectDefaultMetrics)
|
...(typeof collectDefaultMetrics === 'object' && collectDefaultMetrics),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +54,6 @@ export const prometheus = (options?: PrometheusOptions) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await next()
|
await next()
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
const commonLabels = {
|
const commonLabels = {
|
||||||
method: c.req.method,
|
method: c.req.method,
|
||||||
|
@ -64,14 +64,14 @@ export const prometheus = (options?: PrometheusOptions) => {
|
||||||
|
|
||||||
timer?.({
|
timer?.({
|
||||||
...commonLabels,
|
...commonLabels,
|
||||||
...evaluateCustomLabels(metricOptions?.requestDuration?.customLabels, c)
|
...evaluateCustomLabels(metricOptions?.requestDuration?.customLabels, c),
|
||||||
})
|
})
|
||||||
|
|
||||||
metrics.requestsTotal?.inc({
|
metrics.requestsTotal?.inc({
|
||||||
...commonLabels,
|
...commonLabels,
|
||||||
...evaluateCustomLabels(metricOptions?.requestsTotal?.customLabels, c)
|
...evaluateCustomLabels(metricOptions?.requestsTotal?.customLabels, c),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@ import type { CounterConfiguration, HistogramConfiguration, Metric } from 'prom-
|
||||||
import { Counter, Histogram, type Registry } from 'prom-client'
|
import { Counter, Histogram, type Registry } from 'prom-client'
|
||||||
|
|
||||||
export type MetricOptions = {
|
export type MetricOptions = {
|
||||||
disabled?: boolean;
|
disabled?: boolean
|
||||||
customLabels?: Record<string, (c: Context) => string>;
|
customLabels?: Record<string, (c: Context) => string>
|
||||||
} & (
|
} & (
|
||||||
({ type: 'counter' } & CounterConfiguration<string>) |
|
| ({ type: 'counter' } & CounterConfiguration<string>)
|
||||||
({ type: 'histogram' } & HistogramConfiguration<string>)
|
| ({ type: 'histogram' } & HistogramConfiguration<string>)
|
||||||
)
|
)
|
||||||
|
|
||||||
const standardMetrics = {
|
const standardMetrics = {
|
||||||
|
@ -35,22 +35,25 @@ export type CustomMetricsOptions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreatedMetrics = {
|
type CreatedMetrics = {
|
||||||
[Name in MetricName]: (typeof standardMetrics)[Name]['type'] extends 'counter' ? Counter<string> : Histogram<string>
|
[Name in MetricName]: (typeof standardMetrics)[Name]['type'] extends 'counter'
|
||||||
|
? Counter<string>
|
||||||
|
: Histogram<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMetricConstructor = (type: MetricOptions['type']) => ({
|
const getMetricConstructor = (type: MetricOptions['type']) =>
|
||||||
|
({
|
||||||
counter: Counter,
|
counter: Counter,
|
||||||
histogram: Histogram,
|
histogram: Histogram,
|
||||||
})[type]
|
}[type])
|
||||||
|
|
||||||
export const createStandardMetrics = ({
|
export const createStandardMetrics = ({
|
||||||
registry,
|
registry,
|
||||||
prefix = '',
|
prefix = '',
|
||||||
customOptions,
|
customOptions,
|
||||||
}: {
|
}: {
|
||||||
registry: Registry;
|
registry: Registry
|
||||||
prefix?: string;
|
prefix?: string
|
||||||
customOptions?: CustomMetricsOptions;
|
customOptions?: CustomMetricsOptions
|
||||||
}) => {
|
}) => {
|
||||||
const createdMetrics: Record<string, Metric> = {}
|
const createdMetrics: Record<string, Metric> = {}
|
||||||
|
|
||||||
|
@ -70,9 +73,10 @@ export const createStandardMetrics = ({
|
||||||
...(opts as object),
|
...(opts as object),
|
||||||
name: `${prefix}${opts.name}`,
|
name: `${prefix}${opts.name}`,
|
||||||
help: opts.help,
|
help: opts.help,
|
||||||
registers: [...opts.registers ?? [], registry],
|
registers: [...(opts.registers ?? []), registry],
|
||||||
labelNames: [...opts.labelNames ?? [], ...Object.keys(opts.customLabels ?? {})],
|
labelNames: [...(opts.labelNames ?? []), ...Object.keys(opts.customLabels ?? {})],
|
||||||
...(opts.type === 'histogram' && opts.buckets && {
|
...(opts.type === 'histogram' &&
|
||||||
|
opts.buckets && {
|
||||||
buckets: opts.buckets,
|
buckets: opts.buckets,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
|
@ -42,7 +42,9 @@ export const sentry = (
|
||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
c.set('sentry', sentry)
|
c.set('sentry', sentry)
|
||||||
if (callback) callback(sentry)
|
if (callback) {
|
||||||
|
callback(sentry)
|
||||||
|
}
|
||||||
|
|
||||||
await next()
|
await next()
|
||||||
if (c.error) {
|
if (c.error) {
|
||||||
|
|
|
@ -58,7 +58,9 @@ export const renderSwaggerUIOptions = (options: DistSwaggerUIOptions) => {
|
||||||
return `${key}: '${v}'`
|
return `${key}: '${v}'`
|
||||||
}
|
}
|
||||||
if (RENDER_TYPE_MAP[key] === RENDER_TYPE.STRING_ARRAY) {
|
if (RENDER_TYPE_MAP[key] === RENDER_TYPE.STRING_ARRAY) {
|
||||||
if (!Array.isArray(v)) return ''
|
if (!Array.isArray(v)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
return `${key}: [${v.map((ve) => `${ve}`).join(',')}]`
|
return `${key}: [${v.map((ve) => `${ve}`).join(',')}]`
|
||||||
}
|
}
|
||||||
if (RENDER_TYPE_MAP[key] === RENDER_TYPE.JSON_STRING) {
|
if (RENDER_TYPE_MAP[key] === RENDER_TYPE.JSON_STRING) {
|
||||||
|
|
|
@ -1,39 +1,37 @@
|
||||||
|
/*eslint quotes: ["off", "single"]*/
|
||||||
|
|
||||||
import { renderSwaggerUIOptions } from '../src/swagger/renderer'
|
import { renderSwaggerUIOptions } from '../src/swagger/renderer'
|
||||||
|
|
||||||
describe('SwaggerUIOption Rendering', () => {
|
describe('SwaggerUIOption Rendering', () => {
|
||||||
it('renders correctly with configUrl', () => {
|
it('renders correctly with configUrl', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
configUrl: 'https://petstore3.swagger.io/api/v3/openapi.json',
|
configUrl: 'https://petstore3.swagger.io/api/v3/openapi.json',
|
||||||
})
|
})
|
||||||
).toEqual('configUrl: \'https://petstore3.swagger.io/api/v3/openapi.json\'')
|
).toEqual("configUrl: 'https://petstore3.swagger.io/api/v3/openapi.json'"))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with presets', () => {
|
it('renders correctly with presets', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
presets: ['SwaggerUIBundle.presets.apis', 'SwaggerUIStandalonePreset'],
|
presets: ['SwaggerUIBundle.presets.apis', 'SwaggerUIStandalonePreset'],
|
||||||
})
|
})
|
||||||
).toEqual('presets: [SwaggerUIBundle.presets.apis,SwaggerUIStandalonePreset]')
|
).toEqual('presets: [SwaggerUIBundle.presets.apis,SwaggerUIStandalonePreset]'))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with plugins', () => {
|
it('renders correctly with plugins', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
plugins: ['SwaggerUIBundle.plugins.DownloadUrl'],
|
plugins: ['SwaggerUIBundle.plugins.DownloadUrl'],
|
||||||
})
|
})
|
||||||
).toEqual('plugins: [SwaggerUIBundle.plugins.DownloadUrl]')
|
).toEqual('plugins: [SwaggerUIBundle.plugins.DownloadUrl]'))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with deepLinking', () => {
|
it('renders correctly with deepLinking', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
deepLinking: true,
|
deepLinking: true,
|
||||||
})
|
})
|
||||||
).toEqual('deepLinking: true')
|
).toEqual('deepLinking: true'))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with spec', () => {
|
it('renders correctly with spec', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
spec: {
|
spec: {
|
||||||
|
@ -51,15 +49,14 @@ describe('SwaggerUIOption Rendering', () => {
|
||||||
})
|
})
|
||||||
).toEqual(
|
).toEqual(
|
||||||
'spec: {"openapi":"3.0.0","info":{"title":"Swagger Petstore","version":"1.0.0"},"servers":[{"url":"https://petstore3.swagger.io/api/v3"}]}'
|
'spec: {"openapi":"3.0.0","info":{"title":"Swagger Petstore","version":"1.0.0"},"servers":[{"url":"https://petstore3.swagger.io/api/v3"}]}'
|
||||||
)
|
))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with url', () => {
|
it('renders correctly with url', () => {
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
|
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
|
||||||
})
|
})
|
||||||
).toEqual('url: \'https://petstore3.swagger.io/api/v3/openapi.json\'')
|
).toEqual("url: 'https://petstore3.swagger.io/api/v3/openapi.json'")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders correctly with urls', () => {
|
it('renders correctly with urls', () => {
|
||||||
|
@ -77,59 +74,52 @@ describe('SwaggerUIOption Rendering', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders correctly with layout', () => {
|
it('renders correctly with layout', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
layout: 'StandaloneLayout',
|
layout: 'StandaloneLayout',
|
||||||
})
|
})
|
||||||
).toEqual('layout: \'StandaloneLayout\'')
|
).toEqual("layout: 'StandaloneLayout'"))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with docExpansion', () => {
|
it('renders correctly with docExpansion', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
docExpansion: 'list',
|
docExpansion: 'list',
|
||||||
})
|
})
|
||||||
).toEqual('docExpansion: \'list\'')
|
).toEqual("docExpansion: 'list'"))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with maxDisplayedTags', () => {
|
it('renders correctly with maxDisplayedTags', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
maxDisplayedTags: 5,
|
maxDisplayedTags: 5,
|
||||||
})
|
})
|
||||||
).toEqual('maxDisplayedTags: 5')
|
).toEqual('maxDisplayedTags: 5'))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with operationsSorter', () => {
|
it('renders correctly with operationsSorter', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
operationsSorter: '(a, b) => a.path.localeCompare(b.path)',
|
operationsSorter: '(a, b) => a.path.localeCompare(b.path)',
|
||||||
})
|
})
|
||||||
).toEqual('operationsSorter: (a, b) => a.path.localeCompare(b.path)')
|
).toEqual('operationsSorter: (a, b) => a.path.localeCompare(b.path)'))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with requestInterceptor', () => {
|
it('renders correctly with requestInterceptor', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
requestInterceptor: '(req) => req',
|
requestInterceptor: '(req) => req',
|
||||||
})
|
})
|
||||||
).toEqual('requestInterceptor: (req) => req')
|
).toEqual('requestInterceptor: (req) => req'))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with responseInterceptor', () => {
|
it('renders correctly with responseInterceptor', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
responseInterceptor: '(res) => res',
|
responseInterceptor: '(res) => res',
|
||||||
})
|
})
|
||||||
).toEqual('responseInterceptor: (res) => res')
|
).toEqual('responseInterceptor: (res) => res'))
|
||||||
})
|
|
||||||
|
|
||||||
it('renders correctly with persistAuthorization', () => {
|
it('renders correctly with persistAuthorization', () =>
|
||||||
expect(
|
expect(
|
||||||
renderSwaggerUIOptions({
|
renderSwaggerUIOptions({
|
||||||
persistAuthorization: true,
|
persistAuthorization: true,
|
||||||
})
|
})
|
||||||
).toEqual('persistAuthorization: true')
|
).toEqual('persistAuthorization: true'))
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
Binary file not shown.
|
@ -41,16 +41,18 @@
|
||||||
"zod": "3.*"
|
"zod": "3.*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@cloudflare/workers-types": "^4.20240117.0",
|
||||||
"hono": "^3.11.7",
|
"hono": "^3.11.7",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"openapi3-ts": "^4.1.2",
|
"openapi3-ts": "^4.1.2",
|
||||||
"tsup": "^8.0.1",
|
"tsup": "^8.0.1",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
"vitest": "^1.0.1",
|
"vitest": "^1.0.1",
|
||||||
"zod": "^3.22.1"
|
"zod": "^3.22.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "^5.5.0",
|
"@asteasolutions/zod-to-openapi": "^5.5.0",
|
||||||
"@hono/zod-validator": "^0.1.11"
|
"@hono/zod-validator": "0.1.11"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
13
yarn.lock
13
yarn.lock
|
@ -876,6 +876,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@cloudflare/workers-types@npm:^4.20240117.0":
|
||||||
|
version: 4.20240117.0
|
||||||
|
resolution: "@cloudflare/workers-types@npm:4.20240117.0"
|
||||||
|
checksum: 900b796af2ae97257e1f6171b9c37d718c5f8ae064cea8f8d1c48e52ccde01492c5cfa61716fc703d05a6bd92e50a55f7d9e525d2c91919db76e42458c3e8e76
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@colors/colors@npm:1.5.0":
|
"@colors/colors@npm:1.5.0":
|
||||||
version: 1.5.0
|
version: 1.5.0
|
||||||
resolution: "@colors/colors@npm:1.5.0"
|
resolution: "@colors/colors@npm:1.5.0"
|
||||||
|
@ -1688,11 +1695,13 @@ __metadata:
|
||||||
resolution: "@hono/zod-openapi@workspace:packages/zod-openapi"
|
resolution: "@hono/zod-openapi@workspace:packages/zod-openapi"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@asteasolutions/zod-to-openapi": "npm:^5.5.0"
|
"@asteasolutions/zod-to-openapi": "npm:^5.5.0"
|
||||||
"@hono/zod-validator": "npm:^0.1.11"
|
"@cloudflare/workers-types": "npm:^4.20240117.0"
|
||||||
|
"@hono/zod-validator": "npm:0.1.11"
|
||||||
hono: "npm:^3.11.7"
|
hono: "npm:^3.11.7"
|
||||||
jest: "npm:^29.7.0"
|
jest: "npm:^29.7.0"
|
||||||
openapi3-ts: "npm:^4.1.2"
|
openapi3-ts: "npm:^4.1.2"
|
||||||
tsup: "npm:^8.0.1"
|
tsup: "npm:^8.0.1"
|
||||||
|
typescript: "npm:^5.3.3"
|
||||||
vitest: "npm:^1.0.1"
|
vitest: "npm:^1.0.1"
|
||||||
zod: "npm:^3.22.1"
|
zod: "npm:^3.22.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1701,7 +1710,7 @@ __metadata:
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@hono/zod-validator@npm:^0.1.11, @hono/zod-validator@workspace:packages/zod-validator":
|
"@hono/zod-validator@npm:0.1.11, @hono/zod-validator@workspace:packages/zod-validator":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@hono/zod-validator@workspace:packages/zod-validator"
|
resolution: "@hono/zod-validator@workspace:packages/zod-validator"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in New Issue