ronantakizawa/codereview-bench huggingface dataset:
Repository: honojs/hono
File: src/jsx/intrinsic-elements.ts (TypeScript)

Code before:
    blocking?: 'render' | undefined
  }

  interface MapHTMLAttributes extends HTMLAttributes {
    name?: string | undefined
  }

  interface MenuHTMLAttributes extends HTMLAttributes {
    type?: string | undefined
  }

  interface MediaHTMLAttributes extends HTMLAttributes {
    autoplay?: boolean | undefined
    controls?: boolean | undefined
    controlslist?: string | undefined
    crossorigin?: CrossOrigin
    loop?: boolean | undefined
    mediagroup?: string | undefined
    muted?: boolean | undefined
    playsinline?: boolean | undefined
    preload?: string | undefined
    src?: string | undefined
  }

  /**
   * String literal types with auto-complition
   * @see https://github.com/Microsoft/TypeScript/issues/29729
   */
  type LiteralUnion<T> = T | (string & Record<never, never>)
  /**
   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#http-equiv
   */
  type MetaHttpEquiv =
    | LiteralUnion<
        'content-security-policy' | 'content-type' | 'default-style' | 'x-ua-compatible' | 'refresh'
      >
    | undefined
  /**
   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name
   */
  type MetaName =
    | LiteralUnion<
        | 'application-name'
        | 'author'
        | 'description'
        | 'generator'
        | 'keywords'
        | 'referrer'
        | 'theme-color'
        | 'color-scheme'
        | 'viewport'

Review comment (suggestion):
`auto-complition` is a typo. Should be `auto-completion`.

Code after:
    blocking?: 'render' | undefined
  }

  interface MapHTMLAttributes extends HTMLAttributes {
    name?: string | undefined
  }

  interface MenuHTMLAttributes extends HTMLAttributes {
    type?: string | undefined
  }

  interface MediaHTMLAttributes extends HTMLAttributes {
    autoplay?: boolean | undefined
    controls?: boolean | undefined
    controlslist?: string | undefined
    crossorigin?: CrossOrigin
    loop?: boolean | undefined
    mediagroup?: string | undefined
    muted?: boolean | undefined
    playsinline?: boolean | undefined
    preload?: string | undefined
    src?: string | undefined
  }

  /**
   * String literal types with auto-completion
   * @see https://github.com/Microsoft/TypeScript/issues/29729
   */
  type LiteralUnion<T> = T | (string & Record<never, never>)
  /**
   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#http-equiv
   */
  type MetaHttpEquiv =
    | 'content-security-policy'
    | 'content-type'
    | 'default-style'
    | 'x-ua-compatible'
    | 'refresh'
  /**
   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name
   */
  type MetaName =
    | 'application-name'
    | 'author'
    | 'description'
    | 'generator'
    | 'keywords'
    | 'referrer'
    | 'theme-color'
    | 'color-scheme'
    | 'viewport'
Repository: honojs/hono
File: src/middleware/secure-headers/secure-headers.ts (TypeScript)

Code before:
          }),
        resultValues,
      ]
}

function getPermissionsPolicyDirectives(policy: PermissionsPolicyOptions): string {
  return Object.entries(policy)
    .map(([directive, value]) => {
      if (typeof value === 'boolean') {
        return `${camelToKebab(directive)}=${value ? '()' : 'none'}`
      }
      if (Array.isArray(value)) {
        if (value.length === 0) {
          return `${camelToKebab(directive)}=()`
        }
        const allowlist = value.map((v) => (v === 'self' || v === 'src' ? v : v)).join(' ')
        return `${camelToKebab(directive)}=(${allowlist})`
      }
      return ''
    })
    .filter(Boolean)
    .join(', ')
}

