@pyreon/zero 0.12.2 → 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 (138) 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 +80 -22
  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 +76 -95
  40. package/lib/types/ai.d.ts.map +1 -1
  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 +42 -61
  54. package/lib/types/csp.d.ts.map +1 -1
  55. package/lib/types/env.d.ts +26 -26
  56. package/lib/types/env.d.ts.map +1 -1
  57. package/lib/types/favicon.d.ts +58 -54
  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 -56
  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 +37 -48
  72. package/lib/types/logger.d.ts.map +1 -1
  73. package/lib/types/meta.d.ts +180 -105
  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 +63 -59
  78. package/lib/types/og-image.d.ts.map +1 -1
  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 +12 -12
  90. package/src/actions.ts +1 -3
  91. package/src/adapters/bun.ts +2 -0
  92. package/src/adapters/cloudflare.ts +2 -0
  93. package/src/adapters/netlify.ts +2 -0
  94. package/src/adapters/node.ts +2 -0
  95. package/src/adapters/validate.ts +16 -0
  96. package/src/adapters/vercel.ts +2 -0
  97. package/src/compression.ts +19 -3
  98. package/src/entry-server.ts +28 -5
  99. package/src/index.ts +1 -0
  100. package/src/link.tsx +6 -0
  101. package/src/meta.tsx +41 -13
  102. package/src/rate-limit.ts +11 -9
  103. package/src/theme.tsx +12 -1
  104. package/src/vite-plugin.ts +5 -1
  105. package/lib/types/adapters/bun.d.ts +0 -6
  106. package/lib/types/adapters/bun.d.ts.map +0 -1
  107. package/lib/types/adapters/cloudflare.d.ts +0 -26
  108. package/lib/types/adapters/cloudflare.d.ts.map +0 -1
  109. package/lib/types/adapters/index.d.ts +0 -13
  110. package/lib/types/adapters/index.d.ts.map +0 -1
  111. package/lib/types/adapters/netlify.d.ts +0 -21
  112. package/lib/types/adapters/netlify.d.ts.map +0 -1
  113. package/lib/types/adapters/node.d.ts +0 -6
  114. package/lib/types/adapters/node.d.ts.map +0 -1
  115. package/lib/types/adapters/static.d.ts +0 -7
  116. package/lib/types/adapters/static.d.ts.map +0 -1
  117. package/lib/types/adapters/vercel.d.ts +0 -21
  118. package/lib/types/adapters/vercel.d.ts.map +0 -1
  119. package/lib/types/app.d.ts +0 -24
  120. package/lib/types/app.d.ts.map +0 -1
  121. package/lib/types/entry-server.d.ts +0 -37
  122. package/lib/types/entry-server.d.ts.map +0 -1
  123. package/lib/types/error-overlay.d.ts +0 -6
  124. package/lib/types/error-overlay.d.ts.map +0 -1
  125. package/lib/types/fs-router.d.ts +0 -47
  126. package/lib/types/fs-router.d.ts.map +0 -1
  127. package/lib/types/isr.d.ts +0 -9
  128. package/lib/types/isr.d.ts.map +0 -1
  129. package/lib/types/not-found.d.ts +0 -7
  130. package/lib/types/not-found.d.ts.map +0 -1
  131. package/lib/types/types.d.ts +0 -111
  132. package/lib/types/types.d.ts.map +0 -1
  133. package/lib/types/utils/use-intersection-observer.d.ts +0 -10
  134. package/lib/types/utils/use-intersection-observer.d.ts.map +0 -1
  135. package/lib/types/utils/with-headers.d.ts +0 -6
  136. package/lib/types/utils/with-headers.d.ts.map +0 -1
  137. package/lib/types/vite-plugin.d.ts +0 -17
  138. package/lib/types/vite-plugin.d.ts.map +0 -1
