Skip to content
Closed
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
66 changes: 59 additions & 7 deletions benches/static-file-handler.bench.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,62 @@
import type { StandardLazyRequest } from '@standard-server/core'
import type { Stats } from 'node:fs'
import { Buffer } from 'node:buffer'
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { Readable } from 'node:stream'
import { StaticFileHandlerPlugin } from '@orpc/node'
import { RPCHandlerCodec, StandardHandler } from '@orpc/server/standard'
import { bench } from 'vitest'
import { drainBody } from './__shared__/payloads'

const rootDir = mkdtempSync(path.join(tmpdir(), 'orpc-static-file-bench-'))
/**
* CodSpeed measures a single call, so files are served from memory: real fs calls hop through the
* libuv thread pool and the event loop, which lets thread scheduling and unrelated callbacks
* (like vitest's worker IPC) land in the measurement and flip results between runs.
* The shared payloads are not imported either, since building them changes how much V8 compilation
* lands in the first measured call.
*/
vi.mock('node:fs', async importOriginal => ({ ...await importOriginal<typeof import('node:fs')>(), createReadStream }))
vi.mock('node:fs/promises', async importOriginal => ({ ...await importOriginal<typeof import('node:fs/promises')>(), stat, realpath }))

writeFileSync(path.join(rootDir, 'file.txt'), Buffer.alloc(10 * 1024, 'a'))
mkdirSync(path.join(rootDir, 'deeply', 'nested', 'dir'), { recursive: true })
writeFileSync(path.join(rootDir, 'deeply', 'nested', 'dir', 'file.txt'), Buffer.alloc(10 * 1024, 'a'))
const rootDir = path.resolve('/static')
const mtime = new Date('2024-01-01T00:00:00.000Z')

const files = new Map([
[path.join(rootDir, 'file.txt'), Buffer.alloc(10 * 1024, 'a')],
[path.join(rootDir, 'deeply', 'nested', 'dir', 'file.txt'), Buffer.alloc(10 * 1024, 'a')],
])

function notFound(filePath: string): Error {
return Object.assign(new Error(`ENOENT: no such file or directory, '${filePath}'`), { code: 'ENOENT' })
}

async function stat(filePath: string): Promise<Stats> {
const content = files.get(filePath)

if (content === undefined) {
throw notFound(filePath)
}

return { size: content.length, mtime, isFile: () => true, isDirectory: () => false } as Stats
}

async function realpath(filePath: string): Promise<string> {
if (filePath !== rootDir && !files.has(filePath)) {
throw notFound(filePath)
}

return filePath
}

function createReadStream(filePath: string, { start = 0, end = Infinity }: { start?: number, end?: number } = {}): Readable {
const chunk = files.get(filePath)!.subarray(start, end + 1)

return new Readable({
read() {
this.push(chunk)
this.push(null)
},
})
}

const handler = new StandardHandler(new RPCHandlerCodec({}, {}), {
plugins: [new StaticFileHandlerPlugin({ rootDir })],
Expand All @@ -27,6 +71,14 @@ function createRequest(url: `/${string}`, headers: Record<string, string> = {}):
}
}

async function drainBody(body: unknown): Promise<void> {
const reader = (body as ReadableStream<Uint8Array>).getReader()

while (!(await reader.read()).done) {
// discard the chunk
}
}

const { response } = await handler.handle(createRequest('/file.txt'), { context: {} })
await drainBody(response!.body)
const etag = response!.headers.etag as string
Expand Down
Loading