function camelToKebab(str: string): string {
  return str
    .split('')
    .map((letter, idx) => {
      return letter.toUpperCase() === letter
        ? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}`
        : letter
    })
    .join('')
}

function getReportingEndpoints(
  reportingEndpoints: SecureHeadersOptions['reportingEndpoints'] = []
): string {
  return reportingEndpoints.map((endpoint) => `${endpoint.name}="${endpoint.url}"`).join(', ')
}

function getReportToOptions(reportTo: SecureHeadersOptions['reportTo'] = []): string {
  return reportTo.map((option) => JSON.stringify(option)).join(', ')
}

function setHeaders(ctx: Context, headersToSet: [string, string][]) {
  headersToSet.forEach(([header, value]) => {
    ctx.res.headers.set(header, value)
  })
}


Review comment (suggestion):
By converting uppercase letters that follow lowercase letters or numbers, the implementation becomes simpler
```ts
function camelToKebab(str: string): string {
  return str.replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase()
}
```

Code after:
          }),
        resultValues,
      ]
}

function getPermissionsPolicyDirectives(policy: PermissionsPolicyOptions): string {
  return Object.entries(policy)
    .map(([directive, value]) => {
      const kebabDirective = camelToKebab(directive)

      if (typeof value === 'boolean') {
        return `${kebabDirective}=${value ? '()' : 'none'}`
      }

      if (Array.isArray(value)) {
        const allowlist = value.length === 0 ? '()' : `(${value.join(' ')})`
        return `${kebabDirective}=${allowlist}`
      }

      return ''
    })
    .filter(Boolean)
    .join(', ')
}

function camelToKebab(str: string): string {
  return str.replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase()
}

function getReportingEndpoints(
  reportingEndpoints: SecureHeadersOptions['reportingEndpoints'] = []
): string {
  return reportingEndpoints.map((endpoint) => `${endpoint.name}="${endpoint.url}"`).join(', ')
}

function getReportToOptions(reportTo: SecureHeadersOptions['reportTo'] = []): string {
  return reportTo.map((option) => JSON.stringify(option)).join(', ')
}

function setHeaders(ctx: Context, headersToSet: [string, string][]) {
  headersToSet.forEach(([header, value]) => {
    ctx.res.headers.set(header, value)
  })
}
Repository: honojs/hono
File: src/utils/basic-auth.ts (TypeScript)

Code before:
import type { HonoRequest } from '../request'
import { decodeBase64 } from './encode'

const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/
const USER_PASS_REGEXP = /^([^:]*):(.*)$/
const utf8Decoder = new TextDecoder()

export type Auth = (req: HonoRequest) => { username: string; password: string } | undefined

export const auth: Auth = (req: HonoRequest) => {
  const match = CREDENTIALS_REGEXP.exec(req.header('Authorization') || '')
  if (!match) {
    return undefined
  }

  let userPass = undefined
  // If an invalid string is passed to atob(), it throws a `DOMException`.
  try {
    userPass = USER_PASS_REGEXP.exec(utf8Decoder.decode(decodeBase64(match[1])))
  } catch {} // Do nothing

  if (!userPass) {
    return undefined
  }

  return { username: userPass[1], password: userPass[2] }
}


Review comment (refactor):
I prefer making `req` as `Request` not `HonoRequest`. The utility function should be more general. So, `HonoRequest` that depends on Hono is not good.

Code after:
import { decodeBase64 } from './encode'

const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/
const USER_PASS_REGEXP = /^([^:]*):(.*)$/
const utf8Decoder = new TextDecoder()

export type Auth = (req: Request) => { username: string; password: string } | undefined

export const auth: Auth = (req: Request) => {
  const match = CREDENTIALS_REGEXP.exec(req.headers.get('Authorization') || '')
  if (!match) {
    return undefined
  }

  let userPass = undefined
  // If an invalid string is passed to atob(), it throws a `DOMException`.
  try {
    userPass = USER_PASS_REGEXP.exec(utf8Decoder.decode(decodeBase64(match[1])))
  } catch {} // Do nothing

  if (!userPass) {
    return undefined
  }

  return { username: userPass[1], password: userPass[2] }
}
Repository: honojs/hono
File: src/utils/filepath.ts (TypeScript)

Code before:
  } else if (!filename.match(/\.[a-zA-Z0-9_-]+$/)) {
    // /top => /top/index.html
    filename = filename.concat('/' + defaultDocument)
  }

  const path = getFilePathWithoutDefaultDocument({
    root: options.root,
    allowAbsoluteRoot: options.allowAbsoluteRoot,
    filename,
  })

  return path
}

export const getFilePathWithoutDefaultDocument = (
  options: Omit<FilePathOptions, 'defaultDocument'>
): string | undefined => {
  let root = options.root || ''
  let filename = options.filename

  if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
    return
  }

  // /foo.html => foo.html
  filename = filename.replace(/^\.?[\/\\]/, '')

  // assets\foo => assets/foo
  root = root.replace(/\\/, '/')

  // foo\bar.txt => foo/bar.txt
  filename = filename.replace(/\\/, '/')

  // assets/ => assets
  root = root.replace(/\/$/, '')

  // ./assets/foo.html => assets/foo.html
  let path = root ? root + '/' + filename : filename

  if (!options.allowAbsoluteRoot) {
    path = path.replace(/^\.?\//, '')
  } else {
    // assets => /assets
    path = path.replace(/^(?!\/)/, '/')
    // Using URL to normalize the path.
    const url = new URL(`file://${path}`)
    path = url.pathname
  }

  return path
}

