feat: mockly-hono — file-based mock API server (Hono)
File path = route. JSON/JSON5, dynamic .ts handlers + faker, templating, stateful CRUD (pagination/sort/persist), __variants, sequence, chaos, rate-limit, auth, validation, per-route + global proxy/record, splat routes, X-Scenario overlays, SSE. Visual docs (Scalar) at /__docs from a live OpenAPI 3 spec. OpenAPI import script. Zero-build via tsx. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import type { MockContext } from './respond.ts'
|
||||
|
||||
/**
|
||||
* Replace `{{ path }}` placeholders inside a JSON value.
|
||||
* {{params.id}} {{query.q}} {{headers.x-foo}}
|
||||
* {{faker.person.fullName}} {{faker.number.int}} (zero-arg faker methods)
|
||||
* If a string is exactly one placeholder, the resolved (typed) value is used —
|
||||
* so {{faker.number.int}} yields a number, not "123".
|
||||
*/
|
||||
const RE = /\{\{\s*([\w.$-]+)\s*\}\}/g
|
||||
|
||||
function resolve(path: string, ctx: MockContext): unknown {
|
||||
const parts = path.split('.')
|
||||
let cur: any =
|
||||
parts[0] === 'faker'
|
||||
? ctx.faker
|
||||
: parts[0] === 'params'
|
||||
? ctx.params
|
||||
: parts[0] === 'query'
|
||||
? ctx.query
|
||||
: parts[0] === 'headers'
|
||||
? ctx.headers
|
||||
: undefined
|
||||
if (cur === undefined) return undefined
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
cur = cur?.[parts[i]]
|
||||
if (cur === undefined) return undefined
|
||||
}
|
||||
return typeof cur === 'function' ? cur.call(parts[0] === 'faker' ? undefined : cur) : cur
|
||||
}
|
||||
|
||||
export function hasTemplate(v: unknown): boolean {
|
||||
if (typeof v === 'string') return RE.test(v)
|
||||
if (Array.isArray(v)) return v.some(hasTemplate)
|
||||
if (v && typeof v === 'object') return Object.values(v).some(hasTemplate)
|
||||
return false
|
||||
}
|
||||
|
||||
export function applyTemplate(value: unknown, ctx: MockContext): unknown {
|
||||
if (typeof value === 'string') {
|
||||
const whole = value.match(/^\{\{\s*([\w.$-]+)\s*\}\}$/)
|
||||
if (whole) {
|
||||
const r = resolve(whole[1], ctx)
|
||||
return r === undefined ? value : r
|
||||
}
|
||||
return value.replace(RE, (m, p) => {
|
||||
const r = resolve(p, ctx)
|
||||
return r === undefined ? m : String(r)
|
||||
})
|
||||
}
|
||||
if (Array.isArray(value)) return value.map((v) => applyTemplate(v, ctx))
|
||||
if (value && typeof value === 'object') {
|
||||
const out: Record<string, unknown> = {}
|
||||
for (const [k, v] of Object.entries(value)) out[k] = applyTemplate(v, ctx)
|
||||
return out
|
||||
}
|
||||
return value
|
||||
}
|
||||
Reference in New Issue
Block a user