@pyreon/zero 0.4.1 → 0.11.0

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.
Files changed (110) hide show
  1. package/lib/cache.js.map +1 -1
  2. package/lib/client.js.map +1 -1
  3. package/lib/config.js.map +1 -1
  4. package/lib/font.js.map +1 -1
  5. package/lib/fs-router-BkbIWqek.js.map +1 -1
  6. package/lib/fs-router-n4VA4lxu.js.map +1 -1
  7. package/lib/image-plugin.js.map +1 -1
  8. package/lib/image.js +5 -5
  9. package/lib/image.js.map +1 -1
  10. package/lib/index.js +14 -14
  11. package/lib/index.js.map +1 -1
  12. package/lib/link.js +9 -9
  13. package/lib/link.js.map +1 -1
  14. package/lib/script.js +1 -1
  15. package/lib/script.js.map +1 -1
  16. package/lib/seo.js.map +1 -1
  17. package/lib/theme.js +2 -2
  18. package/lib/theme.js.map +1 -1
  19. package/package.json +14 -13
  20. package/src/actions.ts +20 -28
  21. package/src/adapters/bun.ts +7 -7
  22. package/src/adapters/index.ts +12 -14
  23. package/src/adapters/node.ts +8 -11
  24. package/src/adapters/static.ts +3 -3
  25. package/src/api-routes.ts +23 -50
  26. package/src/app.ts +9 -13
  27. package/src/cache.ts +16 -29
  28. package/src/client.ts +8 -8
  29. package/src/compression.ts +21 -28
  30. package/src/config.ts +6 -7
  31. package/src/cors.ts +20 -28
  32. package/src/entry-server.ts +15 -19
  33. package/src/error-overlay.ts +10 -13
  34. package/src/font.ts +44 -55
  35. package/src/fs-router.ts +44 -63
  36. package/src/image-plugin.ts +53 -79
  37. package/src/image.tsx +41 -43
  38. package/src/index.ts +36 -36
  39. package/src/isr.ts +8 -8
  40. package/src/link.tsx +35 -38
  41. package/src/rate-limit.ts +15 -15
  42. package/src/script.tsx +21 -22
  43. package/src/seo.ts +47 -57
  44. package/src/sharp.d.ts +2 -6
  45. package/src/testing.ts +8 -12
  46. package/src/theme.tsx +19 -21
  47. package/src/types.ts +6 -6
  48. package/src/utils/use-intersection-observer.ts +2 -2
  49. package/src/utils/with-headers.ts +1 -4
  50. package/src/vite-plugin.ts +21 -28
  51. package/lib/types/actions.d.ts +0 -57
  52. package/lib/types/actions.d.ts.map +0 -1
  53. package/lib/types/adapters/bun.d.ts +0 -6
  54. package/lib/types/adapters/bun.d.ts.map +0 -1
  55. package/lib/types/adapters/index.d.ts +0 -10
  56. package/lib/types/adapters/index.d.ts.map +0 -1
  57. package/lib/types/adapters/node.d.ts +0 -6
  58. package/lib/types/adapters/node.d.ts.map +0 -1
  59. package/lib/types/adapters/static.d.ts +0 -7
  60. package/lib/types/adapters/static.d.ts.map +0 -1
  61. package/lib/types/api-routes.d.ts +0 -66
  62. package/lib/types/api-routes.d.ts.map +0 -1
  63. package/lib/types/app.d.ts +0 -24
  64. package/lib/types/app.d.ts.map +0 -1
  65. package/lib/types/cache.d.ts +0 -54
  66. package/lib/types/cache.d.ts.map +0 -1
  67. package/lib/types/client.d.ts +0 -19
  68. package/lib/types/client.d.ts.map +0 -1
  69. package/lib/types/compression.d.ts +0 -33
  70. package/lib/types/compression.d.ts.map +0 -1
  71. package/lib/types/config.d.ts +0 -18
  72. package/lib/types/config.d.ts.map +0 -1
  73. package/lib/types/cors.d.ts +0 -32
  74. package/lib/types/cors.d.ts.map +0 -1
  75. package/lib/types/entry-server.d.ts +0 -34
  76. package/lib/types/entry-server.d.ts.map +0 -1
  77. package/lib/types/error-overlay.d.ts +0 -6
  78. package/lib/types/error-overlay.d.ts.map +0 -1
  79. package/lib/types/font.d.ts +0 -119
  80. package/lib/types/font.d.ts.map +0 -1
  81. package/lib/types/fs-router.d.ts +0 -38
  82. package/lib/types/fs-router.d.ts.map +0 -1
  83. package/lib/types/image-plugin.d.ts +0 -79
  84. package/lib/types/image-plugin.d.ts.map +0 -1
  85. package/lib/types/image.d.ts +0 -51
  86. package/lib/types/image.d.ts.map +0 -1
  87. package/lib/types/index.d.ts +0 -37
  88. package/lib/types/index.d.ts.map +0 -1
  89. package/lib/types/isr.d.ts +0 -9
  90. package/lib/types/isr.d.ts.map +0 -1
  91. package/lib/types/link.d.ts +0 -116
  92. package/lib/types/link.d.ts.map +0 -1
  93. package/lib/types/rate-limit.d.ts +0 -34
  94. package/lib/types/rate-limit.d.ts.map +0 -1
  95. package/lib/types/script.d.ts +0 -35
  96. package/lib/types/script.d.ts.map +0 -1
  97. package/lib/types/seo.d.ts +0 -88
  98. package/lib/types/seo.d.ts.map +0 -1
  99. package/lib/types/testing.d.ts +0 -85
  100. package/lib/types/testing.d.ts.map +0 -1
  101. package/lib/types/theme.d.ts +0 -39
  102. package/lib/types/theme.d.ts.map +0 -1
  103. package/lib/types/types.d.ts +0 -109
  104. package/lib/types/types.d.ts.map +0 -1
  105. package/lib/types/utils/use-intersection-observer.d.ts +0 -10
  106. package/lib/types/utils/use-intersection-observer.d.ts.map +0 -1
  107. package/lib/types/utils/with-headers.d.ts +0 -6
  108. package/lib/types/utils/with-headers.d.ts.map +0 -1
  109. package/lib/types/vite-plugin.d.ts +0 -17
  110. package/lib/types/vite-plugin.d.ts.map +0 -1