Review comment (suggestion):
I think it would be better to use `/^\.?[\/\\]+/` because it is very dangerous if a `/` remains at the beginning due to the following changes.

https://github.com/honojs/hono/pull/3420/files#diff-ab73a967275a45e7a4ef6280ce16d384179d552efc1095f067f3a43706fb189eR12

```suggestion
  filename = filename.replace(/^\.?[\/\\]+/, '')
```

Code after:
    // /top => /top/index.html
    filename = filename.concat('/' + defaultDocument)
  }

  const path = getFilePathWithoutDefaultDocument({
    root: options.root,
    filename,
  })

  return path
}

export const getFilePathWithoutDefaultDocument = (
  options: Omit<FilePathOptions, 'defaultDocument'>
): string | undefined => {
  let root = options.root || ''
  let filename = options.filename

  if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
    return
  }

  // /foo.html => foo.html
  filename = filename.replace(/^\.?[\/\\]/, '')

  // foo\bar.txt => foo/bar.txt
  filename = filename.replace(/\\/, '/')

  // assets/ => assets
  root = root.replace(/\/$/, '')

  // ./assets/foo.html => assets/foo.html
  let path = root ? root + '/' + filename : filename
  path = path.replace(/^\.?\//, '')

  if (root[0] !== '/' && path[0] === '/') {
    return
  }

  return path
}
Repository: honojs/hono
File: src/jsx/jsx-runtime.ts (TypeScript)

Code before:
/**
 * @module
 * This module provides Hono's JSX runtime.
 */

export { jsxDEV as jsx, Fragment } from './jsx-dev-runtime'
export { jsxDEV as jsxs } from './jsx-dev-runtime'
export type { JSX } from './jsx-dev-runtime'
import { html, raw } from '../helper/html'
import type { HtmlEscapedString, StringBuffer, HtmlEscaped } from '../utils/html'
import { escapeToBuffer, stringBufferToString } from '../utils/html'
import { booleanAttributes } from './base'
import { normalizeIntrinsicElementKey, styleObjectForEach } from './utils'

export { html as jsxTemplate }

