@pyreon/zero 0.12.1 → 0.12.3

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 (140) hide show
  1. package/lib/actions.js +97 -0
  2. package/lib/actions.js.map +1 -0
  3. package/lib/ai.js +503 -0
  4. package/lib/ai.js.map +1 -0
  5. package/lib/api-routes.js +137 -0
  6. package/lib/api-routes.js.map +1 -0
  7. package/lib/compression.js +80 -0
  8. package/lib/compression.js.map +1 -0
  9. package/lib/cors.js +57 -0
  10. package/lib/cors.js.map +1 -0
  11. package/lib/csp.js +119 -0
  12. package/lib/csp.js.map +1 -0
  13. package/lib/env.js +217 -0
  14. package/lib/env.js.map +1 -0
  15. package/lib/favicon.js +424 -0
  16. package/lib/favicon.js.map +1 -0
  17. package/lib/i18n-routing.js +167 -0
  18. package/lib/i18n-routing.js.map +1 -0
  19. package/lib/index.js +1631 -179
  20. package/lib/index.js.map +1 -1
  21. package/lib/link.js +5 -0
  22. package/lib/link.js.map +1 -1
  23. package/lib/logger.js +78 -0
  24. package/lib/logger.js.map +1 -0
  25. package/lib/meta.js +336 -0
  26. package/lib/meta.js.map +1 -0
  27. package/lib/middleware.js +53 -0
  28. package/lib/middleware.js.map +1 -0
  29. package/lib/og-image.js +233 -0
  30. package/lib/og-image.js.map +1 -0
  31. package/lib/rate-limit.js +76 -0
  32. package/lib/rate-limit.js.map +1 -0
  33. package/lib/testing.js +179 -0
  34. package/lib/testing.js.map +1 -0
  35. package/lib/theme.js +11 -2
  36. package/lib/theme.js.map +1 -1
  37. package/lib/types/actions.d.ts +27 -24
  38. package/lib/types/actions.d.ts.map +1 -1
  39. package/lib/types/ai.d.ts +163 -0
  40. package/lib/types/ai.d.ts.map +1 -0
  41. package/lib/types/api-routes.d.ts +37 -33
  42. package/lib/types/api-routes.d.ts.map +1 -1
  43. package/lib/types/cache.d.ts +26 -22
  44. package/lib/types/cache.d.ts.map +1 -1
  45. package/lib/types/client.d.ts +13 -9
  46. package/lib/types/client.d.ts.map +1 -1
  47. package/lib/types/compression.d.ts +14 -10
  48. package/lib/types/compression.d.ts.map +1 -1
  49. package/lib/types/config.d.ts +39 -4
  50. package/lib/types/config.d.ts.map +1 -1
  51. package/lib/types/cors.d.ts +20 -16
  52. package/lib/types/cors.d.ts.map +1 -1
  53. package/lib/types/csp.d.ts +88 -0
  54. package/lib/types/csp.d.ts.map +1 -0
  55. package/lib/types/env.d.ts +118 -0
  56. package/lib/types/env.d.ts.map +1 -0
  57. package/lib/types/favicon.d.ts +70 -24
  58. package/lib/types/favicon.d.ts.map +1 -1
  59. package/lib/types/font.d.ts +68 -65
  60. package/lib/types/font.d.ts.map +1 -1
  61. package/lib/types/i18n-routing.d.ts +43 -37
  62. package/lib/types/i18n-routing.d.ts.map +1 -1
  63. package/lib/types/image-plugin.d.ts +49 -45
  64. package/lib/types/image-plugin.d.ts.map +1 -1
  65. package/lib/types/image.d.ts +47 -36
  66. package/lib/types/image.d.ts.map +1 -1
  67. package/lib/types/index.d.ts +1961 -46
  68. package/lib/types/index.d.ts.map +1 -1
  69. package/lib/types/link.d.ts +61 -56
  70. package/lib/types/link.d.ts.map +1 -1
  71. package/lib/types/logger.d.ts +57 -0
  72. package/lib/types/logger.d.ts.map +1 -0
  73. package/lib/types/meta.d.ts +180 -69
  74. package/lib/types/meta.d.ts.map +1 -1
  75. package/lib/types/middleware.d.ts +8 -4
  76. package/lib/types/middleware.d.ts.map +1 -1
  77. package/lib/types/og-image.d.ts +111 -0
  78. package/lib/types/og-image.d.ts.map +1 -0
  79. package/lib/types/rate-limit.d.ts +20 -16
  80. package/lib/types/rate-limit.d.ts.map +1 -1
  81. package/lib/types/script.d.ts +23 -19
  82. package/lib/types/script.d.ts.map +1 -1
  83. package/lib/types/seo.d.ts +47 -43
  84. package/lib/types/seo.d.ts.map +1 -1
  85. package/lib/types/testing.d.ts +64 -27
  86. package/lib/types/testing.d.ts.map +1 -1
  87. package/lib/types/theme.d.ts +22 -12
  88. package/lib/types/theme.d.ts.map +1 -1
  89. package/package.json +37 -12
  90. package/src/actions.ts +1 -3
  91. package/src/adapters/bun.ts +2 -0
  92. package/src/adapters/cloudflare.ts +84 -0
  93. package/src/adapters/index.ts +13 -1
  94. package/src/adapters/netlify.ts +86 -0
  95. package/src/adapters/node.ts +2 -0
  96. package/src/adapters/validate.ts +16 -0
  97. package/src/adapters/vercel.ts +86 -0
  98. package/src/ai.ts +623 -0
  99. package/src/compression.ts +19 -3
  100. package/src/csp.ts +207 -0
  101. package/src/entry-server.ts +28 -5
  102. package/src/env.ts +344 -0
  103. package/src/favicon.ts +221 -80
  104. package/src/index.ts +42 -2
  105. package/src/link.tsx +6 -0
  106. package/src/logger.ts +144 -0
  107. package/src/meta.tsx +124 -14
  108. package/src/og-image.ts +378 -0
  109. package/src/rate-limit.ts +11 -9
  110. package/src/theme.tsx +12 -1
  111. package/src/types.ts +1 -1
  112. package/src/vite-plugin.ts +5 -1
  113. package/lib/types/adapters/bun.d.ts +0 -6
  114. package/lib/types/adapters/bun.d.ts.map +0 -1
  115. package/lib/types/adapters/index.d.ts +0 -10
  116. package/lib/types/adapters/index.d.ts.map +0 -1
  117. package/lib/types/adapters/node.d.ts +0 -6
  118. package/lib/types/adapters/node.d.ts.map +0 -1
  119. package/lib/types/adapters/static.d.ts +0 -7
  120. package/lib/types/adapters/static.d.ts.map +0 -1
  121. package/lib/types/app.d.ts +0 -24
  122. package/lib/types/app.d.ts.map +0 -1
  123. package/lib/types/entry-server.d.ts +0 -37
  124. package/lib/types/entry-server.d.ts.map +0 -1
  125. package/lib/types/error-overlay.d.ts +0 -6
  126. package/lib/types/error-overlay.d.ts.map +0 -1
  127. package/lib/types/fs-router.d.ts +0 -47
  128. package/lib/types/fs-router.d.ts.map +0 -1
  129. package/lib/types/isr.d.ts +0 -9
  130. package/lib/types/isr.d.ts.map +0 -1
  131. package/lib/types/not-found.d.ts +0 -7
  132. package/lib/types/not-found.d.ts.map +0 -1
  133. package/lib/types/types.d.ts +0 -111
  134. package/lib/types/types.d.ts.map +0 -1
  135. package/lib/types/utils/use-intersection-observer.d.ts +0 -10
  136. package/lib/types/utils/use-intersection-observer.d.ts.map +0 -1
  137. package/lib/types/utils/with-headers.d.ts +0 -6
  138. package/lib/types/utils/with-headers.d.ts.map +0 -1
  139. package/lib/types/vite-plugin.d.ts +0 -17
  140. package/lib/types/vite-plugin.d.ts.map +0 -1
