@pyreon/zero 0.11.5 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/link.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import { createRef } from "@pyreon/core"
2
- import { useRouter } from "@pyreon/router"
3
- import { useIntersectionObserver } from "./utils/use-intersection-observer"
1
+ import { createRef } from '@pyreon/core'
2
+ import { useRouter } from '@pyreon/router'
3
+ import { useIntersectionObserver } from './utils/use-intersection-observer'
4
4
 
5
5
  // ─── Link component with prefetching ────────────────────────────────────────
6
6
  //
@@ -23,19 +23,19 @@ export interface LinkProps {
23
23
  /** Class applied when this link exactly matches the current route. */
24
24
  exactActiveClass?: string
25
25
  /** Prefetch strategy. Default: "hover" */
26
- prefetch?: "hover" | "viewport" | "none"
26
+ prefetch?: 'hover' | 'viewport' | 'none'
27
27
  /** Open in new tab. */
28
28
  external?: boolean
29
29
  /** Inline styles. */
30
30
  style?: string
31
31
  /** ARIA label. */
32
- "aria-label"?: string
32
+ 'aria-label'?: string
33
33
  }
34
34
 
35
35
  /** Props passed to a custom component via createLink. */
36
36
  export interface LinkRenderProps {
37
37
  href: string
38
- ref: import("@pyreon/core").Ref<HTMLAnchorElement>
38
+ ref: import('@pyreon/core').Ref<HTMLAnchorElement>
39
39
  onClick: (e: MouseEvent) => void
40
40
  onMouseEnter: () => void
41
41
  onTouchStart: () => void
@@ -46,14 +46,14 @@ export interface LinkRenderProps {
46
46
  style?: string
47
47
  target?: string
48
48
  rel?: string
49
- "aria-label"?: string
49
+ 'aria-label'?: string
50
50
  children?: any
51
51
  }
52
52
 
53
53
  /** Return type of useLink. */
54
54
  export interface UseLinkReturn {
55
55
  /** Ref object — attach to the root element for viewport-based prefetch. */
56
- ref: import("@pyreon/core").Ref<HTMLAnchorElement>
56
+ ref: import('@pyreon/core').Ref<HTMLAnchorElement>
57
57
  /** Click handler — performs client-side navigation. */
58
58
  handleClick: (e: MouseEvent) => void
59
59
  /** Mouse enter handler — triggers hover prefetch. */
@@ -74,15 +74,15 @@ function doPrefetch(href: string) {
74
74
  if (prefetched.has(href)) return
75
75
  prefetched.add(href)
76
76
 
77
- const docLink = document.createElement("link")
78
- docLink.rel = "prefetch"
77
+ const docLink = document.createElement('link')
78
+ docLink.rel = 'prefetch'
79
79
  docLink.href = href
80
- docLink.as = "document"
80
+ docLink.as = 'document'
81
81
  document.head.appendChild(docLink)
82
82
 
83
83
  try {
84
- const chunkHint = document.createElement("link")
85
- chunkHint.rel = "modulepreload"
84
+ const chunkHint = document.createElement('link')
85
+ chunkHint.rel = 'modulepreload'
86
86
  chunkHint.href = href
87
87
  document.head.appendChild(chunkHint)
88
88
  } catch {
@@ -109,7 +109,7 @@ function doPrefetch(href: string) {
109
109
  export function useLink(props: LinkProps): UseLinkReturn {
110
110
  const router = useRouter()
111
111
  const elementRef = createRef<HTMLAnchorElement>()
112
- const strategy = props.prefetch ?? "hover"
112
+ const strategy = props.prefetch ?? 'hover'
113
113
 
114
114
  function handleClick(e: MouseEvent) {
115
115
  if (
@@ -128,18 +128,18 @@ export function useLink(props: LinkProps): UseLinkReturn {
128
128
  }
129
129
 
130
130
  function handleMouseEnter() {
131
- if (strategy === "hover") {
131
+ if (strategy === 'hover') {
132
132
  doPrefetch(props.href)
133
133
  }
134
134
  }
135
135
 
136
136
  function handleTouchStart() {
137
- if (strategy === "hover" || strategy === "viewport") {
137
+ if (strategy === 'hover' || strategy === 'viewport') {
138
138
  doPrefetch(props.href)
139
139
  }
140
140
  }
141
141
 
142
- if (strategy === "viewport") {
142
+ if (strategy === 'viewport') {
143
143
  useIntersectionObserver(
144
144
  () => elementRef.current ?? undefined,
145
145
  () => doPrefetch(props.href),
@@ -149,7 +149,7 @@ export function useLink(props: LinkProps): UseLinkReturn {
149
149
  const isActive = () => {
150
150
  const currentPath = router.currentRoute()?.path
151
151
  if (!currentPath || !props.href) return false
152
- if (props.href === "/") return currentPath === "/"
152
+ if (props.href === '/') return currentPath === '/'
153
153
  return currentPath.startsWith(props.href)
154
154
  }
155
155
 
@@ -164,7 +164,7 @@ export function useLink(props: LinkProps): UseLinkReturn {
164
164
  if (props.class) cls.push(props.class)
165
165
  if (props.activeClass && isActive()) cls.push(props.activeClass)
166
166
  if (props.exactActiveClass && isExactActive()) cls.push(props.exactActiveClass)
167
- return cls.join(" ")
167
+ return cls.join(' ')
168
168
  }
169
169
 
170
170
  return {
@@ -228,8 +228,8 @@ export function createLink(Component: (props: LinkRenderProps) => any): (props:
228
228
  isExactActive={link.isExactActive}
229
229
  class={link.classes}
230
230
  {...(props.style ? { style: props.style } : {})}
231
- {...(props.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
232
- {...(props["aria-label"] ? { "aria-label": props["aria-label"] } : {})}
231
+ {...(props.external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
232
+ {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}
233
233
  children={props.children}
234
234
  />
235
235
  )
@@ -251,8 +251,8 @@ export const Link = createLink((props: LinkRenderProps) => (
251
251
  {...(props.style ? { style: props.style } : {})}
252
252
  {...(props.target ? { target: props.target } : {})}
253
253
  {...(props.rel ? { rel: props.rel } : {})}
254
- {...(props["aria-label"] ? { "aria-label": props["aria-label"] } : {})}
255
- {...(props.isExactActive() ? { "aria-current": "page" as const } : {})}
254
+ {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}
255
+ {...(props.isExactActive() ? { 'aria-current': 'page' as const } : {})}
256
256
  onClick={props.onClick}
257
257
  onMouseEnter={props.onMouseEnter}
258
258
  onTouchStart={props.onTouchStart}
package/src/rate-limit.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Middleware, MiddlewareContext } from "@pyreon/server"
1
+ import type { Middleware, MiddlewareContext } from '@pyreon/server'
2
2
 
3
3
  // ─── Rate limiting middleware ───────────────────────────────────────────────
4
4
 
@@ -61,7 +61,7 @@ export function rateLimitMiddleware(config: RateLimitConfig = {}): Middleware {
61
61
  }, windowMs)
62
62
 
63
63
  // Allow GC to clean up the interval
64
- if (typeof cleanupInterval === "object" && "unref" in cleanupInterval) {
64
+ if (typeof cleanupInterval === 'object' && 'unref' in cleanupInterval) {
65
65
  cleanupInterval.unref()
66
66
  }
67
67
 
@@ -84,21 +84,21 @@ export function rateLimitMiddleware(config: RateLimitConfig = {}): Middleware {
84
84
  const resetSeconds = Math.ceil((entry.resetAt - now) / 1000)
85
85
 
86
86
  // Set rate limit headers on all responses
87
- ctx.headers.set("X-RateLimit-Limit", String(max))
88
- ctx.headers.set("X-RateLimit-Remaining", String(remaining))
89
- ctx.headers.set("X-RateLimit-Reset", String(resetSeconds))
87
+ ctx.headers.set('X-RateLimit-Limit', String(max))
88
+ ctx.headers.set('X-RateLimit-Remaining', String(remaining))
89
+ ctx.headers.set('X-RateLimit-Reset', String(resetSeconds))
90
90
 
91
91
  if (entry.count > max) {
92
92
  if (onLimit) return onLimit(ctx)
93
93
 
94
- return new Response(JSON.stringify({ error: "Too many requests" }), {
94
+ return new Response(JSON.stringify({ error: 'Too many requests' }), {
95
95
  status: 429,
96
96
  headers: {
97
- "Content-Type": "application/json",
98
- "Retry-After": String(resetSeconds),
99
- "X-RateLimit-Limit": String(max),
100
- "X-RateLimit-Remaining": "0",
101
- "X-RateLimit-Reset": String(resetSeconds),
97
+ 'Content-Type': 'application/json',
98
+ 'Retry-After': String(resetSeconds),
99
+ 'X-RateLimit-Limit': String(max),
100
+ 'X-RateLimit-Remaining': '0',
101
+ 'X-RateLimit-Reset': String(resetSeconds),
102
102
  },
103
103
  })
104
104
  }
@@ -107,15 +107,15 @@ export function rateLimitMiddleware(config: RateLimitConfig = {}): Middleware {
107
107
 
108
108
  function defaultKeyFn(ctx: MiddlewareContext): string {
109
109
  return (
110
- ctx.req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
111
- ctx.req.headers.get("x-real-ip") ??
112
- "unknown"
110
+ ctx.req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ??
111
+ ctx.req.headers.get('x-real-ip') ??
112
+ 'unknown'
113
113
  )
114
114
  }
115
115
 
116
116
  /** Simple glob matching for path patterns. Supports trailing `*`. */
117
117
  function matchSimpleGlob(pattern: string, path: string): boolean {
118
- if (pattern.endsWith("/*")) {
118
+ if (pattern.endsWith('/*')) {
119
119
  return path.startsWith(pattern.slice(0, -1))
120
120
  }
121
121
  return pattern === path
package/src/script.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import type { VNodeChild } from "@pyreon/core"
2
- import { createRef, onMount, onUnmount } from "@pyreon/core"
3
- import { useIntersectionObserver } from "./utils/use-intersection-observer"
1
+ import type { VNodeChild } from '@pyreon/core'
2
+ import { createRef, onMount, onUnmount } from '@pyreon/core'
3
+ import { useIntersectionObserver } from './utils/use-intersection-observer'
4
4
 
5
5
  // ─── Script optimization component ─────────────────────────────────────────
6
6
  //
@@ -29,11 +29,11 @@ export interface ScriptProps {
29
29
  }
30
30
 
31
31
  export type ScriptStrategy =
32
- | "beforeHydration"
33
- | "afterHydration"
34
- | "onIdle"
35
- | "onInteraction"
36
- | "onViewport"
32
+ | 'beforeHydration'
33
+ | 'afterHydration'
34
+ | 'onIdle'
35
+ | 'onInteraction'
36
+ | 'onViewport'
37
37
 
38
38
  /**
39
39
  * Optimized script loading component.
@@ -55,7 +55,7 @@ export function Script(props: ScriptProps): VNodeChild {
55
55
  // Deduplication
56
56
  if (props.id && document.getElementById(props.id)) return
57
57
 
58
- const script = document.createElement("script")
58
+ const script = document.createElement('script')
59
59
  if (props.src) script.src = props.src
60
60
  if (props.id) script.id = props.id
61
61
  script.async = props.async !== false
@@ -73,28 +73,28 @@ export function Script(props: ScriptProps): VNodeChild {
73
73
  }
74
74
 
75
75
  onMount(() => {
76
- const strategy = props.strategy ?? "afterHydration"
76
+ const strategy = props.strategy ?? 'afterHydration'
77
77
 
78
78
  switch (strategy) {
79
- case "beforeHydration":
79
+ case 'beforeHydration':
80
80
  // Already in HTML — do nothing
81
81
  break
82
82
 
83
- case "afterHydration":
83
+ case 'afterHydration':
84
84
  // Load immediately after mount (hydration is complete)
85
85
  loadScript()
86
86
  break
87
87
 
88
- case "onIdle":
89
- if ("requestIdleCallback" in window) {
88
+ case 'onIdle':
89
+ if ('requestIdleCallback' in window) {
90
90
  requestIdleCallback(() => loadScript(), { timeout: 5000 })
91
91
  } else {
92
92
  setTimeout(loadScript, 200)
93
93
  }
94
94
  break
95
95
 
96
- case "onInteraction": {
97
- const events = ["click", "scroll", "keydown", "touchstart"]
96
+ case 'onInteraction': {
97
+ const events = ['click', 'scroll', 'keydown', 'touchstart']
98
98
  function handler() {
99
99
  for (const e of events) document.removeEventListener(e, handler)
100
100
  loadScript()
@@ -108,7 +108,7 @@ export function Script(props: ScriptProps): VNodeChild {
108
108
  break
109
109
  }
110
110
 
111
- case "onViewport":
111
+ case 'onViewport':
112
112
  // Handled below via useIntersectionObserver on the sentinel element
113
113
  break
114
114
  }
@@ -116,16 +116,16 @@ export function Script(props: ScriptProps): VNodeChild {
116
116
  })
117
117
 
118
118
  const sentinelRef = createRef<HTMLElement>()
119
- const strategy = props.strategy ?? "afterHydration"
119
+ const strategy = props.strategy ?? 'afterHydration'
120
120
 
121
- if (strategy === "onViewport") {
121
+ if (strategy === 'onViewport') {
122
122
  useIntersectionObserver(
123
123
  () => sentinelRef.current ?? undefined,
124
124
  () => loadScript(),
125
125
  )
126
126
  }
127
127
 
128
- if (strategy === "onViewport") {
128
+ if (strategy === 'onViewport') {
129
129
  return <div ref={sentinelRef} style="width:0;height:0;overflow:hidden" />
130
130
  }
131
131
 
package/src/seo.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Middleware } from "@pyreon/server"
2
- import type { Plugin } from "vite"
1
+ import type { Middleware } from '@pyreon/server'
2
+ import type { Plugin } from 'vite'
3
3
 
4
4
  // ─── SEO utilities ──────────────────────────────────────────────────────────
5
5
  //
@@ -29,37 +29,37 @@ export interface SitemapEntry {
29
29
  lastmod?: string
30
30
  }
31
31
 
32
- export type ChangeFreq = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never"
32
+ export type ChangeFreq = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'
33
33
 
34
34
  /**
35
35
  * Generate a sitemap.xml string from route file paths.
36
36
  */
37
37
  export function generateSitemap(routeFiles: string[], config: SitemapConfig): string {
38
- const { origin, exclude = [], changefreq = "weekly", priority = 0.7 } = config
38
+ const { origin, exclude = [], changefreq = 'weekly', priority = 0.7 } = config
39
39
 
40
40
  const paths = routeFiles
41
41
  .filter((f) => {
42
42
  // Exclude layout, error, loading files
43
43
  const name = f
44
- .split("/")
44
+ .split('/')
45
45
  .pop()
46
- ?.replace(/\.\w+$/, "")
47
- return name !== "_layout" && name !== "_error" && name !== "_loading"
46
+ ?.replace(/\.\w+$/, '')
47
+ return name !== '_layout' && name !== '_error' && name !== '_loading'
48
48
  })
49
49
  .map((f) => {
50
50
  // Convert file path to URL
51
51
  let path = f
52
- .replace(/\.\w+$/, "")
53
- .replace(/\/index$/, "/")
54
- .replace(/^index$/, "/")
52
+ .replace(/\.\w+$/, '')
53
+ .replace(/\/index$/, '/')
54
+ .replace(/^index$/, '/')
55
55
 
56
56
  // Skip dynamic routes — they need additionalPaths
57
- if (path.includes("[")) return null
57
+ if (path.includes('[')) return null
58
58
 
59
59
  // Strip route groups
60
- path = path.replace(/\([\w-]+\)\//g, "")
60
+ path = path.replace(/\([\w-]+\)\//g, '')
61
61
 
62
- if (!path.startsWith("/")) path = `/${path}`
62
+ if (!path.startsWith('/')) path = `/${path}`
63
63
  return path
64
64
  })
65
65
  .filter((p): p is string => p !== null)
@@ -72,14 +72,14 @@ export function generateSitemap(routeFiles: string[], config: SitemapConfig): st
72
72
 
73
73
  const entries = allPaths
74
74
  .map((entry) => {
75
- const loc = `${origin}${entry.path === "/" ? "" : entry.path}`
75
+ const loc = `${origin}${entry.path === '/' ? '' : entry.path}`
76
76
  return ` <url>
77
77
  <loc>${escapeXml(loc)}</loc>
78
78
  <changefreq>${entry.changefreq ?? changefreq}</changefreq>
79
- <priority>${entry.priority ?? priority}</priority>${entry.lastmod ? `\n <lastmod>${entry.lastmod}</lastmod>` : ""}
79
+ <priority>${entry.priority ?? priority}</priority>${entry.lastmod ? `\n <lastmod>${entry.lastmod}</lastmod>` : ''}
80
80
  </url>`
81
81
  })
82
- .join("\n")
82
+ .join('\n')
83
83
 
84
84
  return `<?xml version="1.0" encoding="UTF-8"?>
85
85
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@@ -89,11 +89,11 @@ ${entries}
89
89
 
90
90
  function escapeXml(str: string): string {
91
91
  return str
92
- .replace(/&/g, "&amp;")
93
- .replace(/</g, "&lt;")
94
- .replace(/>/g, "&gt;")
95
- .replace(/"/g, "&quot;")
96
- .replace(/'/g, "&apos;")
92
+ .replace(/&/g, '&amp;')
93
+ .replace(/</g, '&lt;')
94
+ .replace(/>/g, '&gt;')
95
+ .replace(/"/g, '&quot;')
96
+ .replace(/'/g, '&apos;')
97
97
  }
98
98
 
99
99
  // ─── Robots.txt ─────────────────────────────────────────────────────────────
@@ -118,7 +118,7 @@ export interface RobotsRule {
118
118
  * Generate a robots.txt string.
119
119
  */
120
120
  export function generateRobots(config: RobotsConfig = {}): string {
121
- const { rules = [{ userAgent: "*", allow: ["/"] }], sitemap, host } = config
121
+ const { rules = [{ userAgent: '*', allow: ['/'] }], sitemap, host } = config
122
122
  const lines: string[] = []
123
123
 
124
124
  for (const rule of rules) {
@@ -130,27 +130,27 @@ export function generateRobots(config: RobotsConfig = {}): string {
130
130
  for (const path of rule.disallow) lines.push(`Disallow: ${path}`)
131
131
  }
132
132
  if (rule.crawlDelay) lines.push(`Crawl-delay: ${rule.crawlDelay}`)
133
- lines.push("")
133
+ lines.push('')
134
134
  }
135
135
 
136
136
  if (sitemap) lines.push(`Sitemap: ${sitemap}`)
137
137
  if (host) lines.push(`Host: ${host}`)
138
138
 
139
- return lines.join("\n")
139
+ return lines.join('\n')
140
140
  }
141
141
 
142
142
  // ─── Structured data (JSON-LD) ──────────────────────────────────────────────
143
143
 
144
144
  export type JsonLdType =
145
- | "WebSite"
146
- | "WebPage"
147
- | "Article"
148
- | "BlogPosting"
149
- | "Product"
150
- | "Organization"
151
- | "Person"
152
- | "BreadcrumbList"
153
- | "FAQPage"
145
+ | 'WebSite'
146
+ | 'WebPage'
147
+ | 'Article'
148
+ | 'BlogPosting'
149
+ | 'Product'
150
+ | 'Organization'
151
+ | 'Person'
152
+ | 'BreadcrumbList'
153
+ | 'FAQPage'
154
154
  | (string & {})
155
155
 
156
156
  /**
@@ -167,7 +167,7 @@ export type JsonLdType =
167
167
  */
168
168
  export function jsonLd(data: Record<string, unknown>): string {
169
169
  const ld = {
170
- "@context": "https://schema.org",
170
+ '@context': 'https://schema.org',
171
171
  ...data,
172
172
  }
173
173
  return `<script type="application/ld+json">${JSON.stringify(ld)}</script>`
@@ -202,13 +202,13 @@ export interface SeoPluginConfig {
202
202
  */
203
203
  export function seoPlugin(config: SeoPluginConfig = {}): Plugin {
204
204
  return {
205
- name: "pyreon-zero-seo",
206
- apply: "build",
205
+ name: 'pyreon-zero-seo',
206
+ apply: 'build',
207
207
 
208
208
  async generateBundle(_, _bundle) {
209
209
  // Generate sitemap.xml
210
210
  if (config.sitemap) {
211
- const { scanRouteFiles } = await import("./fs-router")
211
+ const { scanRouteFiles } = await import('./fs-router')
212
212
  const routesDir = `${process.cwd()}/src/routes`
213
213
 
214
214
  try {
@@ -216,8 +216,8 @@ export function seoPlugin(config: SeoPluginConfig = {}): Plugin {
216
216
  const sitemap = generateSitemap(files, config.sitemap)
217
217
 
218
218
  this.emitFile({
219
- type: "asset",
220
- fileName: "sitemap.xml",
219
+ type: 'asset',
220
+ fileName: 'sitemap.xml',
221
221
  source: sitemap,
222
222
  })
223
223
  } catch {
@@ -230,8 +230,8 @@ export function seoPlugin(config: SeoPluginConfig = {}): Plugin {
230
230
  const robots = generateRobots(config.robots)
231
231
 
232
232
  this.emitFile({
233
- type: "asset",
234
- fileName: "robots.txt",
233
+ type: 'asset',
234
+ fileName: 'robots.txt',
235
235
  source: robots,
236
236
  })
237
237
  }
@@ -247,21 +247,21 @@ export function seoPlugin(config: SeoPluginConfig = {}): Plugin {
247
247
  */
248
248
  export function seoMiddleware(config: SeoPluginConfig = {}): Middleware {
249
249
  return async (ctx) => {
250
- if (ctx.url.pathname === "/robots.txt" && config.robots) {
250
+ if (ctx.url.pathname === '/robots.txt' && config.robots) {
251
251
  return new Response(generateRobots(config.robots), {
252
- headers: { "Content-Type": "text/plain" },
252
+ headers: { 'Content-Type': 'text/plain' },
253
253
  })
254
254
  }
255
255
 
256
- if (ctx.url.pathname === "/sitemap.xml" && config.sitemap) {
256
+ if (ctx.url.pathname === '/sitemap.xml' && config.sitemap) {
257
257
  try {
258
- const { scanRouteFiles } = await import("./fs-router")
258
+ const { scanRouteFiles } = await import('./fs-router')
259
259
  const routesDir = `${process.cwd()}/src/routes`
260
260
  const files = await scanRouteFiles(routesDir)
261
261
  const sitemap = generateSitemap(files, config.sitemap)
262
262
 
263
263
  return new Response(sitemap, {
264
- headers: { "Content-Type": "application/xml" },
264
+ headers: { 'Content-Type': 'application/xml' },
265
265
  })
266
266
  } catch {
267
267
  // Sitemap generation failed — continue to rendering
package/src/sharp.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- declare module "sharp" {
1
+ declare module 'sharp' {
2
2
  interface SharpInstance {
3
3
  resize(width: number, height?: number, options?: { fit?: string }): SharpInstance
4
4
  webp(options?: { quality?: number }): SharpInstance
package/src/testing.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { Middleware, MiddlewareContext } from "@pyreon/server"
2
- import type { ApiHandler, ApiRouteEntry } from "./api-routes"
3
- import { createApiMiddleware } from "./api-routes"
1
+ import type { Middleware, MiddlewareContext } from '@pyreon/server'
2
+ import type { ApiHandler, ApiRouteEntry } from './api-routes'
3
+ import { createApiMiddleware } from './api-routes'
4
4
 
5
5
  // ─── Test helpers for Zero applications ─────────────────────────────────────
6
6
 
@@ -21,14 +21,14 @@ export function createTestContext(
21
21
  body?: unknown
22
22
  } = {},
23
23
  ): MiddlewareContext {
24
- const { method = "GET", headers = {}, body } = options
24
+ const { method = 'GET', headers = {}, body } = options
25
25
  const url = new URL(`http://localhost${path}`)
26
26
 
27
27
  const requestHeaders: Record<string, string> = { ...headers }
28
28
  let requestBody: string | undefined
29
29
 
30
30
  if (body !== undefined) {
31
- requestHeaders["Content-Type"] = "application/json"
31
+ requestHeaders['Content-Type'] = 'application/json'
32
32
  requestBody = JSON.stringify(body)
33
33
  }
34
34
 
@@ -107,7 +107,7 @@ export function createTestApiServer(routes: ApiRouteEntry[]) {
107
107
  const ctx = createTestContext(path, options)
108
108
  const result = await middleware(ctx)
109
109
  if (!result) {
110
- return new Response("Not Found", { status: 404 })
110
+ return new Response('Not Found', { status: 404 })
111
111
  }
112
112
  return result
113
113
  },
@@ -138,7 +138,7 @@ export function createMockHandler(
138
138
  calls.push({ path: ctx.path, params: ctx.params })
139
139
  return new Response(JSON.stringify(body), {
140
140
  status,
141
- headers: { "Content-Type": "application/json", ...headers },
141
+ headers: { 'Content-Type': 'application/json', ...headers },
142
142
  })
143
143
  }
144
144