export const jsxAttr = (
  key: string,
  v: string | Promise<string> | Record<string, string | number | null | undefined | boolean>
): HtmlEscapedString | Promise<HtmlEscapedString> => {
  key = normalizeIntrinsicElementKey(key)
  const buffer: StringBuffer = [`${key}="`] as StringBuffer
  if (key === 'style' && typeof v === 'object') {
    // object to style strings
    let styleStr = ''
    styleObjectForEach(v as Record<string, string | number>, (property, value) => {
      if (value != null) {
        styleStr += `${styleStr ? ';' : ''}${property}:${value}`
      }
    })
    escapeToBuffer(styleStr, buffer)
    buffer[0] += '"'
  } else if (typeof v === 'string') {
    escapeToBuffer(v, buffer)
    buffer[0] += '"'
  } else if (v === null || v === undefined) {
    return raw('')

Review comment (refactor):
This `booleanAttributes` is never used. Could you remove this line?

Code after:
/**
 * @module
 * This module provides Hono's JSX runtime.
 */

export { jsxDEV as jsx, Fragment } from './jsx-dev-runtime'
export { jsxDEV as jsxs } from './jsx-dev-runtime'
export type { JSX } from './jsx-dev-runtime'
import { html, raw } from '../helper/html'
import type { HtmlEscapedString, StringBuffer, HtmlEscaped } from '../utils/html'
import { escapeToBuffer, stringBufferToString } from '../utils/html'
import { styleObjectForEach } from './utils'

export { html as jsxTemplate }

export const jsxAttr = (
  key: string,
  v: string | Promise<string> | Record<string, string | number | null | undefined | boolean>
): HtmlEscapedString | Promise<HtmlEscapedString> => {
  const buffer: StringBuffer = [`${key}="`] as StringBuffer
  if (key === 'style' && typeof v === 'object') {
    // object to style strings
    let styleStr = ''
    styleObjectForEach(v as Record<string, string | number>, (property, value) => {
      if (value != null) {
        styleStr += `${styleStr ? ';' : ''}${property}:${value}`
      }
    })
    escapeToBuffer(styleStr, buffer)
    buffer[0] += '"'
  } else if (typeof v === 'string') {
    escapeToBuffer(v, buffer)
    buffer[0] += '"'
  } else if (v === null || v === undefined) {
    return raw('')
  } else if (typeof v === 'number' || (v as unknown as HtmlEscaped).isEscaped) {
    buffer[0] += `${v}"`
Repository: honojs/hono
File: src/hono-base.ts (TypeScript)

Code before:
        input = new Request(input, requestInit)
      }
      return this.fetch(input, Env, executionCtx)
    }
    input = input.toString()
    const path = /^https?:\/\//.test(input) ? input : `http://localhost${mergePath('/', input)}`
    const req = new Request(path, requestInit)
    return this.fetch(req, Env, executionCtx)
  }

  /**
   * `.fire()` automatically adds a global fetch event listener.
   * This can be useful for environments that adhere to the Service Worker API, such as non-ES module Cloudflare Workers.
   * @see https://hono.dev/docs/api/hono#fire
   * @see https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
   * @see https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/
   */
  fire = (): void => {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    addEventListener('fetch', (event: FetchEventLike): void => {
      event.respondWith(this.dispatch(event.request, event, undefined, event.request.method))
    })
  }
}
export type ExcludeEmptyObject<T> = T extends {} ? ({} extends T ? never : T) : T

export { Hono as HonoBase }


Review comment (refactor):
Shall we move this `ExcludeEmptyObject` to another place (`utils/types.ts`?)?

Code after:
      if (requestInit !== undefined) {
        input = new Request(input, requestInit)
      }
      return this.fetch(input, Env, executionCtx)
    }
    input = input.toString()
    const path = /^https?:\/\//.test(input) ? input : `http://localhost${mergePath('/', input)}`
    const req = new Request(path, requestInit)
    return this.fetch(req, Env, executionCtx)
  }

  /**
   * `.fire()` automatically adds a global fetch event listener.
   * This can be useful for environments that adhere to the Service Worker API, such as non-ES module Cloudflare Workers.
   * @see https://hono.dev/docs/api/hono#fire
   * @see https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
   * @see https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/
   */
  fire = (): void => {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    addEventListener('fetch', (event: FetchEventLike): void => {
      event.respondWith(this.dispatch(event.request, event, undefined, event.request.method))
    })
  }
}

export { Hono as HonoBase }
Repository: honojs/hono
File: src/middleware/powered-by/index.test.ts (TypeScript)

Code before:
import { Hono } from '../../hono'
import { poweredBy } from '.'