@@ -1,4 +1,4 @@
1
- import type { Adapter, AdapterBuildOptions } from '../types'
1
+ import type { Adapter, AdapterBuildOptions } from "../types"
2
2
 
3
3
  /**
4
4
  * Static adapter — just copies the client build output.
@@ -6,9 +6,9 @@ import type { Adapter, AdapterBuildOptions } from '../types'
6
6
  */
7
7
  export function staticAdapter(): Adapter {
8
8
  return {
9
- name: 'static',
9
+ name: "static",
10
10
  async build(options: AdapterBuildOptions) {
11
- const { cp, mkdir } = await import('node:fs/promises')
11
+ const { cp, mkdir } = await import("node:fs/promises")
12
12
 
13
13
  await mkdir(options.outDir, { recursive: true })
14
14
  await cp(options.clientOutDir, options.outDir, { recursive: true })
package/src/api-routes.ts CHANGED
@@ -1,16 +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 =
7
- | 'GET'
8
- | 'POST'
9
- | 'PUT'
10
- | 'PATCH'
11
- | 'DELETE'
12
- | 'HEAD'
13
- | 'OPTIONS'
6
+ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"
14
7
 
15
8
  /** Context passed to API route handlers. */
16
9
  export interface ApiContext {
@@ -54,12 +47,9 @@ export interface ApiRouteEntry {
54
47
  * Match a URL path against an API route pattern.
55
48
  * Returns extracted params or null if no match.
56
49
  */
57
- export function matchApiRoute(
58
- pattern: string,
59
- path: string,
60
- ): Record<string, string> | null {
61
- const patternParts = pattern.split('/').filter(Boolean)
62
- const pathParts = path.split('/').filter(Boolean)
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)
63
53
  const params: Record<string, string> = {}
64
54
 
65
55
  for (let i = 0; i < patternParts.length; i++) {
@@ -67,9 +57,9 @@ export function matchApiRoute(
67
57
  if (!pp) continue
68
58
 
69
59
  // Catch-all: :param*
70
- if (pp.endsWith('*')) {
60
+ if (pp.endsWith("*")) {
71
61
  const paramName = pp.slice(1, -1)
72
- params[paramName] = pathParts.slice(i).join('/')
62
+ params[paramName] = pathParts.slice(i).join("/")
73
63
  return params
74
64
  }
75
65
 
@@ -77,7 +67,7 @@ export function matchApiRoute(
77
67
  if (i >= pathParts.length) return null
78
68
 
79
69
  // Dynamic segment: :param
80
- if (pp.startsWith(':')) {
70
+ if (pp.startsWith(":")) {
81
71
  params[pp.slice(1)] = pathParts[i]!
82
72
  continue
83
73
  }
@@ -91,15 +81,7 @@ export function matchApiRoute(
91
81
 
92
82
  // ─── Middleware ───────────────────────────────────────────────────────────────
93
83
 
94
- const HTTP_METHODS: HttpMethod[] = [
95
- 'GET',
96
- 'POST',
97
- 'PUT',
98
- 'PATCH',
99
- 'DELETE',
100
- 'HEAD',
101
- 'OPTIONS',
102
- ]
84
+ const HTTP_METHODS: HttpMethod[] = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
103
85
 
104
86
  /**
105
87
  * Create a middleware that dispatches API route requests.
@@ -116,12 +98,12 @@ export function createApiMiddleware(routes: ApiRouteEntry[]): Middleware {
116
98
 
117
99
  if (!handler) {
118
100
  // Route matched but method not supported
119
- const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(', ')
101
+ const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(", ")
120
102
  return new Response(null, {
121
103
  status: 405,
122
104
  headers: {
123
105
  Allow: allowed,
124
- 'Content-Type': 'application/json',
106
+ "Content-Type": "application/json",
125
107
  },
126
108
  })
127
109
  }
@@ -144,12 +126,12 @@ export function createApiMiddleware(routes: ApiRouteEntry[]): Middleware {
144
126
  * API routes are `.ts` or `.js` files inside an `api/` directory.
145
127
  */
146
128
  export function isApiRoute(filePath: string): boolean {
147
- const normalized = filePath.replace(/\\/g, '/')
129
+ const normalized = filePath.replace(/\\/g, "/")
148
130
  return (
149
- normalized.startsWith('api/') &&
150
- (normalized.endsWith('.ts') || normalized.endsWith('.js')) &&
151
- !normalized.endsWith('.tsx') &&
152
- !normalized.endsWith('.jsx')
131
+ normalized.startsWith("api/") &&
132
+ (normalized.endsWith(".ts") || normalized.endsWith(".js")) &&
133
+ !normalized.endsWith(".tsx") &&
134
+ !normalized.endsWith(".jsx")
153
135
  )
154
136
  }
155
137
 
@@ -165,18 +147,18 @@ export function isApiRoute(filePath: string): boolean {
165
147
  export function apiFilePathToPattern(filePath: string): string {
166
148
  let route = filePath
167
149
  // Remove extension
168
- for (const ext of ['.ts', '.js']) {
150
+ for (const ext of [".ts", ".js"]) {
169
151
  if (route.endsWith(ext)) {
170
152
  route = route.slice(0, -ext.length)
171
153
  break
172
154
  }
173
155
  }
174
156
 
175
- const segments = route.split('/')
157
+ const segments = route.split("/")
176
158
  const urlSegments: string[] = []
177
159
 
178
160
  for (const seg of segments) {
179
- if (seg === 'index') continue
161
+ if (seg === "index") continue
180
162
 
181
163
  // Catch-all: [...param]
182
164
  const catchAll = seg.match(/^\[\.\.\.(\w+)\]$/)
@@ -195,21 +177,18 @@ export function apiFilePathToPattern(filePath: string): string {
195
177
  urlSegments.push(seg)
196
178
  }
197
179
 
198
- return `/${urlSegments.join('/')}`
180
+ return `/${urlSegments.join("/")}`
199
181
  }
200
182
 
201
183
  /**
202
184
  * Generate a virtual module that exports API route entries.
203
185
  * Each entry maps a URL pattern to a module with HTTP method handlers.
204
186
  */
205
- export function generateApiRouteModule(
206
- files: string[],
207
- routesDir: string,
208
- ): string {
187
+ export function generateApiRouteModule(files: string[], routesDir: string): string {
209
188
  const apiFiles = files.filter(isApiRoute)
210
189
 
211
190
  if (apiFiles.length === 0) {
212
- return 'export const apiRoutes = []\n'
191
+ return "export const apiRoutes = []\n"
213
192
  }
214
193
 
215
194
  const imports: string[] = []
@@ -226,11 +205,5 @@ export function generateApiRouteModule(
226
205
  entries.push(` { pattern: ${JSON.stringify(pattern)}, module: ${name} }`)
227
206
  }
228
207
 
229
- return [
230
- ...imports,
231
- '',
232
- 'export const apiRoutes = [',
233
- entries.join(',\n'),
234
- ']',
235
- ].join('\n')
208
+ return [...imports, "", "export const apiRoutes = [", entries.join(",\n"), "]"].join("\n")
236
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
@@ -54,9 +54,5 @@ export function createApp(options: CreateAppOptions) {
54
54
  }
55
55
 
56
56
  function DefaultLayout(props: Props) {
57
- return h(
58
- Fragment,
59
- null,
60
- ...(Array.isArray(props.children) ? props.children : [props.children]),
61
- )
57
+ return h(Fragment, null, ...(Array.isArray(props.children) ? props.children : [props.children]))
62
58
  }
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
  //
@@ -32,15 +32,14 @@ export interface CacheRule {
32
32
  }
33
33
 
34
34
  const HASHED_ASSET = /\.[a-f0-9]{8,}\.\w+$/
35
- const STATIC_EXT =
36
- /\.(png|jpe?g|gif|svg|webp|avif|ico|woff2?|ttf|otf|eot|mp4|webm|ogg|mp3|wav)$/i
35
+ const STATIC_EXT = /\.(png|jpe?g|gif|svg|webp|avif|ico|woff2?|ttf|otf|eot|mp4|webm|ogg|mp3|wav)$/i
37
36
  const SCRIPT_EXT = /\.(js|css|mjs)$/i
38
37
 
39
38
  /** @internal Exported for testing */
40
39
  export function matchGlob(pattern: string, path: string): boolean {
41
40
  // Escape regex special chars, then convert glob wildcards
42
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&')
43
- const regex = escaped.replace(/\*/g, '.*').replace(/\?/g, '.')
41
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&")
42
+ const regex = escaped.replace(/\*/g, ".*").replace(/\?/g, ".")
44
43
  return new RegExp(`^${regex}$`).test(path)
45
44
  }
46
45
 
@@ -63,7 +62,7 @@ function resolveControl(
63
62
  if (pageDuration > 0) {
64
63
  return `public, max-age=${pageDuration}, stale-while-revalidate=${swr}`
65
64
  }
66
- return 'no-cache'
65
+ return "no-cache"
67
66
  }
68
67
 
69
68
  /**
@@ -98,19 +97,13 @@ export function cacheMiddleware(config: CacheConfig = {}): Middleware {
98
97
 
99
98
  for (const rule of rules) {
100
99
  if (matchGlob(rule.match, path)) {
101
- ctx.headers.set('Cache-Control', rule.control)
100
+ ctx.headers.set("Cache-Control", rule.control)
102
101
  return
103
102
  }
104
103
  }
105
104
 
106
- const control = resolveControl(
107
- path,
108
- immutableDuration,
109
- staticDuration,
110
- pageDuration,
111
- swr,
112
- )
113
- ctx.headers.set('Cache-Control', control)
105
+ const control = resolveControl(path, immutableDuration, staticDuration, pageDuration, swr)
106
+ ctx.headers.set("Cache-Control", control)
114
107
  }
115
108
  }
116
109
 
@@ -120,14 +113,11 @@ export function cacheMiddleware(config: CacheConfig = {}): Middleware {
120
113
  */
121
114
  export function securityHeaders(): Middleware {
122
115
  return (ctx: MiddlewareContext) => {
123
- ctx.headers.set('X-Content-Type-Options', 'nosniff')
124
- ctx.headers.set('X-Frame-Options', 'DENY')
125
- ctx.headers.set('X-XSS-Protection', '1; mode=block')
126
- ctx.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
127
- ctx.headers.set(
128
- 'Permissions-Policy',
129
- 'camera=(), microphone=(), geolocation=()',
130
- )
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=()")
131
121
  }
132
122
  }
133
123
 
@@ -138,12 +128,9 @@ export function securityHeaders(): Middleware {
138
128
  */
139
129
  export function varyEncoding(): Middleware {
140
130
  return (ctx: MiddlewareContext) => {
141
- const existing = ctx.headers.get('Vary')
142
- if (!existing?.includes('Accept-Encoding')) {
143
- ctx.headers.set(
144
- 'Vary',
145
- existing ? `${existing}, Accept-Encoding` : 'Accept-Encoding',
146
- )
131
+ const existing = ctx.headers.get("Vary")
132
+ if (!existing?.includes("Accept-Encoding")) {
133
+ ctx.headers.set("Vary", existing ? `${existing}, Accept-Encoding` : "Accept-Encoding")
147
134
  }
148
135
  }
149
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
  /**
@@ -22,13 +22,11 @@ export interface CompressionConfig {
22
22
  * compressionMiddleware() // gzip with 1KB threshold
23
23
  * compressionMiddleware({ threshold: 512, encodings: ["gzip"] })
24
24
  */
25
- export function compressionMiddleware(
26
- config: CompressionConfig = {},
27
- ): Middleware {
28
- const { threshold = 1024, encodings = ['gzip', 'deflate'] } = config
25
+ export function compressionMiddleware(config: CompressionConfig = {}): Middleware {
26
+ const { threshold = 1024, encodings = ["gzip", "deflate"] } = config
29
27
 
30
28
  return (ctx: MiddlewareContext) => {
31
- const acceptEncoding = ctx.req.headers.get('accept-encoding') ?? ''
29
+ const acceptEncoding = ctx.req.headers.get("accept-encoding") ?? ""
32
30
 
33
31
  // Find the best supported encoding
34
32
  const encoding = encodings.find((enc) => acceptEncoding.includes(enc))
@@ -37,7 +35,7 @@ export function compressionMiddleware(
37
35
  // Store the encoding choice for post-processing
38
36
  ctx.locals.__compressionEncoding = encoding
39
37
  ctx.locals.__compressionThreshold = threshold
40
- ctx.headers.append('Vary', 'Accept-Encoding')
38
+ ctx.headers.append("Vary", "Accept-Encoding")
41
39
  }
42
40
  }
43
41
 
@@ -51,16 +49,16 @@ export function compressionMiddleware(
51
49
  */
52
50
  export async function compressResponse(
53
51
  response: Response,
54
- encoding: 'gzip' | 'deflate',
52
+ encoding: "gzip" | "deflate",
55
53
  threshold: number,
56
54
  ): Promise<Response> {
57
- const contentType = response.headers.get('content-type') ?? ''
55
+ const contentType = response.headers.get("content-type") ?? ""
58
56
 
59
57
  // Only compress text-based content
60
58
  if (!isCompressible(contentType)) return response
61
59
 
62
60
  // Skip if already encoded
63
- if (response.headers.get('content-encoding')) return response
61
+ if (response.headers.get("content-encoding")) return response
64
62
 
65
63
  const body = await response.arrayBuffer()
66
64
 
@@ -70,9 +68,9 @@ export async function compressResponse(
70
68
  const compressed = await compress(body, encoding)
71
69
 
72
70
  const headers = new Headers(response.headers)
73
- headers.set('Content-Encoding', encoding)
74
- headers.delete('Content-Length')
75
- headers.append('Vary', 'Accept-Encoding')
71
+ headers.set("Content-Encoding", encoding)
72
+ headers.delete("Content-Length")
73
+ headers.append("Vary", "Accept-Encoding")
76
74
 
77
75
  return new Response(compressed, {
78
76
  status: response.status,
@@ -82,12 +80,12 @@ export async function compressResponse(
82
80
  }
83
81
 
84
82
  const COMPRESSIBLE_TYPES = [
85
- 'text/',
86
- 'application/json',
87
- 'application/javascript',
88
- 'application/xml',
89
- 'application/xhtml+xml',
90
- 'image/svg+xml',
83
+ "text/",
84
+ "application/json",
85
+ "application/javascript",
86
+ "application/xml",
87
+ "application/xhtml+xml",
88
+ "image/svg+xml",
91
89
  ]
92
90
 
93
91
  /** Check if a content type is compressible. Exported for testing. */
@@ -95,13 +93,8 @@ export function isCompressible(contentType: string): boolean {
95
93
  return COMPRESSIBLE_TYPES.some((t) => contentType.includes(t))
96
94
  }
97
95
 
98
- async function compress(
99
- data: ArrayBuffer,
100
- encoding: 'gzip' | 'deflate',
101
- ): Promise<ArrayBuffer> {
102
- const format = encoding === 'gzip' ? 'gzip' : 'deflate'
103
- const stream = new Blob([data])
104
- .stream()
105
- .pipeThrough(new CompressionStream(format))
96
+ async function compress(data: ArrayBuffer, encoding: "gzip" | "deflate"): Promise<ArrayBuffer> {
97
+ const format = encoding === "gzip" ? "gzip" : "deflate"
98
+ const stream = new Blob([data]).stream().pipeThrough(new CompressionStream(format))
106
99
  return new Response(stream).arrayBuffer()
107
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,16 +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'>> &
24
- ZeroConfig {
23
+ ): Required<Pick<ZeroConfig, "mode" | "base" | "port" | "adapter">> & ZeroConfig {
25
24
  return {
26
- mode: 'ssr',
27
- base: '/',
25
+ mode: "ssr",
26
+ base: "/",
28
27
  port: 3000,
29
- adapter: 'node',
28
+ adapter: "node",
30
29
  ...userConfig,
31
30
  ssr: {
32
- mode: 'string',
31
+ mode: "string",
33
32
  ...userConfig.ssr,
34
33
  },
35
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,53 +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(
61
- 'Access-Control-Expose-Headers',
62
- exposedHeaders.join(', '),
63
- )
60
+ ctx.headers.set("Access-Control-Expose-Headers", exposedHeaders.join(", "))
64
61
  }
65
- if (resolvedOrigin !== '*') {
66
- ctx.headers.append('Vary', 'Origin')
62
+ if (resolvedOrigin !== "*") {
63
+ ctx.headers.append("Vary", "Origin")
67
64
  }
68
65
 
69
66
  // Handle preflight
70
- if (ctx.req.method === 'OPTIONS') {
67
+ if (ctx.req.method === "OPTIONS") {
71
68
  return new Response(null, {
72
69
  status: 204,
73
70
  headers: {
74
- 'Access-Control-Allow-Origin': resolvedOrigin,
75
- 'Access-Control-Allow-Methods': methods.join(', '),
76
- 'Access-Control-Allow-Headers': allowedHeaders.join(', '),
77
- 'Access-Control-Max-Age': String(maxAge),
78
- ...(credentials
79
- ? { 'Access-Control-Allow-Credentials': 'true' }
80
- : {}),
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" } : {}),
81
76
  },
82
77
  })
83
78
  }
84
79
  }
85
80
  }
86
81
 
87
- function resolveOrigin(
88
- config: CorsConfig['origin'],
89
- requestOrigin: string,
90
- ): string | null {
91
- if (config === '*') return '*'
92
- if (typeof config === 'string') {
82
+ function resolveOrigin(config: CorsConfig["origin"], requestOrigin: string): string | null {
83
+ if (config === "*") return "*"
84
+ if (typeof config === "string") {
93
85
  return config === requestOrigin ? config : null
94
86
  }
95
- if (typeof config === 'function') {
87
+ if (typeof config === "function") {
96
88
  return config(requestOrigin) ? requestOrigin : null
97
89
  }
98
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
 
@@ -28,15 +28,11 @@ export interface CreateServerOptions {
28
28
  /**
29
29
  * Create a middleware that dispatches per-route middleware based on URL pattern matching.
30
30
  */
31
- function createRouteMiddlewareDispatcher(
32
- entries: RouteMiddlewareEntry[],
33
- ): Middleware {
31
+ function createRouteMiddlewareDispatcher(entries: RouteMiddlewareEntry[]): Middleware {
34
32
  return async (ctx: MiddlewareContext) => {
35
33
  for (const entry of entries) {
36
34
  if (matchPattern(entry.pattern, ctx.path)) {
37
- const mw = Array.isArray(entry.middleware)
38
- ? entry.middleware
39
- : [entry.middleware]
35
+ const mw = Array.isArray(entry.middleware) ? entry.middleware : [entry.middleware]
40
36
  for (const fn of mw) {
41
37
  const result = await fn(ctx)
42
38
  if (result) return result
@@ -48,14 +44,14 @@ function createRouteMiddlewareDispatcher(
48
44
 
49
45
  /** Simple URL pattern matcher supporting :param and :param* segments. */
50
46
  export function matchPattern(pattern: string, path: string): boolean {
51
- const patternParts = pattern.split('/').filter(Boolean)
52
- const pathParts = path.split('/').filter(Boolean)
47
+ const patternParts = pattern.split("/").filter(Boolean)
48
+ const pathParts = path.split("/").filter(Boolean)
53
49
 
54
50
  for (let i = 0; i < patternParts.length; i++) {
55
51
  const pp = patternParts[i]
56
52
  if (!pp) continue
57
- if (pp.endsWith('*')) return true // catch-all matches everything after
58
- 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
59
55
  if (pp !== pathParts[i]) return false
60
56
  }
61
57
 
@@ -93,14 +89,14 @@ export function createServer(options: CreateServerOptions) {
93
89
 
94
90
  const { App } = createApp({
95
91
  routes: options.routes,
96
- routerMode: 'history',
92
+ routerMode: "history",
97
93
  })
98
94
 
99
95
  return createHandler({
100
96
  App,
101
97
  routes: options.routes,
102
98
  middleware: allMiddleware,
103
- mode: config.ssr?.mode ?? 'string',
99
+ mode: config.ssr?.mode ?? "string",
104
100
  ...(options.template ? { template: options.template } : {}),
105
101
  ...(options.clientEntry ? { clientEntry: options.clientEntry } : {}),
106
102
  })