package/src/actions.ts CHANGED
@@ -34,7 +34,6 @@ export interface Action<T = unknown> {
34
34
  // ─── Registry ────────────────────────────────────────────────────────────────
35
35
 
36
36
  const actionRegistry = new Map<string, RegisteredAction>()
37
- let actionCounter = 0
38
37
 
39
38
  /**
40
39
  * Define a server action. Returns a callable function that:
@@ -53,7 +52,7 @@ let actionCounter = 0
53
52
  * const result = await createPost({ title: 'Hello', body: '...' })
54
53
  */
55
54
  export function defineAction<T = unknown>(handler: ActionHandler<T>): Action<T> {
56
- const id = `action_${actionCounter++}`
55
+ const id = `action_${crypto.randomUUID().slice(0, 8)}`
57
56
 
58
57
  actionRegistry.set(id, { id, handler: handler as ActionHandler })
59
58
 
@@ -100,7 +99,6 @@ export function getRegisteredActions(): Map<string, RegisteredAction> {
100
99
  */
101
100
  export function _resetActions(): void {
102
101
  actionRegistry.clear()
103
- actionCounter = 0
104
102
  }
105
103
 
106
104
  // ─── Server handler ──────────────────────────────────────────────────────────
@@ -1,4 +1,5 @@
1
1
  import type { Adapter, AdapterBuildOptions } from '../types'
2
+ import { validateBuildInputs } from './validate'
2
3
 
3
4
  /**
4
5
  * Bun adapter — generates a standalone Bun.serve() entry.
@@ -7,6 +8,7 @@ export function bunAdapter(): Adapter {
7
8
  return {
8
9
  name: 'bun',
9
10
  async build(options: AdapterBuildOptions) {
11
+ await validateBuildInputs(options)
10
12
  const { writeFile, cp, mkdir } = await import('node:fs/promises')
11
13
  const { join } = await import('node:path')
12
14
 
@@ -0,0 +1,84 @@
1
+ import type { Adapter, AdapterBuildOptions } from '../types'
2
+ import { validateBuildInputs } from './validate'
3
+
4
+ /**
5
+ * Cloudflare Pages adapter — generates output for Cloudflare Pages with Functions.
6
+ *
7
+ * Produces:
8
+ * - Client assets in the output directory root (served as static)
9
+ * - `_worker.js` — Cloudflare Pages Function for SSR
10
+ *
11
+ * Note: Cloudflare Pages Functions have a ~1MB module size limit.
12
+ * For large apps, configure Vite's SSR build to bundle server code:
13
+ * `ssr: { noExternal: true }` in vite.config.ts.
14
+ *
15
+ * Deploy with: `npx wrangler pages deploy ./dist`
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // zero.config.ts
20
+ * import { defineConfig } from "@pyreon/zero"
21
+ *
22
+ * export default defineConfig({
23
+ * adapter: "cloudflare",
24
+ * })
25
+ * ```
26
+ */
27
+ export function cloudflareAdapter(): Adapter {
28
+ return {
29
+ name: 'cloudflare',
30
+ async build(options: AdapterBuildOptions) {
31
+ await validateBuildInputs(options)
32
+ const { writeFile, cp, mkdir } = await import('node:fs/promises')
33
+ const { join } = await import('node:path')
34
+
35
+ const outDir = options.outDir
36
+ await mkdir(outDir, { recursive: true })
37
+
38
+ // Copy client assets to root (Cloudflare serves static files from root)
39
+ await cp(options.clientOutDir, outDir, { recursive: true })
40
+
41
+ // Copy server build
42
+ await cp(join(options.serverEntry, '..'), join(outDir, '_server'), {
43
+ recursive: true,
44
+ })
45
+
46
+ // Generate Cloudflare Pages _worker.js (ES module format)
47
+ const workerEntry = `
48
+ import handler from "./_server/entry-server.js"
49
+
50
+ export default {
51
+ async fetch(request, env, ctx) {
52
+ const url = new URL(request.url)
53
+
54
+ // Let Cloudflare serve static assets (files with extensions)
55
+ // This check is a fallback — Pages routes static files automatically
56
+ const ext = url.pathname.split(".").pop()
57
+ if (ext && ext !== url.pathname && !url.pathname.endsWith("/")) {
58
+ // Cloudflare Pages handles static assets automatically via its asset binding
59
+ // Only reach here if the file doesn't exist — fall through to SSR
60
+ }
61
+
62
+ // SSR handler
63
+ try {
64
+ return await handler(request)
65
+ } catch (err) {
66
+ return new Response("Internal Server Error", { status: 500 })
67
+ }
68
+ },
69
+ }
70
+ `.trimStart()
71
+
72
+ await writeFile(join(outDir, '_worker.js'), workerEntry)
73
+
74
+ // Cloudflare Pages config — _routes.json for routing
75
+ const routesConfig = {
76
+ version: 1,
77
+ include: ['/*'],
78
+ exclude: ['/assets/*', '/favicon.*', '/site.webmanifest', '/robots.txt', '/sitemap.xml'],
79
+ }
80
+
81
+ await writeFile(join(outDir, '_routes.json'), JSON.stringify(routesConfig, null, 2))
82
+ },
83
+ }
84
+ }
@@ -1,11 +1,17 @@
1
1
  export { bunAdapter } from './bun'
2
+ export { cloudflareAdapter } from './cloudflare'
3
+ export { netlifyAdapter } from './netlify'
2
4
  export { nodeAdapter } from './node'
3
5
  export { staticAdapter } from './static'
6
+ export { vercelAdapter } from './vercel'
4
7
 
5
8
  import type { Adapter, ZeroConfig } from '../types'
6
9
  import { bunAdapter } from './bun'
10
+ import { cloudflareAdapter } from './cloudflare'
11
+ import { netlifyAdapter } from './netlify'
7
12
  import { nodeAdapter } from './node'
8
13
  import { staticAdapter } from './static'
14
+ import { vercelAdapter } from './vercel'
9
15
 
10
16
  /**
11
17
  * Resolve the adapter from config.
@@ -21,7 +27,13 @@ export function resolveAdapter(config: ZeroConfig): Adapter {
21
27
  return bunAdapter()
22
28
  case 'static':
23
29
  return staticAdapter()
30
+ case 'vercel':
31
+ return vercelAdapter()
32
+ case 'cloudflare':
33
+ return cloudflareAdapter()
34
+ case 'netlify':
35
+ return netlifyAdapter()
24
36
  default:
25
- throw new Error(`[zero] Unknown adapter: "${name}". Use "node", "bun", or "static".`)
37
+ throw new Error(`[zero] Unknown adapter: "${name}". Use "node", "bun", "static", "vercel", "cloudflare", or "netlify".`)
26
38
  }
27
39
  }
@@ -0,0 +1,86 @@
1
+ import type { Adapter, AdapterBuildOptions } from '../types'
2
+ import { validateBuildInputs } from './validate'
3
+
4
+ /**
5
+ * Netlify adapter — generates output for Netlify Functions (v2).
6
+ *
7
+ * Produces:
8
+ * - Client assets in `publish/` directory
9
+ * - `netlify/functions/ssr.mjs` — Netlify Function for SSR
10
+ * - `netlify.toml` — routing configuration
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * // zero.config.ts
15
+ * import { defineConfig } from "@pyreon/zero"
16
+ *
17
+ * export default defineConfig({
18
+ * adapter: "netlify",
19
+ * })
20
+ * ```
21
+ */
22
+ export function netlifyAdapter(): Adapter {
23
+ return {
24
+ name: 'netlify',
25
+ async build(options: AdapterBuildOptions) {
26
+ await validateBuildInputs(options)
27
+ const { writeFile, cp, mkdir } = await import('node:fs/promises')
28
+ const { join } = await import('node:path')
29
+
30
+ const outDir = options.outDir
31
+ const publishDir = join(outDir, 'publish')
32
+ const functionsDir = join(outDir, 'netlify', 'functions')
33
+
34
+ await mkdir(publishDir, { recursive: true })
35
+ await mkdir(functionsDir, { recursive: true })
36
+
37
+ // Copy client assets to publish/
38
+ await cp(options.clientOutDir, publishDir, { recursive: true })
39
+
40
+ // Copy server build to functions directory
41
+ await cp(join(options.serverEntry, '..'), join(functionsDir, '_server'), {
42
+ recursive: true,
43
+ })
44
+
45
+ // Generate Netlify Function (v2 format — ESM, Web-standard Request/Response)
46
+ const funcEntry = `
47
+ import handler from "./_server/entry-server.js"
48
+
49
+ export default async function(req, context) {
50
+ try {
51
+ return await handler(req)
52
+ } catch (err) {
53
+ return new Response("Internal Server Error", { status: 500 })
54
+ }
55
+ }
56
+
57
+ export const config = {
58
+ path: "/*",
59
+ preferStatic: true,
60
+ }
61
+ `.trimStart()
62
+
63
+ await writeFile(join(functionsDir, 'ssr.mjs'), funcEntry)
64
+
65
+ // Generate netlify.toml
66
+ const toml = `
67
+ [build]
68
+ publish = "publish"
69
+ functions = "netlify/functions"
70
+
71
+ [[headers]]
72
+ for = "/assets/*"
73
+ [headers.values]
74
+ Cache-Control = "public, max-age=31536000, immutable"
75
+
76
+ [[redirects]]
77
+ from = "/*"
78
+ to = "/.netlify/functions/ssr"
79
+ status = 200
80
+ conditions = {Role = ["admin", "user", ""]}
81
+ `.trimStart()
82
+
83
+ await writeFile(join(outDir, 'netlify.toml'), toml)
84
+ },
85
+ }
86
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Adapter, AdapterBuildOptions } from '../types'
2
+ import { validateBuildInputs } from './validate'
2
3
 
3
4
  /**
4
5
  * Node.js adapter — generates a standalone server entry using node:http.
@@ -7,6 +8,7 @@ export function nodeAdapter(): Adapter {
7
8
  return {
8
9
  name: 'node',
9
10
  async build(options: AdapterBuildOptions) {
11
+ await validateBuildInputs(options)
10
12
  const { writeFile, cp, mkdir } = await import('node:fs/promises')
11
13
  const { join } = await import('node:path')
12
14
 
@@ -0,0 +1,16 @@
1
+ import type { AdapterBuildOptions } from '../types'
2
+
3
+ /**
4
+ * Validate that adapter build inputs exist before copying.
5
+ * Throws with a clear error message if directories are missing.
6
+ * @internal
7
+ */
8
+ export async function validateBuildInputs(options: AdapterBuildOptions): Promise<void> {
9
+ const { existsSync } = await import('node:fs')
10
+ if (!existsSync(options.clientOutDir)) {
11
+ throw new Error(`[zero:adapter] Client build output not found: ${options.clientOutDir}. Run "vite build" first.`)
12
+ }
13
+ if (!existsSync(options.serverEntry)) {
14
+ throw new Error(`[zero:adapter] Server entry not found: ${options.serverEntry}. Run "vite build --ssr" first.`)
15
+ }
16
+ }
@@ -0,0 +1,86 @@
1
+ import type { Adapter, AdapterBuildOptions } from '../types'
2
+ import { validateBuildInputs } from './validate'
3
+
4
+ /**
5
+ * Vercel adapter — generates output for Vercel's Build Output API v3.
6
+ *
7
+ * Produces a `.vercel/output` directory with:
8
+ * - `static/` — client-side assets (JS, CSS, images)
9
+ * - `functions/ssr.func/` — serverless function for SSR
10
+ * - `config.json` — routing configuration
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * // zero.config.ts
15
+ * import { defineConfig } from "@pyreon/zero"
16
+ *
17
+ * export default defineConfig({
18
+ * adapter: "vercel",
19
+ * })
20
+ * ```
21
+ */
22
+ export function vercelAdapter(): Adapter {
23
+ return {
24
+ name: 'vercel',
25
+ async build(options: AdapterBuildOptions) {
26
+ await validateBuildInputs(options)
27
+ const { writeFile, cp, mkdir } = await import('node:fs/promises')
28
+ const { join } = await import('node:path')
29
+
30
+ const vercelDir = join(options.outDir, '.vercel', 'output')
31
+ const staticDir = join(vercelDir, 'static')
32
+ const funcDir = join(vercelDir, 'functions', 'ssr.func')
33
+
34
+ await mkdir(staticDir, { recursive: true })
35
+ await mkdir(funcDir, { recursive: true })
36
+
37
+ // Copy client assets to static/
38
+ await cp(options.clientOutDir, staticDir, { recursive: true })
39
+
40
+ // Copy server build to function directory
41
+ await cp(join(options.serverEntry, '..'), funcDir, { recursive: true })
42
+
43
+ // Generate serverless function entry
44
+ const funcEntry = `
45
+ export default async function handler(req) {
46
+ const handler = (await import("./entry-server.js")).default
47
+ return handler(req)
48
+ }
49
+ `.trimStart()
50
+
51
+ await writeFile(join(funcDir, 'index.js'), funcEntry)
52
+
53
+ // Function config
54
+ await writeFile(
55
+ join(funcDir, '.vc-config.json'),
56
+ JSON.stringify(
57
+ {
58
+ runtime: 'nodejs20.x',
59
+ handler: 'index.js',
60
+ launcherType: 'Nodejs',
61
+ },
62
+ null,
63
+ 2,
64
+ ),
65
+ )
66
+
67
+ // Vercel Build Output config
68
+ const config = {
69
+ version: 3,
70
+ routes: [
71
+ // Serve static assets directly
72
+ {
73
+ src: '/assets/(.*)',
74
+ headers: { 'Cache-Control': 'public, max-age=31536000, immutable' },
75
+ },
76
+ // Favicon and manifest
77
+ { src: '/(favicon\\..*|site\\.webmanifest|robots\\.txt|sitemap\\.xml)', dest: '/$1' },
78
+ // All other routes → SSR function
79
+ { src: '/(.*)', dest: '/ssr' },
80
+ ],
81
+ }
82
+
83
+ await writeFile(join(vercelDir, 'config.json'), JSON.stringify(config, null, 2))
84
+ },
85
+ }
86
+ }