describe('Powered by Middleware', () => {
  const app = new Hono()

  app.use('/poweredBy/*', poweredBy())
  app.get('/poweredBy', (c) => c.text('root'))

  app.use('/poweredBy2/*', poweredBy())
  app.use('/poweredBy2/*', poweredBy())
  app.get('/poweredBy2', (c) => c.text('root'))

  app.use('/poweredByPatrickJS/*', poweredBy({ serverName: 'PatrickJS' }))
  app.get('/poweredByPatrickJS', (c) => c.text('root'))

  it('Should return with X-Powered-By header', async () => {
    const res = await app.request('http://localhost/poweredBy')
    expect(res).not.toBeNull()
    expect(res.status).toBe(200)
    expect(res.headers.get('X-Powered-By')).toBe('Hono')
  })

  it('Should not return duplicate values', async () => {
    const res = await app.request('http://localhost/poweredBy2')
    expect(res).not.toBeNull()
    expect(res.status).toBe(200)
    expect(res.headers.get('X-Powered-By')).toBe('Hono')
  })

  it('Should return custom serverName', async () => {
    const res = await app.request('http://localhost/poweredByPatrickJS')
    expect(res).not.toBeNull()
    expect(res.status).toBe(200)
    expect(res.headers.get('X-Powered-By')).toBe('PatrickJS')
  })
})


Review comment (suggestion):
Using your name `PatrickJS` is not proper for this test. It's good to use more general words. `foo` is okay though it's boring.

Code after:
import { Hono } from '../../hono'
import { poweredBy } from '.'

describe('Powered by Middleware', () => {
  const app = new Hono()

  app.use('/poweredBy/*', poweredBy())
  app.get('/poweredBy', (c) => c.text('root'))

  app.use('/poweredBy2/*', poweredBy())
  app.use('/poweredBy2/*', poweredBy())
  app.get('/poweredBy2', (c) => c.text('root'))

  app.use('/poweredBy3/*', poweredBy({ serverName: 'Foo' }))
  app.get('/poweredBy3', (c) => c.text('root'))

  it('Should return with X-Powered-By header', async () => {
    const res = await app.request('http://localhost/poweredBy')
    expect(res).not.toBeNull()
    expect(res.status).toBe(200)
    expect(res.headers.get('X-Powered-By')).toBe('Hono')
  })

  it('Should not return duplicate values', async () => {
    const res = await app.request('http://localhost/poweredBy2')
    expect(res).not.toBeNull()
    expect(res.status).toBe(200)
    expect(res.headers.get('X-Powered-By')).toBe('Hono')
  })

  it('Should return custom serverName', async () => {
    const res = await app.request('http://localhost/poweredBy3')
    expect(res).not.toBeNull()
    expect(res.status).toBe(200)
    expect(res.headers.get('X-Powered-By')).toBe('Foo')
  })
})
Repository: honojs/hono
File: src/adapter/service-worker/handler.test.ts (TypeScript)

Code before:
import { Hono } from '../../hono'
import { handle } from './handler'
import type { FetchEvent } from './types'

beforeAll(() => {
  // fetch errors when it's not bound to globalThis in service worker
  // set a fetch stub to emulate that behavior
  vi.stubGlobal(
    'fetch',
    function fetch(this: undefined | typeof globalThis, arg0: string | Request) {
      if (this !== globalThis) {
        const error = new Error(
          'Failed to execute \'fetch\' on \'WorkerGlobalScope\': Illegal invocation'
        )
        error.name = 'TypeError'
        throw error
      }
      if (arg0 instanceof Request && arg0.url === 'http://localhost/fallback') {
        return new Response('hello world')
      }
      return Response.error()
    }
  )
})
afterAll(() => {
  vi.unstubAllGlobals()
})

