Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import { openapi, withHeaders } from '../src/index'
const app = new Elysia()
.use(
openapi({
embedSchema: true,
mapJsonSchema: {
zod: z.toJSONSchema
},
scalar: {
onBeforeRequest: ({ request }) => {
console.info('onBeforeRequest', request.method, request.url)
}
}
})
)
Expand Down
87 changes: 70 additions & 17 deletions src/scalar/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { OpenAPIV3 } from 'openapi-types'
import type { ApiReferenceConfiguration } from '@scalar/types'
import { ElysiaOpenAPIConfig } from '../types'

const elysiaCSS = `.light-mode {
Expand Down Expand Up @@ -123,10 +122,75 @@ const elysiaCSS = `.light-mode {
filter: opacity(4%) saturate(200%);
}`

const serializeArrayWithFunctions = (arr: unknown[]): string => {
return `[${arr.map((item) => (typeof item === 'function' ? item.toString() : JSON.stringify(item))).join(', ')}]`
}
Comment on lines +125 to +127
Copy link

@coderabbitai coderabbitai bot Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

❓ Verification inconclusive

Verify function serialization handles all function types.

The serializeArrayWithFunctions helper uses toString() to serialize functions. Ensure this approach correctly handles:

  • Arrow functions
  • Async functions
  • Generator functions
  • Methods with complex closures

Additionally, consider that toString() does not capture the closure scope, so functions referencing external variables may not work as expected when deserialized.

Run the following script to check if there are existing tests for function serialization:


🏁 Script executed:

#!/bin/bash
# Description: Search for tests related to function serialization in scalar configuration

# Search for test files that might cover getScriptTags or serializeArrayWithFunctions
rg -n "getScriptTags|serializeArrayWithFunctions|onBeforeRequest" --type ts --glob '*test*' --glob '*spec*'

Length of output: 0


Add unit tests for serializeArrayWithFunctions covering all function forms
serializeArrayWithFunctions relies on toString(), which may not serialize async/generator/arrow functions consistently and doesn’t capture closure scope. Add tests for:

  • Arrow functions
  • Async functions
  • Generator functions
  • Functions referencing external variables

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine this is a common pattern where you declare a function on the runtime (Bun) side that then gets injected into the webpage.
So, I wouldn’t add extra checks that could make the code more complex to maintain.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcalexiei, understood! Your point about this being a common pattern for runtime-to-webpage function injection makes sense, and keeping the implementation straightforward is a reasonable trade-off.

🐰


✏️ Learnings added
Learnt from: marcalexiei
PR: elysiajs/elysia-openapi#281
File: src/scalar/index.ts:125-127
Timestamp: 2025-10-09T09:45:40.847Z
Learning: In the elysia-openapi repository, when serializing functions for injection from the runtime (Bun) side into webpages (like in serializeArrayWithFunctions in src/scalar/index.ts), the team prefers to keep the code simple and maintainable rather than adding extensive checks for different function types (arrow, async, generator) or edge cases, as this is a common pattern in their codebase.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


/**
* Generates the complete HTML script block required for Scalar setup, based on the provided configuration.
*
* This includes:
* 1. The Scalar bundle script.
* 2. An inline script that initializes the Scalar reference with user-provided configuration data.
*
* This function is adapted from the Scalar Core implementation.
* @see https://github.com/scalar/scalar/blob/main/packages/core/src/libs/html-rendering/html-rendering.ts#L93
*
* @param config - The Scalar configuration object.
* @returns A string containing all required <script> tags for embedding Scalar.
*/
export function getScriptTags({
cdn,
...configuration
}: NonNullable<ElysiaOpenAPIConfig['scalar']>) {
const restConfig = { ...configuration }

const functionProps: string[] = []

for (const [key, value] of Object.entries(configuration) as [
keyof typeof configuration,
unknown
][]) {
if (typeof value === 'function') {
functionProps.push(`"${key}": ${value.toString()}`)
delete restConfig[key]
} else if (
Array.isArray(value) &&
value.some((item) => typeof item === 'function')
) {
// Handle arrays that contain functions (like plugins)
functionProps.push(
`"${key}": ${serializeArrayWithFunctions(value)}`
)
delete restConfig[key]
}
}

// Stringify the rest of the configuration
const configString = JSON.stringify(restConfig, null, 2)
.split('\n')
.map((line, index) => (index === 0 ? line : ' ' + line))
.join('\n')
.replace(/\s*}$/, '') // Remove the closing brace and any whitespace before it

const functionPropsString = functionProps.length
? `,\n ${functionProps.join(',\n ')}\n }`
: '}'

return `
<!-- Scalar script -->
<script src="${cdn ?? 'https://cdn.jsdelivr.net/npm/@scalar/api-reference'}"></script>

<!-- Initialize the Scalar API Reference using provided config -->
<script type="text/javascript">
Scalar.createApiReference('#app', ${configString}${functionPropsString})
</script>`
}

export const ScalarRender = (
info: OpenAPIV3.InfoObject,
config: NonNullable<ElysiaOpenAPIConfig['scalar']>,
embedSpec?: string
info: OpenAPIV3.InfoObject,
config: NonNullable<ElysiaOpenAPIConfig['scalar']>,
embedSpec?: string
) => `<!doctype html>
<html>
<head>
Expand All @@ -153,18 +217,7 @@ export const ScalarRender = (
</style>
</head>
<body>
<script
id="api-reference"
data-configuration='${JSON.stringify(
Object.assign(
config,
{
content: embedSpec
}
)
)}'
>
</script>
<script src="${config.cdn}" crossorigin></script>
<div id="app"></div>
${getScriptTags(Object.assign(config, { content: embedSpec }) )}
</body>
</html>`
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export interface ElysiaOpenAPIConfig<
*'
* @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md
*/
scalar?: ApiReferenceConfiguration & {
scalar?: Partial<ApiReferenceConfiguration> & {
/**
* Version to use for Scalar cdn bundle
*
Expand Down