package/lib/testing.js ADDED
@@ -0,0 +1,179 @@
1
+ //#region src/api-routes.ts
2
+ /**
3
+ * Match a URL path against an API route pattern.
4
+ * Returns extracted params or null if no match.
5
+ */
6
+ function matchApiRoute(pattern, path) {
7
+ const patternParts = pattern.split("/").filter(Boolean);
8
+ const pathParts = path.split("/").filter(Boolean);
9
+ const params = {};
10
+ for (let i = 0; i < patternParts.length; i++) {
11
+ const pp = patternParts[i];
12
+ if (!pp) continue;
13
+ if (pp.endsWith("*")) {
14
+ const paramName = pp.slice(1, -1);
15
+ params[paramName] = pathParts.slice(i).join("/");
16
+ return params;
17
+ }
18
+ if (i >= pathParts.length) return null;
19
+ if (pp.startsWith(":")) {
20
+ params[pp.slice(1)] = pathParts[i];
21
+ continue;
22
+ }
23
+ if (pp !== pathParts[i]) return null;
24
+ }
25
+ return patternParts.length === pathParts.length ? params : null;
26
+ }
27
+ const HTTP_METHODS = [
28
+ "GET",
29
+ "POST",
30
+ "PUT",
31
+ "PATCH",
32
+ "DELETE",
33
+ "HEAD",
34
+ "OPTIONS"
35
+ ];
36
+ /**
37
+ * Create a middleware that dispatches API route requests.
38
+ * API routes are matched by URL pattern and HTTP method.
39
+ */
40
+ function createApiMiddleware(routes) {
41
+ return async (ctx) => {
42
+ for (const route of routes) {
43
+ const params = matchApiRoute(route.pattern, ctx.path);
44
+ if (!params) continue;
45
+ const method = ctx.req.method.toUpperCase();
46
+ const handler = route.module[method];
47
+ if (!handler) {
48
+ const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(", ");
49
+ return new Response(null, {
50
+ status: 405,
51
+ headers: {
52
+ Allow: allowed,
53
+ "Content-Type": "application/json"
54
+ }
55
+ });
56
+ }
57
+ return handler({
58
+ request: ctx.req,
59
+ url: ctx.url,
60
+ path: ctx.path,
61
+ params,
62
+ headers: ctx.req.headers
63
+ });
64
+ }
65
+ };
66
+ }
67
+
68
+ //#endregion
69
+ //#region src/testing.ts
70
+ /**
71
+ * Create a mock MiddlewareContext for testing middleware.
72
+ *
73
+ * @example
74
+ * import { createTestContext } from "@pyreon/zero/testing"
75
+ *
76
+ * const ctx = createTestContext("/api/posts", { method: "POST", body: { title: "Hello" } })
77
+ * const result = await myMiddleware(ctx)
78
+ */
79
+ function createTestContext(path, options = {}) {
80
+ const { method = "GET", headers = {}, body } = options;
81
+ const url = new URL(`http://localhost${path}`);
82
+ const requestHeaders = { ...headers };
83
+ let requestBody;
84
+ if (body !== void 0) {
85
+ requestHeaders["Content-Type"] = "application/json";
86
+ requestBody = JSON.stringify(body);
87
+ }
88
+ return {
89
+ req: new Request(url.toString(), {
90
+ method,
91
+ headers: requestHeaders,
92
+ ...requestBody != null ? { body: requestBody } : {}
93
+ }),
94
+ url,
95
+ path,
96
+ headers: new Headers(),
97
+ locals: {}
98
+ };
99
+ }
100
+ /**
101
+ * Test a middleware by running it with a mock context and returning
102
+ * the result along with the response headers it set.
103
+ *
104
+ * @example
105
+ * import { testMiddleware } from "@pyreon/zero/testing"
106
+ *
107
+ * const { response, headers } = await testMiddleware(
108
+ * corsMiddleware({ origin: "*" }),
109
+ * "/api/posts"
110
+ * )
111
+ * expect(headers.get("Access-Control-Allow-Origin")).toBe("*")
112
+ */
113
+ async function testMiddleware(middleware, path, options = {}) {
114
+ const ctx = createTestContext(path, options);
115
+ return {
116
+ response: await middleware(ctx),
117
+ headers: ctx.headers
118
+ };
119
+ }
120
+ /**
121
+ * Create a test server for API routes. Returns a function that
122
+ * accepts Request objects and dispatches to the correct handler.
123
+ *
124
+ * @example
125
+ * import { createTestApiServer } from "@pyreon/zero/testing"
126
+ *
127
+ * const server = createTestApiServer([
128
+ * { pattern: "/api/posts", module: postsApi },
129
+ * { pattern: "/api/posts/:id", module: postByIdApi },
130
+ * ])
131
+ *
132
+ * const response = await server.request("/api/posts")
133
+ * expect(response.status).toBe(200)
134
+ *
135
+ * const data = await server.request("/api/posts", { method: "POST", body: { title: "Hi" } })
136
+ * expect(data.status).toBe(201)
137
+ */
138
+ function createTestApiServer(routes) {
139
+ const middleware = createApiMiddleware(routes);
140
+ return { async request(path, options = {}) {
141
+ const result = await middleware(createTestContext(path, options));
142
+ if (!result) return new Response("Not Found", { status: 404 });
143
+ return result;
144
+ } };
145
+ }
146
+ /**
147
+ * Create a mock API handler for testing.
148
+ * Records all calls and returns a configurable response.
149
+ *
150
+ * @example
151
+ * import { createMockHandler } from "@pyreon/zero/testing"
152
+ *
153
+ * const handler = createMockHandler({ status: 200, body: { ok: true } })
154
+ * // ... use handler in your API route module
155
+ * expect(handler.calls).toHaveLength(1)
156
+ * expect(handler.calls[0].params).toEqual({ id: "123" })
157
+ */
158
+ function createMockHandler(responseConfig = {}) {
159
+ const { status = 200, body = null, headers = {} } = responseConfig;
160
+ const calls = [];
161
+ const handler = (ctx) => {
162
+ calls.push({
163
+ path: ctx.path,
164
+ params: ctx.params
165
+ });
166
+ return new Response(JSON.stringify(body), {
167
+ status,
168
+ headers: {
169
+ "Content-Type": "application/json",
170
+ ...headers
171
+ }
172
+ });
173
+ };
174
+ return Object.assign(handler, { calls });
175
+ }
176
+
177
+ //#endregion
178
+ export { createMockHandler, createTestApiServer, createTestContext, testMiddleware };
179
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/api-routes.ts","../src/testing.ts"],"sourcesContent":["import type { Middleware, MiddlewareContext } from '@pyreon/server'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** HTTP methods supported by API routes. */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'\n\n/** Context passed to API route handlers. */\nexport interface ApiContext {\n /** The incoming request. */\n request: Request\n /** Parsed URL. */\n url: URL\n /** URL path. */\n path: string\n /** Dynamic route parameters (e.g., { id: \"123\" }). */\n params: Record<string, string>\n /** Request headers. */\n headers: Headers\n}\n\n/** An API route handler function. */\nexport type ApiHandler = (ctx: ApiContext) => Response | Promise<Response>\n\n/** An API route module — exports named HTTP method handlers. */\nexport interface ApiRouteModule {\n GET?: ApiHandler\n POST?: ApiHandler\n PUT?: ApiHandler\n PATCH?: ApiHandler\n DELETE?: ApiHandler\n HEAD?: ApiHandler\n OPTIONS?: ApiHandler\n}\n\n/** A registered API route entry. */\nexport interface ApiRouteEntry {\n /** URL pattern (e.g., \"/api/posts/:id\"). */\n pattern: string\n /** The route module with method handlers. */\n module: ApiRouteModule\n}\n\n// ─── Pattern matching ────────────────────────────────────────────────────────\n\n/**\n * Match a URL path against an API route pattern.\n * Returns extracted params or null if no match.\n */\nexport function matchApiRoute(pattern: string, path: string): Record<string, string> | null {\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = path.split('/').filter(Boolean)\n const params: Record<string, string> = {}\n\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i]\n if (!pp) continue\n\n // Catch-all: :param*\n if (pp.endsWith('*')) {\n const paramName = pp.slice(1, -1)\n params[paramName] = pathParts.slice(i).join('/')\n return params\n }\n\n // No more path segments\n if (i >= pathParts.length) return null\n\n // Dynamic segment: :param\n if (pp.startsWith(':')) {\n params[pp.slice(1)] = pathParts[i]!\n continue\n }\n\n // Static segment\n if (pp !== pathParts[i]) return null\n }\n\n return patternParts.length === pathParts.length ? params : null\n}\n\n// ─── Middleware ───────────────────────────────────────────────────────────────\n\nconst HTTP_METHODS: HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']\n\n/**\n * Create a middleware that dispatches API route requests.\n * API routes are matched by URL pattern and HTTP method.\n */\nexport function createApiMiddleware(routes: ApiRouteEntry[]): Middleware {\n return async (ctx: MiddlewareContext) => {\n for (const route of routes) {\n const params = matchApiRoute(route.pattern, ctx.path)\n if (!params) continue\n\n const method = ctx.req.method.toUpperCase() as HttpMethod\n const handler = route.module[method]\n\n if (!handler) {\n // Route matched but method not supported\n const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(', ')\n return new Response(null, {\n status: 405,\n headers: {\n Allow: allowed,\n 'Content-Type': 'application/json',\n },\n })\n }\n\n return handler({\n request: ctx.req,\n url: ctx.url,\n path: ctx.path,\n params,\n headers: ctx.req.headers,\n })\n }\n }\n}\n\n// ─── Virtual module generation ───────────────────────────────────────────────\n\n/**\n * Detect whether a route file is an API route.\n * API routes are `.ts` or `.js` files inside an `api/` directory.\n */\nexport function isApiRoute(filePath: string): boolean {\n const normalized = filePath.replace(/\\\\/g, '/')\n return (\n normalized.startsWith('api/') &&\n (normalized.endsWith('.ts') || normalized.endsWith('.js')) &&\n !normalized.endsWith('.tsx') &&\n !normalized.endsWith('.jsx')\n )\n}\n\n/**\n * Convert an API route file path to a URL pattern.\n *\n * Examples:\n * \"api/posts.ts\" → \"/api/posts\"\n * \"api/posts/index.ts\" → \"/api/posts\"\n * \"api/posts/[id].ts\" → \"/api/posts/:id\"\n * \"api/[...path].ts\" → \"/api/:path*\"\n */\nexport function apiFilePathToPattern(filePath: string): string {\n let route = filePath\n // Remove extension\n for (const ext of ['.ts', '.js']) {\n if (route.endsWith(ext)) {\n route = route.slice(0, -ext.length)\n break\n }\n }\n\n const segments = route.split('/')\n const urlSegments: string[] = []\n\n for (const seg of segments) {\n if (seg === 'index') continue\n\n // Catch-all: [...param]\n const catchAll = seg.match(/^\\[\\.\\.\\.(\\w+)\\]$/)\n if (catchAll) {\n urlSegments.push(`:${catchAll[1]}*`)\n continue\n }\n\n // Dynamic: [param]\n const dynamic = seg.match(/^\\[(\\w+)\\]$/)\n if (dynamic) {\n urlSegments.push(`:${dynamic[1]}`)\n continue\n }\n\n urlSegments.push(seg)\n }\n\n return `/${urlSegments.join('/')}`\n}\n\n/**\n * Generate a virtual module that exports API route entries.\n * Each entry maps a URL pattern to a module with HTTP method handlers.\n */\nexport function generateApiRouteModule(files: string[], routesDir: string): string {\n const apiFiles = files.filter(isApiRoute)\n\n if (apiFiles.length === 0) {\n return 'export const apiRoutes = []\\n'\n }\n\n const imports: string[] = []\n const entries: string[] = []\n\n for (let i = 0; i < apiFiles.length; i++) {\n const name = `_api${i}`\n const file = apiFiles[i]\n if (!file) continue\n const fullPath = `${routesDir}/${file}`\n const pattern = apiFilePathToPattern(file)\n\n imports.push(`import * as ${name} from \"${fullPath}\"`)\n entries.push(` { pattern: ${JSON.stringify(pattern)}, module: ${name} }`)\n }\n\n return [...imports, '', 'export const apiRoutes = [', entries.join(',\\n'), ']'].join('\\n')\n}\n","import type { Middleware, MiddlewareContext } from '@pyreon/server'\nimport type { ApiHandler, ApiRouteEntry } from './api-routes'\nimport { createApiMiddleware } from './api-routes'\n\n// ─── Test helpers for Zero applications ─────────────────────────────────────\n\n/**\n * Create a mock MiddlewareContext for testing middleware.\n *\n * @example\n * import { createTestContext } from \"@pyreon/zero/testing\"\n *\n * const ctx = createTestContext(\"/api/posts\", { method: \"POST\", body: { title: \"Hello\" } })\n * const result = await myMiddleware(ctx)\n */\nexport function createTestContext(\n path: string,\n options: {\n method?: string\n headers?: Record<string, string>\n body?: unknown\n } = {},\n): MiddlewareContext {\n const { method = 'GET', headers = {}, body } = options\n const url = new URL(`http://localhost${path}`)\n\n const requestHeaders: Record<string, string> = { ...headers }\n let requestBody: string | undefined\n\n if (body !== undefined) {\n requestHeaders['Content-Type'] = 'application/json'\n requestBody = JSON.stringify(body)\n }\n\n const req = new Request(url.toString(), {\n method,\n headers: requestHeaders,\n ...(requestBody != null ? { body: requestBody } : {}),\n })\n\n return {\n req,\n url,\n path,\n headers: new Headers(),\n locals: {},\n }\n}\n\n/**\n * Test a middleware by running it with a mock context and returning\n * the result along with the response headers it set.\n *\n * @example\n * import { testMiddleware } from \"@pyreon/zero/testing\"\n *\n * const { response, headers } = await testMiddleware(\n * corsMiddleware({ origin: \"*\" }),\n * \"/api/posts\"\n * )\n * expect(headers.get(\"Access-Control-Allow-Origin\")).toBe(\"*\")\n */\nexport async function testMiddleware(\n middleware: Middleware,\n path: string,\n options: {\n method?: string\n headers?: Record<string, string>\n body?: unknown\n } = {},\n): Promise<{ response: Response | undefined; headers: Headers }> {\n const ctx = createTestContext(path, options)\n const response = (await middleware(ctx)) as Response | undefined\n return { response, headers: ctx.headers }\n}\n\n/**\n * Create a test server for API routes. Returns a function that\n * accepts Request objects and dispatches to the correct handler.\n *\n * @example\n * import { createTestApiServer } from \"@pyreon/zero/testing\"\n *\n * const server = createTestApiServer([\n * { pattern: \"/api/posts\", module: postsApi },\n * { pattern: \"/api/posts/:id\", module: postByIdApi },\n * ])\n *\n * const response = await server.request(\"/api/posts\")\n * expect(response.status).toBe(200)\n *\n * const data = await server.request(\"/api/posts\", { method: \"POST\", body: { title: \"Hi\" } })\n * expect(data.status).toBe(201)\n */\nexport function createTestApiServer(routes: ApiRouteEntry[]) {\n const middleware = createApiMiddleware(routes)\n\n return {\n async request(\n path: string,\n options: {\n method?: string\n headers?: Record<string, string>\n body?: unknown\n } = {},\n ): Promise<Response> {\n const ctx = createTestContext(path, options)\n const result = await middleware(ctx)\n if (!result) {\n return new Response('Not Found', { status: 404 })\n }\n return result\n },\n }\n}\n\n/**\n * Create a mock API handler for testing.\n * Records all calls and returns a configurable response.\n *\n * @example\n * import { createMockHandler } from \"@pyreon/zero/testing\"\n *\n * const handler = createMockHandler({ status: 200, body: { ok: true } })\n * // ... use handler in your API route module\n * expect(handler.calls).toHaveLength(1)\n * expect(handler.calls[0].params).toEqual({ id: \"123\" })\n */\nexport function createMockHandler(\n responseConfig: { status?: number; body?: unknown; headers?: Record<string, string> } = {},\n): ApiHandler & {\n calls: Array<{ path: string; params: Record<string, string> }>\n} {\n const { status = 200, body = null, headers = {} } = responseConfig\n const calls: Array<{ path: string; params: Record<string, string> }> = []\n\n const handler: ApiHandler = (ctx) => {\n calls.push({ path: ctx.path, params: ctx.params })\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json', ...headers },\n })\n }\n\n return Object.assign(handler, { calls })\n}\n"],"mappings":"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"}
package/lib/theme.js CHANGED
@@ -55,11 +55,20 @@ const jsxs = jsx;
55
55
  const STORAGE_KEY = "zero-theme";
56
56
  /** Reactive theme signal. */
57
57
  const theme = signal("system");
58
+ /** SSR fallback when system preference can't be detected. Default: 'light'. */
59
+ let _ssrDefault = "light";
60
+ /**
61
+ * Set the default theme for SSR (when `matchMedia` is unavailable).
62
+ * Call once at server startup before rendering.
63
+ */
64
+ function setSSRThemeDefault(value) {
65
+ _ssrDefault = value;
66
+ }
58
67
  /** Computed resolved theme (what's actually applied). */
59
68
  function resolvedTheme() {
60
69
  const t = theme();
61
70
  if (t === "system") {
62
- if (typeof window === "undefined") return "dark";
71
+ if (typeof window === "undefined") return _ssrDefault;
63
72
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
64
73
  }
65
74
  return t;
@@ -210,5 +219,5 @@ function ThemeToggle(props) {
210
219
  const themeScript = `(function(){try{var t=localStorage.getItem("${STORAGE_KEY}");var r=t==="light"?"light":t==="dark"?"dark":window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light";document.documentElement.dataset.theme=r}catch(e){}})()`;
211
220
 
212
221
  //#endregion
213
- export { ThemeToggle, initTheme, resolvedTheme, setTheme, theme, themeScript, toggleTheme };
222
+ export { ThemeToggle, initTheme, resolvedTheme, setSSRThemeDefault, setTheme, theme, themeScript, toggleTheme };
214
223
  //# sourceMappingURL=theme.js.map
package/lib/theme.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"theme.js","names":[],"sources":["../../../core/core/lib/jsx-runtime.js","../src/theme.tsx"],"sourcesContent":["//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNodeChild } from '@pyreon/core'\nimport { onMount, onUnmount } from '@pyreon/core'\nimport { effect, signal } from '@pyreon/reactivity'\n\n// ─── Theme system ───────────────────────────────────────────────────────────\n//\n// Provides dark/light/system theme support with:\n// - System preference detection via matchMedia\n// - Persistent preference via localStorage\n// - No flash of wrong theme (inline script in HTML)\n// - Reactive theme signal for components\n\nexport type Theme = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'zero-theme'\n\n/** Reactive theme signal. */\nexport const theme = signal<Theme>('system')\n\n/** Computed resolved theme (what's actually applied). */\nexport function resolvedTheme(): 'light' | 'dark' {\n const t = theme()\n if (t === 'system') {\n if (typeof window === 'undefined') return 'dark'\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n }\n return t\n}\n\n/** Toggle between light and dark. */\nexport function toggleTheme() {\n const current = resolvedTheme()\n setTheme(current === 'dark' ? 'light' : 'dark')\n}\n\n/** Set theme explicitly. */\nexport function setTheme(t: Theme) {\n theme.set(t)\n if (typeof document !== 'undefined') {\n document.documentElement.dataset.theme = resolvedTheme()\n try {\n localStorage.setItem(STORAGE_KEY, t)\n } catch {\n // localStorage may not be available (SSR, private browsing)\n }\n }\n}\n\n/**\n * Initialize the theme system. Call once in your app entry or layout.\n * Reads from localStorage, listens for system preference changes.\n */\nexport function initTheme() {\n onMount(() => {\n // Read persisted preference\n try {\n const stored = localStorage.getItem(STORAGE_KEY) as Theme | null\n if (stored === 'light' || stored === 'dark' || stored === 'system') {\n theme.set(stored)\n }\n } catch {\n // localStorage may not be available\n }\n\n // Apply to document\n document.documentElement.dataset.theme = resolvedTheme()\n\n // Watch for system preference changes\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n function onChange() {\n if (theme() === 'system') {\n document.documentElement.dataset.theme = resolvedTheme()\n }\n }\n mq.addEventListener('change', onChange)\n onUnmount(() => mq.removeEventListener('change', onChange))\n\n // Re-apply when theme signal changes\n const dispose = effect(() => {\n document.documentElement.dataset.theme = resolvedTheme()\n })\n if (dispose) onUnmount(() => dispose.dispose())\n\n return undefined\n })\n}\n\n/**\n * Theme toggle button component.\n *\n * @example\n * import { ThemeToggle } from \"@pyreon/zero/theme\"\n * <ThemeToggle />\n */\nexport function ThemeToggle(props: { class?: string; style?: string }): VNodeChild {\n initTheme()\n\n return (\n <button\n class={props.class}\n style={props.style}\n onClick={toggleTheme}\n aria-label=\"Toggle theme\"\n title=\"Toggle theme\"\n type=\"button\"\n >\n {() =>\n resolvedTheme() === 'dark' ? (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"5\" />\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\" />\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\" />\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n </svg>\n ) : (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n )\n }\n </button>\n )\n}\n\n/**\n * Inline script to prevent flash of wrong theme.\n * Include this in your index.html <head> BEFORE any stylesheets.\n *\n * @example\n * // index.html\n * <head>\n * <script>{themeScript}</script>\n * ...\n * </head>\n */\nexport const themeScript = `(function(){try{var t=localStorage.getItem(\"${STORAGE_KEY}\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r}catch(e){}})()`\n"],"mappings":"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"}
1
+ {"version":3,"file":"theme.js","names":[],"sources":["../../../core/core/lib/jsx-runtime.js","../src/theme.tsx"],"sourcesContent":["//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNodeChild } from '@pyreon/core'\nimport { onMount, onUnmount } from '@pyreon/core'\nimport { effect, signal } from '@pyreon/reactivity'\n\n// ─── Theme system ───────────────────────────────────────────────────────────\n//\n// Provides dark/light/system theme support with:\n// - System preference detection via matchMedia\n// - Persistent preference via localStorage\n// - No flash of wrong theme (inline script in HTML)\n// - Reactive theme signal for components\n\nexport type Theme = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'zero-theme'\n\n/** Reactive theme signal. */\nexport const theme = signal<Theme>('system')\n\n/** SSR fallback when system preference can't be detected. Default: 'light'. */\nlet _ssrDefault: 'light' | 'dark' = 'light'\n\n/**\n * Set the default theme for SSR (when `matchMedia` is unavailable).\n * Call once at server startup before rendering.\n */\nexport function setSSRThemeDefault(value: 'light' | 'dark'): void {\n _ssrDefault = value\n}\n\n/** Computed resolved theme (what's actually applied). */\nexport function resolvedTheme(): 'light' | 'dark' {\n const t = theme()\n if (t === 'system') {\n if (typeof window === 'undefined') return _ssrDefault\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n }\n return t\n}\n\n/** Toggle between light and dark. */\nexport function toggleTheme() {\n const current = resolvedTheme()\n setTheme(current === 'dark' ? 'light' : 'dark')\n}\n\n/** Set theme explicitly. */\nexport function setTheme(t: Theme) {\n theme.set(t)\n if (typeof document !== 'undefined') {\n document.documentElement.dataset.theme = resolvedTheme()\n try {\n localStorage.setItem(STORAGE_KEY, t)\n } catch {\n // localStorage may not be available (SSR, private browsing)\n }\n }\n}\n\n/**\n * Initialize the theme system. Call once in your app entry or layout.\n * Reads from localStorage, listens for system preference changes.\n */\nexport function initTheme() {\n onMount(() => {\n // Read persisted preference\n try {\n const stored = localStorage.getItem(STORAGE_KEY) as Theme | null\n if (stored === 'light' || stored === 'dark' || stored === 'system') {\n theme.set(stored)\n }\n } catch {\n // localStorage may not be available\n }\n\n // Apply to document\n document.documentElement.dataset.theme = resolvedTheme()\n\n // Watch for system preference changes\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n function onChange() {\n if (theme() === 'system') {\n document.documentElement.dataset.theme = resolvedTheme()\n }\n }\n mq.addEventListener('change', onChange)\n onUnmount(() => mq.removeEventListener('change', onChange))\n\n // Re-apply when theme signal changes\n const dispose = effect(() => {\n document.documentElement.dataset.theme = resolvedTheme()\n })\n if (dispose) onUnmount(() => dispose.dispose())\n\n return undefined\n })\n}\n\n/**\n * Theme toggle button component.\n *\n * @example\n * import { ThemeToggle } from \"@pyreon/zero/theme\"\n * <ThemeToggle />\n */\nexport function ThemeToggle(props: { class?: string; style?: string }): VNodeChild {\n initTheme()\n\n return (\n <button\n class={props.class}\n style={props.style}\n onClick={toggleTheme}\n aria-label=\"Toggle theme\"\n title=\"Toggle theme\"\n type=\"button\"\n >\n {() =>\n resolvedTheme() === 'dark' ? (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"5\" />\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\" />\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\" />\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n </svg>\n ) : (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n )\n }\n </button>\n )\n}\n\n/**\n * Inline script to prevent flash of wrong theme.\n * Include this in your index.html <head> BEFORE any stylesheets.\n *\n * @example\n * // index.html\n * <head>\n * <script>{themeScript}</script>\n * ...\n * </head>\n */\nexport const themeScript = `(function(){try{var t=localStorage.getItem(\"${STORAGE_KEY}\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r}catch(e){}})()`\n"],"mappings":"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"}
@@ -1,28 +1,30 @@
1
- import type { MiddlewareContext } from '@pyreon/server';
1
+ import { MiddlewareContext } from "@pyreon/server";
2
+
3
+ //#region src/actions.d.ts
2
4
  /** Context passed to server action handlers. */
3
- export interface ActionContext {
4
- /** The original request. */
5
- request: Request;
6
- /** Parsed form data (for form submissions). */
7
- formData: FormData | null;
8
- /** Parsed JSON body (for JSON submissions). */
9
- json: unknown;
10
- /** Request headers. */
11
- headers: Headers;
5
+ interface ActionContext {
6
+ /** The original request. */
7
+ request: Request;
8
+ /** Parsed form data (for form submissions). */
9
+ formData: FormData | null;
10
+ /** Parsed JSON body (for JSON submissions). */
11
+ json: unknown;
12
+ /** Request headers. */
13
+ headers: Headers;
12
14
  }
13
15
  /** A server action handler function. */
14
- export type ActionHandler<T = unknown> = (ctx: ActionContext) => T | Promise<T>;
16
+ type ActionHandler<T = unknown> = (ctx: ActionContext) => T | Promise<T>;
15
17
  /** A registered action with its ID and handler. */
16
18
  interface RegisteredAction {
17
- id: string;
18
- handler: ActionHandler;
19
+ id: string;
20
+ handler: ActionHandler;
19
21
  }
20
22
  /** Client-side callable action returned by defineAction. */
21
- export interface Action<T = unknown> {
22
- /** Call the action with JSON data. */
23
- (data?: unknown): Promise<T>;
24
- /** The action's unique ID. */
25
- actionId: string;
23
+ interface Action<T = unknown> {
24
+ /** Call the action with JSON data. */
25
+ (data?: unknown): Promise<T>;
26
+ /** The action's unique ID. */
27
+ actionId: string;
26
28
  }
27
29
  /**
28
30
  * Define a server action. Returns a callable function that:
@@ -40,18 +42,19 @@ export interface Action<T = unknown> {
40
42
  * // In a component:
41
43
  * const result = await createPost({ title: 'Hello', body: '...' })
42
44
  */
43
- export declare function defineAction<T = unknown>(handler: ActionHandler<T>): Action<T>;
45
+ declare function defineAction<T = unknown>(handler: ActionHandler<T>): Action<T>;
44
46
  /** Get all registered actions. Useful for testing. */
45
- export declare function getRegisteredActions(): Map<string, RegisteredAction>;
47
+ declare function getRegisteredActions(): Map<string, RegisteredAction>;
46
48
  /**
47
49
  * Reset the action registry. Useful for testing.
48
50
  * @internal
49
51
  */
50
- export declare function _resetActions(): void;
52
+ declare function _resetActions(): void;
51
53
  /**
52
54
  * Create a middleware that handles action requests at `/_zero/actions/*`.
53
55
  * Mount this before the SSR handler in the server entry.
54
56
  */
55
- export declare function createActionMiddleware(): (ctx: MiddlewareContext) => Response | undefined | Promise<Response | undefined>;
56
- export {};
57
- //# sourceMappingURL=actions.d.ts.map
57
+ declare function createActionMiddleware(): (ctx: MiddlewareContext) => Response | undefined | Promise<Response | undefined>;
58
+ //#endregion
59
+ export { Action, ActionContext, ActionHandler, _resetActions, createActionMiddleware, defineAction, getRegisteredActions };
60
+ //# sourceMappingURL=actions2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAIvD,gDAAgD;AAChD,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,+CAA+C;IAC/C,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACzB,+CAA+C;IAC/C,IAAI,EAAE,OAAO,CAAA;IACb,uBAAuB;IACvB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,wCAAwC;AACxC,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,aAAa,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAE/E,mDAAmD;AACnD,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,aAAa,CAAA;CACvB;AAED,4DAA4D;AAC5D,MAAM,WAAW,MAAM,CAAC,CAAC,GAAG,OAAO;IACjC,sCAAsC;IACtC,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC5B,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAA;CACjB;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAmC9E;AAED,sDAAsD;AACtD,wBAAgB,oBAAoB,IAAI,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAEpE;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAGpC;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,CACxC,GAAG,EAAE,iBAAiB,KACnB,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAiBxD"}
1
+ {"version":3,"file":"actions2.d.ts","names":[],"sources":["../../../src/actions.ts"],"mappings":";;;;UAKiB,aAAA;EAAA;EAEf,OAAA,EAAS,OAAA;;EAET,QAAA,EAAU,QAAA;EAAA;EAEV,IAAA;EAEgB;EAAhB,OAAA,EAAS,OAAA;AAAA;;KAIC,aAAA,iBAA8B,GAAA,EAAK,aAAA,KAAkB,CAAA,GAAI,OAAA,CAAQ,CAAA;;UAGnE,gBAAA;EACR,EAAA;EACA,OAAA,EAAS,aAAA;AAAA;;UAIM,MAAA;EATQ;EAAA,CAWtB,IAAA,aAAiB,OAAA,CAAQ,CAAA;EAXmB;EAa7C,QAAA;AAAA;;;;;;;;;;;;AAb6E;;;;;iBAoC/D,YAAA,aAAA,CAA0B,OAAA,EAAS,aAAA,CAAc,CAAA,IAAK,MAAA,CAAO,CAAA;;iBAsC7D,oBAAA,CAAA,GAAwB,GAAA,SAAY,gBAAA;;AAjEpD;;;iBAyEgB,aAAA,CAAA;;;;;iBAUA,sBAAA,CAAA,IACd,GAAA,EAAK,iBAAA,KACF,QAAA,eAAuB,OAAA,CAAQ,QAAA"}
package/lib/types/ai.d.ts CHANGED
@@ -1,68 +1,47 @@
1
- /**
2
- * AI integration utilities for Zero.
3
- *
4
- * - llms.txt auto-generation from routes and API routes
5
- * - JSON-LD auto-inference from route meta + Meta props
6
- * - AI plugin manifest (/.well-known/ai-plugin.json) from API routes
7
- *
8
- * @example
9
- * ```ts
10
- * // vite.config.ts
11
- * import { aiPlugin } from "@pyreon/zero/ai"
12
- *
13
- * export default {
14
- * plugins: [
15
- * aiPlugin({
16
- * name: "My App",
17
- * origin: "https://example.com",
18
- * description: "A modern web application",
19
- * }),
20
- * ],
21
- * }
22
- * ```
23
- */
24
- import type { Plugin } from 'vite';
25
- export interface AiPluginConfig {
26
- /** App/API name. */
27
- name: string;
28
- /** App description for AI agents. */
29
- description: string;
30
- /** Base URL. e.g. "https://example.com" */
31
- origin: string;
32
- /** Contact email (required by OpenAI plugin spec). */
33
- contactEmail?: string;
34
- /** Legal info URL. */
35
- legalUrl?: string;
36
- /** Logo URL for the plugin. */
37
- logoUrl?: string;
38
- /** Routes directory relative to project root. Default: "src/routes" */
39
- routesDir?: string;
40
- /** API routes directory relative to project root. Default: "src/api" */
41
- apiDir?: string;
42
- /**
43
- * API route descriptions — map of pattern to description.
44
- * Used for llms.txt and ai-plugin.json.
45
- *
46
- * @example
47
- * ```ts
48
- * apiDescriptions: {
49
- * "GET /api/posts": "List all blog posts, supports ?page=N&limit=N",
50
- * "GET /api/posts/:id": "Get a single post by ID",
51
- * "POST /api/posts": "Create a new post (requires auth)",
52
- * }
53
- * ```
54
- */
55
- apiDescriptions?: Record<string, string>;
56
- /**
57
- * Page descriptions — map of URL path to description.
58
- * Used for llms.txt. Falls back to route meta.title/description.
59
- */
60
- pageDescriptions?: Record<string, string>;
61
- /**
62
- * Additional content to append to llms.txt.
63
- * Useful for authentication instructions, rate limits, etc.
64
- */
65
- llmsExtra?: string;
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/ai.d.ts
4
+ interface AiPluginConfig {
5
+ /** App/API name. */
6
+ name: string;
7
+ /** App description for AI agents. */
8
+ description: string;
9
+ /** Base URL. e.g. "https://example.com" */
10
+ origin: string;
11
+ /** Contact email (required by OpenAI plugin spec). */
12
+ contactEmail?: string;
13
+ /** Legal info URL. */
14
+ legalUrl?: string;
15
+ /** Logo URL for the plugin. */
16
+ logoUrl?: string;
17
+ /** Routes directory relative to project root. Default: "src/routes" */
18
+ routesDir?: string;
19
+ /** API routes directory relative to project root. Default: "src/api" */
20
+ apiDir?: string;
21
+ /**
22
+ * API route descriptions — map of pattern to description.
23
+ * Used for llms.txt and ai-plugin.json.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * apiDescriptions: {
28
+ * "GET /api/posts": "List all blog posts, supports ?page=N&limit=N",
29
+ * "GET /api/posts/:id": "Get a single post by ID",
30
+ * "POST /api/posts": "Create a new post (requires auth)",
31
+ * }
32
+ * ```
33
+ */
34
+ apiDescriptions?: Record<string, string>;
35
+ /**
36
+ * Page descriptions — map of URL path to description.
37
+ * Used for llms.txt. Falls back to route meta.title/description.
38
+ */
39
+ pageDescriptions?: Record<string, string>;
40
+ /**
41
+ * Additional content to append to llms.txt.
42
+ * Useful for authentication instructions, rate limits, etc.
43
+ */
44
+ llmsExtra?: string;
66
45
  }
67
46
  /**
68
47
  * Generate llms.txt content from route files and config.
@@ -81,38 +60,38 @@ export interface AiPluginConfig {
81
60
  *
82
61
  * @internal Exported for testing.
83
62
  */
84
- export declare function generateLlmsTxt(routeFiles: string[], apiFiles: string[], config: AiPluginConfig): string;
63
+ declare function generateLlmsTxt(routeFiles: string[], apiFiles: string[], config: AiPluginConfig): string;
85
64
  /**
86
65
  * Generate llms-full.txt — expanded version with more detail.
87
66
  * Includes all route metadata and API descriptions.
88
67
  *
89
68
  * @internal Exported for testing.
90
69
  */
91
- export declare function generateLlmsFullTxt(routeFiles: string[], apiFiles: string[], config: AiPluginConfig): string;
92
- export interface InferJsonLdOptions {
93
- /** Page URL. */
70
+ declare function generateLlmsFullTxt(routeFiles: string[], apiFiles: string[], config: AiPluginConfig): string;
71
+ interface InferJsonLdOptions {
72
+ /** Page URL. */
73
+ url: string;
74
+ /** Page title. */
75
+ title?: string;
76
+ /** Page description. */
77
+ description?: string;
78
+ /** Page image. */
79
+ image?: string;
80
+ /** Site name. */
81
+ siteName?: string;
82
+ /** Page type hint. */
83
+ type?: 'website' | 'article' | 'product' | 'profile';
84
+ /** Article metadata. */
85
+ publishedTime?: string;
86
+ /** Article author. */
87
+ author?: string;
88
+ /** Article tags. */
89
+ tags?: string[];
90
+ /** Breadcrumb path segments. */
91
+ breadcrumbs?: Array<{
92
+ name: string;
94
93
  url: string;
95
- /** Page title. */
96
- title?: string;
97
- /** Page description. */
98
- description?: string;
99
- /** Page image. */
100
- image?: string;
101
- /** Site name. */
102
- siteName?: string;
103
- /** Page type hint. */
104
- type?: 'website' | 'article' | 'product' | 'profile';
105
- /** Article metadata. */
106
- publishedTime?: string;
107
- /** Article author. */
108
- author?: string;
109
- /** Article tags. */
110
- tags?: string[];
111
- /** Breadcrumb path segments. */
112
- breadcrumbs?: Array<{
113
- name: string;
114
- url: string;
115
- }>;
94
+ }>;
116
95
  }
117
96
  /**
118
97
  * Auto-infer JSON-LD structured data from page metadata.
@@ -133,7 +112,7 @@ export interface InferJsonLdOptions {
133
112
  * // → [Article schema, BreadcrumbList schema]
134
113
  * ```
135
114
  */
136
- export declare function inferJsonLd(options: InferJsonLdOptions): Record<string, unknown>[];
115
+ declare function inferJsonLd(options: InferJsonLdOptions): Record<string, unknown>[];
137
116
  /**
138
117
  * Generate an OpenAI-compatible AI plugin manifest.
139
118
  *
@@ -141,13 +120,13 @@ export declare function inferJsonLd(options: InferJsonLdOptions): Record<string,
141
120
  *
142
121
  * @internal Exported for testing.
143
122
  */
144
- export declare function generateAiPluginManifest(config: AiPluginConfig): Record<string, unknown>;
123
+ declare function generateAiPluginManifest(config: AiPluginConfig): Record<string, unknown>;
145
124
  /**
146
125
  * Generate a minimal OpenAPI 3.0 spec from API route descriptions.
147
126
  *
148
127
  * @internal Exported for testing.
149
128
  */
150
- export declare function generateOpenApiSpec(apiFiles: string[], config: AiPluginConfig): Record<string, unknown>;
129
+ declare function generateOpenApiSpec(apiFiles: string[], config: AiPluginConfig): Record<string, unknown>;
151
130
  /**
152
131
  * AI integration Vite plugin.
153
132
  *
@@ -178,5 +157,7 @@ export declare function generateOpenApiSpec(apiFiles: string[], config: AiPlugin
178
157
  * }
179
158
  * ```
180
159
  */
181
- export declare function aiPlugin(config: AiPluginConfig): Plugin;
182
- //# sourceMappingURL=ai.d.ts.map
160
+ declare function aiPlugin(config: AiPluginConfig): Plugin;
161
+ //#endregion
162
+ export { AiPluginConfig, InferJsonLdOptions, aiPlugin, generateAiPluginManifest, generateLlmsFullTxt, generateLlmsTxt, generateOpenApiSpec, inferJsonLd };
163
+ //# sourceMappingURL=ai2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAKlC,MAAM,WAAW,cAAc;IAC7B,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAA;IACd,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAAE,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,cAAc,GACrB,MAAM,CAiFR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAAE,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,cAAc,GACrB,MAAM,CAiDR;AAID,MAAM,WAAW,kBAAkB;IACjC,gBAAgB;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iBAAiB;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sBAAsB;IACtB,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;IACpD,wBAAwB;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oBAAoB;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,gCAAgC;IAChC,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAuFlF;AAID;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBxF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4CzB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CA0FvD"}
1
+ {"version":3,"file":"ai2.d.ts","names":[],"sources":["../../../src/ai.ts"],"mappings":";;;UA4BiB,cAAA;EAiEO;EA/DtB,IAAA;EA8DA;EA5DA,WAAA;EA6DA;EA3DA,MAAA;EA2DsB;EAzDtB,YAAA;EAmJiC;EAjJjC,QAAA;EAoJsB;EAlJtB,OAAA;EAiJA;EA/IA,SAAA;EAgJA;EA9IA,MAAA;EA8IsB;AAsDxB;;;;;;;;;;;;EAtLE,eAAA,GAAkB,MAAA;EA0MlB;;;;EArMA,gBAAA,GAAmB,MAAA;EAqMoB;AAsBzC;;;EAtNE,SAAA;AAAA;;;;;AAwTF;;;;;;;;;AAuBA;;;;iBAzTgB,eAAA,CACd,UAAA,YACA,QAAA,YACA,MAAA,EAAQ,cAAA;;;;;;AAuYV;iBA7SgB,mBAAA,CACd,UAAA,YACA,QAAA,YACA,MAAA,EAAQ,cAAA;AAAA,UAsDO,kBAAA;EAoPuC;EAlPtD,GAAA;EAkPuB;EAhPvB,KAAA;EAgPsD;EA9OtD,WAAA;;EAEA,KAAA;;EAEA,QAAA;;EAEA,IAAA;;EAEA,aAAA;;EAEA,MAAA;;EAEA,IAAA;;EAEA,WAAA,GAAc,KAAA;IAAQ,IAAA;IAAc,GAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;iBAsBtB,WAAA,CAAY,OAAA,EAAS,kBAAA,GAAqB,MAAA;;;;;;;;iBAkG1C,wBAAA,CAAyB,MAAA,EAAQ,cAAA,GAAiB,MAAA;;;;;;iBAuBlD,mBAAA,CACd,QAAA,YACA,MAAA,EAAQ,cAAA,GACP,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8Ea,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,MAAA"}