describe('handle', () => {
  it('Success to fetch', async () => {
    const app = new Hono()
    app.get('/', (c) => {
      return c.json({ hello: 'world' })
    })
    const handler = handle(app)
    const json = await new Promise<Response>((resolve) => {
      handler({
        request: new Request('http://localhost/'),

Review comment (bug):
The formatter and linter is conflicting in this line and that's the reason why the CI is failing.
Prettier tries to change this into `"Failed to execute 'fetch' on 'WorkerGlobalScope': Illegal invocation"` and ESLint's quotes turns this into `'Failed to execute \'fetch\' on \'WorkerGlobalScope\': Illegal invocation'`.


Code after:
import { Hono } from '../../hono'
import { handle } from './handler'
import type { FetchEvent } from './types'

beforeAll(() => {
  // fetch errors when it's not bound to globalThis in service worker
  // set a fetch stub to emulate that behavior
  vi.stubGlobal(
    'fetch',
    function fetch(this: undefined | typeof globalThis, arg0: string | Request) {
      if (this !== globalThis) {
        const error = new Error(
          // eslint-disable-next-line quotes
          "Failed to execute 'fetch' on 'WorkerGlobalScope': Illegal invocation"
        )
        error.name = 'TypeError'
        throw error
      }
      if (arg0 instanceof Request && arg0.url === 'http://localhost/fallback') {
        return new Response('hello world')
      }
      return Response.error()
    }
  )
})
afterAll(() => {
  vi.unstubAllGlobals()
})

describe('handle', () => {
  it('Success to fetch', async () => {
    const app = new Hono()
    app.get('/', (c) => {
      return c.json({ hello: 'world' })
    })
    const handler = handle(app)
    const json = await new Promise<Response>((resolve) => {
      handler({
Repository: honojs/hono
File: src/client/types.ts (TypeScript)

Code before:

export type InferRequestOptionsType<T> = T extends (
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  args: any,
  options: infer R
) => Promise<ClientResponse<unknown>>
  ? NonNullable<R>
  : never

type PathToChain<
  Path extends string,
  E extends Schema,
  Original extends string = Path
> = Path extends `/${infer P}`
  ? PathToChain<P, E, Path>
  : Path extends `${infer P}/${infer R}`
  ? { [K in P]: PathToChain<R, E, Original> }
  : {
      [K in Path extends '' ? 'index' : Path]: ClientRequest<
        E extends Record<string, unknown> ? E[Original] : never
      >
    }

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Client<T> = T extends Hono<any, infer S, any>
  ? S extends Record<infer K, Schema>
    ? K extends string
      ? PathToChain<K, S>
      : never
    : never
  : never

export type Callback = (opts: CallbackOptions) => unknown

interface CallbackOptions {
  path: string[]
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  args: any[]
}

export type ObjectType<T = unknown> = {
  [key: string]: T
}


Review comment (suggestion):
Weird again.

The reason why I added `ExcludeEmptyObject` is that schema type with empty object type broke RPC type.
For example, `T` in code below is `never` without `ExcludeEmptyObject`.
```typescript
const app = new Hono().route('/', new Hono().get('/foo', c => c.json({ ok: true })))
type T = Client<typeof app>
```

But, replacing `Hono` with `HonoBase` in `Client` fixed the problem


Code after:
  : never

export type InferRequestOptionsType<T> = T extends (
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  args: any,
  options: infer R
) => Promise<ClientResponse<unknown>>
  ? NonNullable<R>
  : never

type PathToChain<
  Path extends string,
  E extends Schema,
  Original extends string = Path
> = Path extends `/${infer P}`
  ? PathToChain<P, E, Path>
  : Path extends `${infer P}/${infer R}`
  ? { [K in P]: PathToChain<R, E, Original> }
  : {
      [K in Path extends '' ? 'index' : Path]: ClientRequest<
        E extends Record<string, unknown> ? E[Original] : never
      >
    }

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Client<T> = T extends HonoBase<any, infer S, any>
  ? S extends Record<infer K, Schema>
    ? K extends string
      ? PathToChain<K, S>
      : never
    : never
  : never

export type Callback = (opts: CallbackOptions) => unknown

interface CallbackOptions {
  path: string[]
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  args: any[]
}

export type ObjectType<T = unknown> = {
  [key: string]: T
}