@pyreon/zero 0.11.5 → 0.11.7

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/api-routes.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { Middleware, MiddlewareContext } from "@pyreon/server"
1
+ import type { Middleware, MiddlewareContext } from '@pyreon/server'
2
2
 
3
3
  // ─── Types ───────────────────────────────────────────────────────────────────
4
4
 
5
5
  /** HTTP methods supported by API routes. */
6
- export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"
6
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'
7
7
 
8
8
  /** Context passed to API route handlers. */
9
9
  export interface ApiContext {
@@ -48,8 +48,8 @@ export interface ApiRouteEntry {
48
48
  * Returns extracted params or null if no match.
49
49
  */
50
50
  export function matchApiRoute(pattern: string, path: string): Record<string, string> | null {
51
- const patternParts = pattern.split("/").filter(Boolean)
52
- const pathParts = path.split("/").filter(Boolean)
51
+ const patternParts = pattern.split('/').filter(Boolean)
52
+ const pathParts = path.split('/').filter(Boolean)
53
53
  const params: Record<string, string> = {}
54
54
 
55
55
  for (let i = 0; i < patternParts.length; i++) {
@@ -57,9 +57,9 @@ export function matchApiRoute(pattern: string, path: string): Record<string, str
57
57
  if (!pp) continue
58
58
 
59
59
  // Catch-all: :param*
60
- if (pp.endsWith("*")) {
60
+ if (pp.endsWith('*')) {
61
61
  const paramName = pp.slice(1, -1)
62
- params[paramName] = pathParts.slice(i).join("/")
62
+ params[paramName] = pathParts.slice(i).join('/')
63
63
  return params
64
64
  }
65
65
 
@@ -67,7 +67,7 @@ export function matchApiRoute(pattern: string, path: string): Record<string, str
67
67
  if (i >= pathParts.length) return null
68
68
 
69
69
  // Dynamic segment: :param
70
- if (pp.startsWith(":")) {
70
+ if (pp.startsWith(':')) {
71
71
  params[pp.slice(1)] = pathParts[i]!
72
72
  continue
73
73
  }
@@ -81,7 +81,7 @@ export function matchApiRoute(pattern: string, path: string): Record<string, str
81
81
 
82
82
  // ─── Middleware ───────────────────────────────────────────────────────────────
83
83
 
84
- const HTTP_METHODS: HttpMethod[] = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
84
+ const HTTP_METHODS: HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']
85
85
 
86
86
  /**
87
87
  * Create a middleware that dispatches API route requests.
@@ -98,12 +98,12 @@ export function createApiMiddleware(routes: ApiRouteEntry[]): Middleware {
98
98
 
99
99
  if (!handler) {
100
100
  // Route matched but method not supported
101
- const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(", ")
101
+ const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(', ')
102
102
  return new Response(null, {
103
103
  status: 405,
104
104
  headers: {
105
105
  Allow: allowed,
106
- "Content-Type": "application/json",
106
+ 'Content-Type': 'application/json',
107
107
  },
108
108
  })
109
109
  }
@@ -126,12 +126,12 @@ export function createApiMiddleware(routes: ApiRouteEntry[]): Middleware {
126
126
  * API routes are `.ts` or `.js` files inside an `api/` directory.
127
127
  */
128
128
  export function isApiRoute(filePath: string): boolean {
129
- const normalized = filePath.replace(/\\/g, "/")
129
+ const normalized = filePath.replace(/\\/g, '/')
130
130
  return (
131
- normalized.startsWith("api/") &&
132
- (normalized.endsWith(".ts") || normalized.endsWith(".js")) &&
133
- !normalized.endsWith(".tsx") &&
134
- !normalized.endsWith(".jsx")
131
+ normalized.startsWith('api/') &&
132
+ (normalized.endsWith('.ts') || normalized.endsWith('.js')) &&
133
+ !normalized.endsWith('.tsx') &&
134
+ !normalized.endsWith('.jsx')
135
135
  )
136
136
  }
137
137
 
@@ -147,18 +147,18 @@ export function isApiRoute(filePath: string): boolean {
147
147
  export function apiFilePathToPattern(filePath: string): string {
148
148
  let route = filePath
149
149
  // Remove extension
150
- for (const ext of [".ts", ".js"]) {
150
+ for (const ext of ['.ts', '.js']) {
151
151
  if (route.endsWith(ext)) {
152
152
  route = route.slice(0, -ext.length)
153
153
  break
154
154
  }
155
155
  }
156
156
 
157
- const segments = route.split("/")
157
+ const segments = route.split('/')
158
158
  const urlSegments: string[] = []
159
159
 
160
160
  for (const seg of segments) {
161
- if (seg === "index") continue
161
+ if (seg === 'index') continue
162
162
 
163
163
  // Catch-all: [...param]
164
164
  const catchAll = seg.match(/^\[\.\.\.(\w+)\]$/)
@@ -177,7 +177,7 @@ export function apiFilePathToPattern(filePath: string): string {
177
177
  urlSegments.push(seg)
178
178
  }
179
179
 
180
- return `/${urlSegments.join("/")}`
180
+ return `/${urlSegments.join('/')}`
181
181
  }
182
182
 
183
183
  /**
@@ -188,7 +188,7 @@ export function generateApiRouteModule(files: string[], routesDir: string): stri
188
188
  const apiFiles = files.filter(isApiRoute)
189
189
 
190
190
  if (apiFiles.length === 0) {
191
- return "export const apiRoutes = []\n"
191
+ return 'export const apiRoutes = []\n'
192
192
  }
193
193
 
194
194
  const imports: string[] = []
@@ -205,5 +205,5 @@ export function generateApiRouteModule(files: string[], routesDir: string): stri
205
205
  entries.push(` { pattern: ${JSON.stringify(pattern)}, module: ${name} }`)
206
206
  }
207
207
 
208
- return [...imports, "", "export const apiRoutes = [", entries.join(",\n"), "]"].join("\n")
208
+ return [...imports, '', 'export const apiRoutes = [', entries.join(',\n'), ']'].join('\n')
209
209
  }
package/src/app.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { ComponentFn, Props } from "@pyreon/core"
2
- import { Fragment, h } from "@pyreon/core"
3
- import { HeadProvider } from "@pyreon/head"
4
- import type { RouteRecord } from "@pyreon/router"
5
- import { createRouter, RouterProvider, RouterView } from "@pyreon/router"
1
+ import type { ComponentFn, Props } from '@pyreon/core'
2
+ import { Fragment, h } from '@pyreon/core'
3
+ import { HeadProvider } from '@pyreon/head'
4
+ import type { RouteRecord } from '@pyreon/router'
5
+ import { createRouter, RouterProvider, RouterView } from '@pyreon/router'
6
6
 
7
7
  // ─── App assembly ────────────────────────────────────────────────────────────
8
8
 
@@ -11,7 +11,7 @@ export interface CreateAppOptions {
11
11
  routes: RouteRecord[]
12
12
 
13
13
  /** Router mode. Default: "history" for SSR, "hash" for SPA. */
14
- routerMode?: "hash" | "history"
14
+ routerMode?: 'hash' | 'history'
15
15
 
16
16
  /** Initial URL for SSR. */
17
17
  url?: string
@@ -31,9 +31,9 @@ export interface CreateAppOptions {
31
31
  export function createApp(options: CreateAppOptions) {
32
32
  const router = createRouter({
33
33
  routes: options.routes,
34
- mode: options.routerMode ?? "history",
34
+ mode: options.routerMode ?? 'history',
35
35
  ...(options.url ? { url: options.url } : {}),
36
- scrollBehavior: "top",
36
+ scrollBehavior: 'top',
37
37
  })
38
38
 
39
39
  const Layout = options.layout ?? DefaultLayout
package/src/cache.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
  // ─── Cache control middleware ───────────────────────────────────────────────
4
4
  //
@@ -38,8 +38,8 @@ const SCRIPT_EXT = /\.(js|css|mjs)$/i
38
38
  /** @internal Exported for testing */
39
39
  export function matchGlob(pattern: string, path: string): boolean {
40
40
  // Escape regex special chars, then convert glob wildcards
41
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&")
42
- const regex = escaped.replace(/\*/g, ".*").replace(/\?/g, ".")
41
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&')
42
+ const regex = escaped.replace(/\*/g, '.*').replace(/\?/g, '.')
43
43
  return new RegExp(`^${regex}$`).test(path)
44
44
  }
45
45
 
@@ -62,7 +62,7 @@ function resolveControl(
62
62
  if (pageDuration > 0) {
63
63
  return `public, max-age=${pageDuration}, stale-while-revalidate=${swr}`
64
64
  }
65
- return "no-cache"
65
+ return 'no-cache'
66
66
  }
67
67
 
68
68
  /**
@@ -97,13 +97,13 @@ export function cacheMiddleware(config: CacheConfig = {}): Middleware {
97
97
 
98
98
  for (const rule of rules) {
99
99
  if (matchGlob(rule.match, path)) {
100
- ctx.headers.set("Cache-Control", rule.control)
100
+ ctx.headers.set('Cache-Control', rule.control)
101
101
  return
102
102
  }
103
103
  }
104
104
 
105
105
  const control = resolveControl(path, immutableDuration, staticDuration, pageDuration, swr)
106
- ctx.headers.set("Cache-Control", control)
106
+ ctx.headers.set('Cache-Control', control)
107
107
  }
108
108
  }
109
109
 
@@ -113,11 +113,11 @@ export function cacheMiddleware(config: CacheConfig = {}): Middleware {
113
113
  */
114
114
  export function securityHeaders(): Middleware {
115
115
  return (ctx: MiddlewareContext) => {
116
- ctx.headers.set("X-Content-Type-Options", "nosniff")
117
- ctx.headers.set("X-Frame-Options", "DENY")
118
- ctx.headers.set("X-XSS-Protection", "1; mode=block")
119
- ctx.headers.set("Referrer-Policy", "strict-origin-when-cross-origin")
120
- ctx.headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
116
+ ctx.headers.set('X-Content-Type-Options', 'nosniff')
117
+ ctx.headers.set('X-Frame-Options', 'DENY')
118
+ ctx.headers.set('X-XSS-Protection', '1; mode=block')
119
+ ctx.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
120
+ ctx.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
121
121
  }
122
122
  }
123
123
 
@@ -128,9 +128,9 @@ export function securityHeaders(): Middleware {
128
128
  */
129
129
  export function varyEncoding(): Middleware {
130
130
  return (ctx: MiddlewareContext) => {
131
- const existing = ctx.headers.get("Vary")
132
- if (!existing?.includes("Accept-Encoding")) {
133
- ctx.headers.set("Vary", existing ? `${existing}, Accept-Encoding` : "Accept-Encoding")
131
+ const existing = ctx.headers.get('Vary')
132
+ if (!existing?.includes('Accept-Encoding')) {
133
+ ctx.headers.set('Vary', existing ? `${existing}, Accept-Encoding` : 'Accept-Encoding')
134
134
  }
135
135
  }
136
136
  }
package/src/client.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { ComponentFn } from "@pyreon/core"
2
- import { h } from "@pyreon/core"
3
- import type { RouteRecord } from "@pyreon/router"
4
- import { hydrateRoot, mount } from "@pyreon/runtime-dom"
5
- import { createApp } from "./app"
1
+ import type { ComponentFn } from '@pyreon/core'
2
+ import { h } from '@pyreon/core'
3
+ import type { RouteRecord } from '@pyreon/router'
4
+ import { hydrateRoot, mount } from '@pyreon/runtime-dom'
5
+ import { createApp } from './app'
6
6
 
7
7
  // ─── Client entry factory ───────────────────────────────────────────────────
8
8
 
@@ -23,12 +23,12 @@ export interface StartClientOptions {
23
23
  * startClient({ routes })
24
24
  */
25
25
  export function startClient(options: StartClientOptions) {
26
- const container = document.getElementById("app")
27
- if (!container) throw new Error("[zero] Missing #app container element")
26
+ const container = document.getElementById('app')
27
+ if (!container) throw new Error('[zero] Missing #app container element')
28
28
 
29
29
  const { App } = createApp({
30
30
  routes: options.routes,
31
- routerMode: "history",
31
+ routerMode: 'history',
32
32
  ...(options.layout ? { layout: options.layout } : {}),
33
33
  })
34
34
 
@@ -1,4 +1,4 @@
1
- import type { Middleware, MiddlewareContext } from "@pyreon/server"
1
+ import type { Middleware, MiddlewareContext } from '@pyreon/server'
2
2
 
3
3
  // ─── Compression middleware ─────────────────────────────────────────────────
4
4
 
@@ -6,7 +6,7 @@ export interface CompressionConfig {
6
6
  /** Minimum response size in bytes to compress. Default: `1024` (1KB) */
7
7
  threshold?: number
8
8
  /** Encoding preference order. Default: `["gzip", "deflate"]` */
9
- encodings?: ("gzip" | "deflate")[]
9
+ encodings?: ('gzip' | 'deflate')[]
10
10
  }
11
11
 
12
12
  /**
@@ -23,10 +23,10 @@ export interface CompressionConfig {
23
23
  * compressionMiddleware({ threshold: 512, encodings: ["gzip"] })
24
24
  */
25
25
  export function compressionMiddleware(config: CompressionConfig = {}): Middleware {
26
- const { threshold = 1024, encodings = ["gzip", "deflate"] } = config
26
+ const { threshold = 1024, encodings = ['gzip', 'deflate'] } = config
27
27
 
28
28
  return (ctx: MiddlewareContext) => {
29
- const acceptEncoding = ctx.req.headers.get("accept-encoding") ?? ""
29
+ const acceptEncoding = ctx.req.headers.get('accept-encoding') ?? ''
30
30
 
31
31
  // Find the best supported encoding
32
32
  const encoding = encodings.find((enc) => acceptEncoding.includes(enc))
@@ -35,7 +35,7 @@ export function compressionMiddleware(config: CompressionConfig = {}): Middlewar
35
35
  // Store the encoding choice for post-processing
36
36
  ctx.locals.__compressionEncoding = encoding
37
37
  ctx.locals.__compressionThreshold = threshold
38
- ctx.headers.append("Vary", "Accept-Encoding")
38
+ ctx.headers.append('Vary', 'Accept-Encoding')
39
39
  }
40
40
  }
41
41
 
@@ -49,16 +49,16 @@ export function compressionMiddleware(config: CompressionConfig = {}): Middlewar
49
49
  */
50
50
  export async function compressResponse(
51
51
  response: Response,
52
- encoding: "gzip" | "deflate",
52
+ encoding: 'gzip' | 'deflate',
53
53
  threshold: number,
54
54
  ): Promise<Response> {
55
- const contentType = response.headers.get("content-type") ?? ""
55
+ const contentType = response.headers.get('content-type') ?? ''
56
56
 
57
57
  // Only compress text-based content
58
58
  if (!isCompressible(contentType)) return response
59
59
 
60
60
  // Skip if already encoded
61
- if (response.headers.get("content-encoding")) return response
61
+ if (response.headers.get('content-encoding')) return response
62
62
 
63
63
  const body = await response.arrayBuffer()
64
64
 
@@ -68,9 +68,9 @@ export async function compressResponse(
68
68
  const compressed = await compress(body, encoding)
69
69
 
70
70
  const headers = new Headers(response.headers)
71
- headers.set("Content-Encoding", encoding)
72
- headers.delete("Content-Length")
73
- headers.append("Vary", "Accept-Encoding")
71
+ headers.set('Content-Encoding', encoding)
72
+ headers.delete('Content-Length')
73
+ headers.append('Vary', 'Accept-Encoding')
74
74
 
75
75
  return new Response(compressed, {
76
76
  status: response.status,
@@ -80,12 +80,12 @@ export async function compressResponse(
80
80
  }
81
81
 
82
82
  const COMPRESSIBLE_TYPES = [
83
- "text/",
84
- "application/json",
85
- "application/javascript",
86
- "application/xml",
87
- "application/xhtml+xml",
88
- "image/svg+xml",
83
+ 'text/',
84
+ 'application/json',
85
+ 'application/javascript',
86
+ 'application/xml',
87
+ 'application/xhtml+xml',
88
+ 'image/svg+xml',
89
89
  ]
90
90
 
91
91
  /** Check if a content type is compressible. Exported for testing. */
@@ -93,8 +93,8 @@ export function isCompressible(contentType: string): boolean {
93
93
  return COMPRESSIBLE_TYPES.some((t) => contentType.includes(t))
94
94
  }
95
95
 
96
- async function compress(data: ArrayBuffer, encoding: "gzip" | "deflate"): Promise<ArrayBuffer> {
97
- const format = encoding === "gzip" ? "gzip" : "deflate"
96
+ async function compress(data: ArrayBuffer, encoding: 'gzip' | 'deflate'): Promise<ArrayBuffer> {
97
+ const format = encoding === 'gzip' ? 'gzip' : 'deflate'
98
98
  const stream = new Blob([data]).stream().pipeThrough(new CompressionStream(format))
99
99
  return new Response(stream).arrayBuffer()
100
100
  }
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ZeroConfig } from "./types"
1
+ import type { ZeroConfig } from './types'
2
2
 
3
3
  /**
4
4
  * Define a Zero configuration.
@@ -20,15 +20,15 @@ export function defineConfig(config: ZeroConfig): ZeroConfig {
20
20
  /** Merge user config with defaults. */
21
21
  export function resolveConfig(
22
22
  userConfig: ZeroConfig = {},
23
- ): Required<Pick<ZeroConfig, "mode" | "base" | "port" | "adapter">> & ZeroConfig {
23
+ ): Required<Pick<ZeroConfig, 'mode' | 'base' | 'port' | 'adapter'>> & ZeroConfig {
24
24
  return {
25
- mode: "ssr",
26
- base: "/",
25
+ mode: 'ssr',
26
+ base: '/',
27
27
  port: 3000,
28
- adapter: "node",
28
+ adapter: 'node',
29
29
  ...userConfig,
30
30
  ssr: {
31
- mode: "string",
31
+ mode: 'string',
32
32
  ...userConfig.ssr,
33
33
  },
34
34
  }
package/src/cors.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
  // ─── CORS middleware ────────────────────────────────────────────────────────
4
4
 
@@ -17,8 +17,8 @@ export interface CorsConfig {
17
17
  maxAge?: number
18
18
  }
19
19
 
20
- const DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
21
- const DEFAULT_HEADERS = ["Content-Type", "Authorization"]
20
+ const DEFAULT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
21
+ const DEFAULT_HEADERS = ['Content-Type', 'Authorization']
22
22
 
23
23
  /**
24
24
  * CORS middleware — handles preflight requests and sets appropriate
@@ -37,7 +37,7 @@ const DEFAULT_HEADERS = ["Content-Type", "Authorization"]
37
37
  */
38
38
  export function corsMiddleware(config: CorsConfig = {}): Middleware {
39
39
  const {
40
- origin = "*",
40
+ origin = '*',
41
41
  methods = DEFAULT_METHODS,
42
42
  allowedHeaders = DEFAULT_HEADERS,
43
43
  exposedHeaders = [],
@@ -46,45 +46,45 @@ export function corsMiddleware(config: CorsConfig = {}): Middleware {
46
46
  } = config
47
47
 
48
48
  return (ctx: MiddlewareContext) => {
49
- const requestOrigin = ctx.req.headers.get("origin") ?? ""
49
+ const requestOrigin = ctx.req.headers.get('origin') ?? ''
50
50
  const resolvedOrigin = resolveOrigin(origin, requestOrigin)
51
51
 
52
52
  if (!resolvedOrigin) return
53
53
 
54
54
  // Set CORS headers on all responses
55
- ctx.headers.set("Access-Control-Allow-Origin", resolvedOrigin)
55
+ ctx.headers.set('Access-Control-Allow-Origin', resolvedOrigin)
56
56
  if (credentials) {
57
- ctx.headers.set("Access-Control-Allow-Credentials", "true")
57
+ ctx.headers.set('Access-Control-Allow-Credentials', 'true')
58
58
  }
59
59
  if (exposedHeaders.length > 0) {
60
- ctx.headers.set("Access-Control-Expose-Headers", exposedHeaders.join(", "))
60
+ ctx.headers.set('Access-Control-Expose-Headers', exposedHeaders.join(', '))
61
61
  }
62
- if (resolvedOrigin !== "*") {
63
- ctx.headers.append("Vary", "Origin")
62
+ if (resolvedOrigin !== '*') {
63
+ ctx.headers.append('Vary', 'Origin')
64
64
  }
65
65
 
66
66
  // Handle preflight
67
- if (ctx.req.method === "OPTIONS") {
67
+ if (ctx.req.method === 'OPTIONS') {
68
68
  return new Response(null, {
69
69
  status: 204,
70
70
  headers: {
71
- "Access-Control-Allow-Origin": resolvedOrigin,
72
- "Access-Control-Allow-Methods": methods.join(", "),
73
- "Access-Control-Allow-Headers": allowedHeaders.join(", "),
74
- "Access-Control-Max-Age": String(maxAge),
75
- ...(credentials ? { "Access-Control-Allow-Credentials": "true" } : {}),
71
+ 'Access-Control-Allow-Origin': resolvedOrigin,
72
+ 'Access-Control-Allow-Methods': methods.join(', '),
73
+ 'Access-Control-Allow-Headers': allowedHeaders.join(', '),
74
+ 'Access-Control-Max-Age': String(maxAge),
75
+ ...(credentials ? { 'Access-Control-Allow-Credentials': 'true' } : {}),
76
76
  },
77
77
  })
78
78
  }
79
79
  }
80
80
  }
81
81
 
82
- function resolveOrigin(config: CorsConfig["origin"], requestOrigin: string): string | null {
83
- if (config === "*") return "*"
84
- if (typeof config === "string") {
82
+ function resolveOrigin(config: CorsConfig['origin'], requestOrigin: string): string | null {
83
+ if (config === '*') return '*'
84
+ if (typeof config === 'string') {
85
85
  return config === requestOrigin ? config : null
86
86
  }
87
- if (typeof config === "function") {
87
+ if (typeof config === 'function') {
88
88
  return config(requestOrigin) ? requestOrigin : null
89
89
  }
90
90
  if (Array.isArray(config)) {
@@ -1,10 +1,10 @@
1
- import type { RouteRecord } from "@pyreon/router"
2
- import type { Middleware, MiddlewareContext } from "@pyreon/server"
3
- import { createHandler } from "@pyreon/server"
4
- import type { ApiRouteEntry } from "./api-routes"
5
- import { createApiMiddleware } from "./api-routes"
6
- import { createApp } from "./app"
7
- import type { RouteMiddlewareEntry, ZeroConfig } from "./types"
1
+ import type { RouteRecord } from '@pyreon/router'
2
+ import type { Middleware, MiddlewareContext } from '@pyreon/server'
3
+ import { createHandler } from '@pyreon/server'
4
+ import type { ApiRouteEntry } from './api-routes'
5
+ import { createApiMiddleware } from './api-routes'
6
+ import { createApp } from './app'
7
+ import type { RouteMiddlewareEntry, ZeroConfig } from './types'
8
8
 
9
9
  // ─── Server entry factory ───────────────────────────────────────────────────
10
10
 
@@ -44,14 +44,14 @@ function createRouteMiddlewareDispatcher(entries: RouteMiddlewareEntry[]): Middl
44
44
 
45
45
  /** Simple URL pattern matcher supporting :param and :param* segments. */
46
46
  export function matchPattern(pattern: string, path: string): boolean {
47
- const patternParts = pattern.split("/").filter(Boolean)
48
- const pathParts = path.split("/").filter(Boolean)
47
+ const patternParts = pattern.split('/').filter(Boolean)
48
+ const pathParts = path.split('/').filter(Boolean)
49
49
 
50
50
  for (let i = 0; i < patternParts.length; i++) {
51
51
  const pp = patternParts[i]
52
52
  if (!pp) continue
53
- if (pp.endsWith("*")) return true // catch-all matches everything after
54
- if (pp.startsWith(":")) continue // dynamic segment matches anything
53
+ if (pp.endsWith('*')) return true // catch-all matches everything after
54
+ if (pp.startsWith(':')) continue // dynamic segment matches anything
55
55
  if (pp !== pathParts[i]) return false
56
56
  }
57
57
 
@@ -89,14 +89,14 @@ export function createServer(options: CreateServerOptions) {
89
89
 
90
90
  const { App } = createApp({
91
91
  routes: options.routes,
92
- routerMode: "history",
92
+ routerMode: 'history',
93
93
  })
94
94
 
95
95
  return createHandler({
96
96
  App,
97
97
  routes: options.routes,
98
98
  middleware: allMiddleware,
99
- mode: config.ssr?.mode ?? "string",
99
+ mode: config.ssr?.mode ?? 'string',
100
100
  ...(options.template ? { template: options.template } : {}),
101
101
  ...(options.clientEntry ? { clientEntry: options.clientEntry } : {}),
102
102
  })
@@ -3,8 +3,8 @@
3
3
  * Renders a styled HTML page with the error stack trace.
4
4
  */
5
5
  export function renderErrorOverlay(error: Error): string {
6
- const title = escapeHtml(error.message || "Unknown error")
7
- const stack = escapeHtml(error.stack || "")
6
+ const title = escapeHtml(error.message || 'Unknown error')
7
+ const stack = escapeHtml(error.stack || '')
8
8
 
9
9
  return `<!DOCTYPE html>
10
10
  <html lang="en">
@@ -96,17 +96,17 @@ export function renderErrorOverlay(error: Error): string {
96
96
 
97
97
  function escapeHtml(str: string): string {
98
98
  return str
99
- .replace(/&/g, "&amp;")
100
- .replace(/</g, "&lt;")
101
- .replace(/>/g, "&gt;")
102
- .replace(/"/g, "&quot;")
99
+ .replace(/&/g, '&amp;')
100
+ .replace(/</g, '&lt;')
101
+ .replace(/>/g, '&gt;')
102
+ .replace(/"/g, '&quot;')
103
103
  }
104
104
 
105
105
  function formatStack(stack: string): string {
106
106
  return stack
107
- .split("\n")
107
+ .split('\n')
108
108
  .map((line) => {
109
- if (line.includes("at ")) {
109
+ if (line.includes('at ')) {
110
110
  const fileMatch = line.match(/\(([^)]+)\)/)
111
111
  if (fileMatch) {
112
112
  return line.replace(fileMatch[0], `(<span class="file">${fileMatch[1]}</span>)`)
@@ -114,5 +114,5 @@ function formatStack(stack: string): string {
114
114
  }
115
115
  return line
116
116
  })
117
- .join("\n")
117
+ .join('\n')
118
118
  }