@devlusoft/devix 0.4.1-beta.9 → 0.4.1

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 (63) hide show
  1. package/dist/cli/build.js +130 -31
  2. package/dist/cli/build.js.map +4 -4
  3. package/dist/cli/dev-server.js +119 -20
  4. package/dist/cli/dev-server.js.map +4 -4
  5. package/dist/cli/generate.js +130 -31
  6. package/dist/cli/generate.js.map +4 -4
  7. package/dist/cli/index.js +131 -32
  8. package/dist/cli/index.js.map +4 -4
  9. package/dist/cli/start.js +1 -1
  10. package/dist/cli/start.js.map +3 -3
  11. package/dist/runtime/client-router.js +1 -1
  12. package/dist/runtime/client-router.js.map +3 -3
  13. package/dist/runtime/context.d.ts +1 -0
  14. package/dist/runtime/context.js.map +2 -2
  15. package/dist/runtime/fetch.d.ts +4 -35
  16. package/dist/runtime/fetch.js +1 -1
  17. package/dist/runtime/fetch.js.map +3 -3
  18. package/dist/runtime/index.d.ts +32 -4
  19. package/dist/runtime/index.js +1 -1
  20. package/dist/runtime/index.js.map +4 -4
  21. package/dist/runtime/link.d.ts +3 -2
  22. package/dist/runtime/link.js +1 -1
  23. package/dist/runtime/link.js.map +4 -4
  24. package/dist/runtime/router-provider.js +1 -1
  25. package/dist/runtime/router-provider.js.map +3 -3
  26. package/dist/runtime/server-app.js +1 -1
  27. package/dist/runtime/server-app.js.map +3 -3
  28. package/dist/server/api-router.d.ts +0 -1
  29. package/dist/server/api-router.js +1 -1
  30. package/dist/server/api-router.js.map +3 -3
  31. package/dist/server/api.js +1 -1
  32. package/dist/server/api.js.map +3 -3
  33. package/dist/server/index.js +1 -1
  34. package/dist/server/index.js.map +3 -3
  35. package/dist/server/pages-router.d.ts +0 -1
  36. package/dist/server/pages-router.js +1 -1
  37. package/dist/server/pages-router.js.map +3 -3
  38. package/dist/server/render.d.ts +15 -0
  39. package/dist/server/render.js +1 -1
  40. package/dist/server/render.js.map +3 -3
  41. package/dist/server/routes.js +1 -1
  42. package/dist/server/routes.js.map +3 -3
  43. package/dist/server/types.d.ts +4 -2
  44. package/dist/utils/banner.js +1 -1
  45. package/dist/utils/banner.js.map +1 -1
  46. package/dist/utils/response.d.ts +13 -2
  47. package/dist/utils/response.js +1 -1
  48. package/dist/utils/response.js.map +3 -3
  49. package/dist/vite/codegen/entry-client.js +14 -1
  50. package/dist/vite/codegen/entry-client.js.map +2 -2
  51. package/dist/vite/codegen/page-types.d.ts +5 -0
  52. package/dist/vite/codegen/page-types.js +14 -0
  53. package/dist/vite/codegen/page-types.js.map +7 -0
  54. package/dist/vite/codegen/routes-dts.js +13 -10
  55. package/dist/vite/codegen/routes-dts.js.map +2 -2
  56. package/dist/vite/codegen/scan-api.js +1 -1
  57. package/dist/vite/codegen/scan-api.js.map +1 -1
  58. package/dist/vite/codegen/server-entry.d.ts +9 -0
  59. package/dist/vite/codegen/server-entry.js +73 -0
  60. package/dist/vite/codegen/server-entry.js.map +7 -0
  61. package/dist/vite/index.js +131 -32
  62. package/dist/vite/index.js.map +4 -4
  63. package/package.json +2 -2
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/vite/codegen/page-types.ts"],
4
+ "sourcesContent": ["import {existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync} from \"node:fs\";\nimport {join, relative} from \"node:path\";\nimport {parseSync} from \"oxc-parser\";\n\nfunction walkPages(dir: string, root: string): string[] {\n const entries: string[] = []\n for (const name of readdirSync(dir)) {\n const full = join(dir, name)\n if (statSync(full).isDirectory()) {\n entries.push(...walkPages(full, root))\n } else if (/\\.(ts|tsx)$/.test(name) && name !== 'layout.tsx' && name !== 'error.tsx') {\n entries.push(relative(root, full).replace(/\\\\/g, '/'))\n }\n }\n return entries\n}\n\nexport function hasLoaderExport(code: string, filePath: string): boolean {\n const ast = parseSync(filePath, code, {sourceType: 'module'})\n for (const node of ast.program.body) {\n if (node.type !== 'ExportNamedDeclaration') continue\n const decl = node.declaration\n if (decl?.type === 'FunctionDeclaration' && decl.id?.name === 'loader') return true\n if (decl?.type === 'VariableDeclaration') {\n for (const d of decl.declarations) {\n if (d.id.type === 'Identifier' && d.id.name === 'loader') return true\n }\n }\n for (const spec of (node.specifiers ?? [])) {\n if (spec.exported.type === 'Identifier' && spec.exported.name === 'loader') return true\n }\n }\n return false\n}\n\nexport function generatePageTypesDts(importPath: string, withLoader: boolean): string {\n if (!withLoader) {\n return '// auto-generado por devix - no editar\\nexport type PageData = undefined\\nexport type PageParams = Record<string, string>\\n'\n }\n return `// auto-generado por devix \u2014 no editar\\nimport type { loader } from \"${importPath}\"\\nimport type { Redirect } from \"@devlusoft/devix\"\\n\\nexport type PageData = Exclude<\\n Awaited<ReturnType<NonNullable<typeof loader>>>,\\n Redirect | void | undefined\\n>\\nexport type PageParams = NonNullable<Parameters<typeof loader>[0]>[\"params\"]\\n`\n}\n\nexport function writePageTypes(pageRelPath: string, root: string): void {\n const fullPath = join(root, pageRelPath)\n const code = readFileSync(fullPath, 'utf-8')\n const withLoader = hasLoaderExport(code, fullPath)\n\n const typesDir = join(root, '.devix', 'pages', pageRelPath.replace(/\\.(tsx?|jsx?)$/, ''))\n const outPath = join(typesDir, '$types.d.ts')\n\n const pageAbsNoExt = fullPath.replace(/\\.(tsx?|jsx?)$/, '')\n const importPath = relative(typesDir, pageAbsNoExt).replace(/\\\\/g, '/')\n\n const content = generatePageTypesDts(importPath, withLoader)\n\n if (existsSync(outPath) && readFileSync(outPath, 'utf-8') === content) return\n\n mkdirSync(typesDir, {recursive: true})\n writeFileSync(outPath, content, 'utf-8')\n}\n\nexport function deletePageTypes(pageRelPath: string, root: string): void {\n const typesDir = join(root, '.devix', 'pages', pageRelPath.replace(/\\.(tsx?|jsx?)$/, ''))\n const outPath = join(typesDir, '$types.d.ts')\n if (existsSync(outPath)) rmSync(outPath)\n}\n\nexport function scanAndWritePageTypes(appDir: string, root: string): void {\n const pagesDir = join(root, appDir, 'pages')\n let files: string[]\n try {\n files = walkPages(pagesDir, root)\n } catch {\n return\n }\n for (const file of files) {\n try {\n writePageTypes(file, root)\n } catch {\n /* ignorar archivos no procesables */\n }\n }\n}"],
5
+ "mappings": "AAAA,OAAQ,cAAAA,EAAY,aAAAC,EAAW,eAAAC,EAAa,gBAAAC,EAAc,UAAAC,EAAQ,YAAAC,EAAU,iBAAAC,MAAoB,UAChG,OAAQ,QAAAC,EAAM,YAAAC,MAAe,YAC7B,OAAQ,aAAAC,MAAgB,aAExB,SAASC,EAAUC,EAAaC,EAAwB,CACpD,IAAMC,EAAoB,CAAC,EAC3B,QAAWC,KAAQZ,EAAYS,CAAG,EAAG,CACjC,IAAMI,EAAOR,EAAKI,EAAKG,CAAI,EACvBT,EAASU,CAAI,EAAE,YAAY,EAC3BF,EAAQ,KAAK,GAAGH,EAAUK,EAAMH,CAAI,CAAC,EAC9B,cAAc,KAAKE,CAAI,GAAKA,IAAS,cAAgBA,IAAS,aACrED,EAAQ,KAAKL,EAASI,EAAMG,CAAI,EAAE,QAAQ,MAAO,GAAG,CAAC,CAE7D,CACA,OAAOF,CACX,CAEO,SAASG,EAAgBC,EAAcC,EAA2B,CACrE,IAAMC,EAAMV,EAAUS,EAAUD,EAAM,CAAC,WAAY,QAAQ,CAAC,EAC5D,QAAWG,KAAQD,EAAI,QAAQ,KAAM,CACjC,GAAIC,EAAK,OAAS,yBAA0B,SAC5C,IAAMC,EAAOD,EAAK,YAClB,GAAIC,GAAM,OAAS,uBAAyBA,EAAK,IAAI,OAAS,SAAU,MAAO,GAC/E,GAAIA,GAAM,OAAS,uBACf,QAAWC,KAAKD,EAAK,aACjB,GAAIC,EAAE,GAAG,OAAS,cAAgBA,EAAE,GAAG,OAAS,SAAU,MAAO,GAGzE,QAAWC,KAASH,EAAK,YAAc,CAAC,EACpC,GAAIG,EAAK,SAAS,OAAS,cAAgBA,EAAK,SAAS,OAAS,SAAU,MAAO,EAE3F,CACA,MAAO,EACX,CAEO,SAASC,EAAqBC,EAAoBC,EAA6B,CAClF,OAAKA,EAGE;AAAA,+BAAwED,CAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAF9E;AAAA;AAAA;AAAA,CAGf,CAEO,SAASE,EAAeC,EAAqBhB,EAAoB,CACpE,IAAMiB,EAAWtB,EAAKK,EAAMgB,CAAW,EACjCX,EAAOd,EAAa0B,EAAU,OAAO,EACrCH,EAAaV,EAAgBC,EAAMY,CAAQ,EAE3CC,EAAWvB,EAAKK,EAAM,SAAU,QAASgB,EAAY,QAAQ,iBAAkB,EAAE,CAAC,EAClFG,EAAUxB,EAAKuB,EAAU,aAAa,EAEtCE,EAAeH,EAAS,QAAQ,iBAAkB,EAAE,EACpDJ,EAAajB,EAASsB,EAAUE,CAAY,EAAE,QAAQ,MAAO,GAAG,EAEhEC,EAAUT,EAAqBC,EAAYC,CAAU,EAEvD1B,EAAW+B,CAAO,GAAK5B,EAAa4B,EAAS,OAAO,IAAME,IAE9DhC,EAAU6B,EAAU,CAAC,UAAW,EAAI,CAAC,EACrCxB,EAAcyB,EAASE,EAAS,OAAO,EAC3C,CAEO,SAASC,EAAgBN,EAAqBhB,EAAoB,CACrE,IAAMkB,EAAWvB,EAAKK,EAAM,SAAU,QAASgB,EAAY,QAAQ,iBAAkB,EAAE,CAAC,EAClFG,EAAUxB,EAAKuB,EAAU,aAAa,EACxC9B,EAAW+B,CAAO,GAAG3B,EAAO2B,CAAO,CAC3C,CAEO,SAASI,EAAsBC,EAAgBxB,EAAoB,CACtE,IAAMyB,EAAW9B,EAAKK,EAAMwB,EAAQ,OAAO,EACvCE,EACJ,GAAI,CACAA,EAAQ5B,EAAU2B,EAAUzB,CAAI,CACpC,MAAQ,CACJ,MACJ,CACA,QAAW2B,KAAQD,EACf,GAAI,CACAX,EAAeY,EAAM3B,CAAI,CAC7B,MAAQ,CAER,CAER",
6
+ "names": ["existsSync", "mkdirSync", "readdirSync", "readFileSync", "rmSync", "statSync", "writeFileSync", "join", "relative", "parseSync", "walkPages", "dir", "root", "entries", "name", "full", "hasLoaderExport", "code", "filePath", "ast", "node", "decl", "d", "spec", "generatePageTypesDts", "importPath", "withLoader", "writePageTypes", "pageRelPath", "fullPath", "typesDir", "outPath", "pageAbsNoExt", "content", "deletePageTypes", "scanAndWritePageTypes", "appDir", "pagesDir", "files", "file"]
7
+ }
@@ -1,24 +1,27 @@
1
- function s(e){return e.replace(/\.(tsx|ts|jsx|js)$/,"").replace(/\(.*?\)\//g,"").replace(/^index$|\/index$/,"").replace(/\[([^\]]+)]/g,":$1")||"/"}function a(e,t){let n=e.slice(t.length+1).replace(/\\/g,"/"),o=s(n);return o==="/"?"/api":`/api/${o}`.replace("/api//","/api/")}function p(e,t){return"_api_"+e.slice(`${t}/`.length).replace(/\.(ts|tsx)$/,"").replace(/[^a-zA-Z0-9]/g,"_")}function g(e,t,n){return{filePath:e,urlPattern:a(e,t),identifier:p(e,t),methods:n}}function f(e,t){if(e.length===0)return`// auto-generado por devix \u2014 no editar
1
+ function i(e){return e.replace(/\.(tsx|ts|jsx|js)$/,"").replace(/\(.*?\)\//g,"").replace(/^index$|\/index$/,"").replace(/\[([^\]]+)]/g,":$1")||"/"}function a(e,t){let n=e.slice(t.length+1).replace(/\\/g,"/"),o=i(n);return o==="/"?"/api":`/api/${o}`.replace("/api//","/api/")}function p(e,t){return"_api_"+e.slice(`${t}/`.length).replace(/\.(ts|tsx)$/,"").replace(/[^a-zA-Z0-9]/g,"_")}function g(e,t,n){return{filePath:e,urlPattern:a(e,t),identifier:p(e,t),methods:n}}function f(e,t){if(e.length===0)return`// auto-generado por devix \u2014 no editar
2
+ export {}
2
3
  declare module '@devlusoft/devix' {
3
4
  interface ApiRoutes {}
4
5
  }
5
- `;let n=e.map(r=>{let i="../"+r.filePath.replace(/\.(ts|tsx)$/,"");return`import type * as ${r.identifier} from '${i}'`}).join(`
6
- `),o=e.flatMap(r=>r.methods.map(i=>` '${i} ${r.urlPattern}': InferRoute<(typeof ${r.identifier})['${i}']>`)).join(`
6
+ `;let n=e.map(r=>{let s="../"+r.filePath.replace(/\.(ts|tsx)$/,"");return`import type * as ${r.identifier} from '${s}'`}).join(`
7
+ `),o=e.flatMap(r=>r.methods.map(s=>` '${s} ${r.urlPattern}': InferRoute<(typeof ${r.identifier})['${s}']>`)).join(`
7
8
  `);return`// auto-generado por devix \u2014 no editar
8
9
  ${n}
9
10
 
10
- type JsonResponse<T> = Response & { readonly __body: T }
11
- type UnwrapJson<T> = T extends JsonResponse<infer U> ? U : never
12
- type InferFnReturn<T> = T extends (...args: any[]) => any
13
- ? UnwrapJson<Awaited<ReturnType<T>>> | Exclude<Awaited<ReturnType<T>>, JsonResponse<any> | null | void | undefined>
14
- : never
11
+ type JsonResponse<T, S extends number = number> = Response & { readonly __body: T; readonly __status: S }
12
+ type Is2xx<S extends number> = [number] extends [S] ? boolean : S extends 200 | 201 | 202 | 203 | 204 | 205 | 206 ? true : false
13
+ type UnwrapSuccessJson<T> = T extends JsonResponse<infer U, infer S> ? Is2xx<S> extends false ? never : U : never
14
+ type UnwrapErrorJson<T> = T extends JsonResponse<infer U, infer S> ? Is2xx<S> extends true ? never : U : never
15
+ type InferFnSuccess<T> = T extends (...args: any[]) => any ? UnwrapSuccessJson<Awaited<ReturnType<T>>> : never
16
+ type InferFnErrors<T> = T extends (...args: any[]) => any ? UnwrapErrorJson<Awaited<ReturnType<T>>> : never
15
17
  type InferRoute<T> =
16
18
  T extends { readonly __return?: infer TReturn; readonly __body?: infer TBody }
17
19
  ? {
18
20
  __body: [TBody] extends [undefined] ? never : Exclude<TBody, undefined>
19
- __response: InferFnReturn<() => TReturn>
21
+ __response: InferFnSuccess<() => TReturn>
22
+ __errors: InferFnErrors<() => TReturn>
20
23
  }
21
- : InferFnReturn<T>
24
+ : InferFnSuccess<T>
22
25
 
23
26
  declare module '@devlusoft/devix' {
24
27
  interface ApiRoutes {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/utils/patterns.ts", "../../../src/server/api-router.ts", "../../../src/vite/codegen/routes-dts.ts"],
4
- "sourcesContent": ["export function routePattern(rel: string): string {\n return rel\n .replace(/\\.(tsx|ts|jsx|js)$/, '')\n .replace(/\\(.*?\\)\\//g, '')\n .replace(/^index$|\\/index$/, '')\n .replace(/\\[([^\\]]+)]/g, ':$1')\n || '/'\n}", "import {routePattern} from \"../utils/patterns\";\n\nexport interface ApiRoute {\n path: string\n key: string\n params: string[]\n regex: RegExp\n}\n\nexport interface ApiMiddleware {\n dir: string\n key: string\n}\n\nexport interface ApiResult {\n routes: ApiRoute[]\n middlewares: ApiMiddleware[]\n}\n\nexport function keyToRoutePattern(key: string, apiDir: string): string {\n const rel = key.slice(apiDir.length + 1).replace(/\\\\/g, '/')\n const pattern = routePattern(rel)\n return pattern === '/' ? '/api' : `/api/${pattern}`.replace('/api//', '/api/')\n}\n\nfunction keyToDir(key: string): string {\n return key.slice(0, key.lastIndexOf('/'))\n}\n\nlet cache: ApiResult | null = null\n\nexport function invalidateApiCache() {\n cache = null\n}\n\nexport function buildRoutes(routeKeys: string[], middlewareKeys: string[], apiDir: string): ApiResult {\n if (cache) return cache\n\n const routes: ApiRoute[] = []\n const middlewares: ApiMiddleware[] = []\n\n for (const key of middlewareKeys) {\n middlewares.push({dir: keyToDir(key), key})\n }\n\n for (const key of routeKeys) {\n const pattern = keyToRoutePattern(key, apiDir)\n const params = [...pattern.matchAll(/:([^/]+)/g)].map(m => m[1])\n const regexStr = pattern\n .replace(/:[^/]+/g, '([^/]+)')\n .replace(/\\//g, '\\\\/')\n routes.push({path: pattern, key, params, regex: new RegExp(`^${regexStr}$`)})\n }\n routes.sort((a, b) => {\n const aScore = (a.path.match(/:/g) || []).length\n const bScore = (b.path.match(/:/g) || []).length\n if (aScore !== bScore) return aScore - bScore\n return b.path.length - a.path.length\n })\n\n cache = {routes, middlewares}\n return cache\n}\n\nexport function collectMiddlewareChain(routeKey: string, middlewares: ApiMiddleware[]): ApiMiddleware[] {\n const routeDir = keyToDir(routeKey)\n\n return middlewares\n .filter(mw => routeDir.startsWith(mw.dir))\n .sort((a, b) => a.dir.split('/').length - b.dir.split('/').length)\n}\n\nexport function matchRoute(\n pathname: string,\n routes: ApiRoute[]\n): {route: ApiRoute; params: Record<string, string>} | null {\n for (const route of routes) {\n const match = pathname.match(route.regex)\n if (match) {\n const params: Record<string, string> = {}\n route.params.forEach((name, i) => {\n params[name] = decodeURIComponent(match[i + 1])\n })\n return {route, params}\n }\n }\n return null\n}\n", "import { keyToRoutePattern } from '../../server/api-router'\nimport type { HttpMethod } from './extract-methods'\n\nexport interface RouteEntry {\n filePath: string\n urlPattern: string\n identifier: string\n methods: HttpMethod[]\n}\n\nexport function filePathToIdentifier(filePath: string, apiDir: string): string {\n return '_api_' + filePath\n .slice(`${apiDir}/`.length)\n .replace(/\\.(ts|tsx)$/, '')\n .replace(/[^a-zA-Z0-9]/g, '_')\n}\n\nexport function buildRouteEntry(filePath: string, apiDir: string, methods: HttpMethod[]): RouteEntry {\n return {\n filePath,\n urlPattern: keyToRoutePattern(filePath, apiDir),\n identifier: filePathToIdentifier(filePath, apiDir),\n methods,\n }\n}\n\nexport function generateRoutesDts(entries: RouteEntry[], apiDir: string): string {\n if (entries.length === 0) {\n return `// auto-generado por devix \u2014 no editar\\ndeclare module '@devlusoft/devix' {\\n interface ApiRoutes {}\\n}\\n`\n }\n\n const imports = entries\n .map(e => {\n const importPath = '../' + e.filePath.replace(/\\.(ts|tsx)$/, '')\n return `import type * as ${e.identifier} from '${importPath}'`\n })\n .join('\\n')\n\n const routeLines = entries.flatMap(e =>\n e.methods.map(m =>\n ` '${m} ${e.urlPattern}': InferRoute<(typeof ${e.identifier})['${m}']>`\n )\n ).join('\\n')\n\n return `// auto-generado por devix \u2014 no editar\n${imports}\n\ntype JsonResponse<T> = Response & { readonly __body: T }\ntype UnwrapJson<T> = T extends JsonResponse<infer U> ? U : never\ntype InferFnReturn<T> = T extends (...args: any[]) => any\n ? UnwrapJson<Awaited<ReturnType<T>>> | Exclude<Awaited<ReturnType<T>>, JsonResponse<any> | null | void | undefined>\n : never\ntype InferRoute<T> =\n T extends { readonly __return?: infer TReturn; readonly __body?: infer TBody }\n ? {\n __body: [TBody] extends [undefined] ? never : Exclude<TBody, undefined>\n __response: InferFnReturn<() => TReturn>\n }\n : InferFnReturn<T>\n\ndeclare module '@devlusoft/devix' {\n interface ApiRoutes {\n${routeLines}\n }\n}\n`\n}\n"],
5
- "mappings": "AAAO,SAASA,EAAaC,EAAqB,CAC9C,OAAOA,EACE,QAAQ,qBAAsB,EAAE,EAChC,QAAQ,aAAc,EAAE,EACxB,QAAQ,mBAAoB,EAAE,EAC9B,QAAQ,eAAgB,KAAK,GAC/B,GACX,CCYO,SAASC,EAAkBC,EAAaC,EAAwB,CACnE,IAAMC,EAAMF,EAAI,MAAMC,EAAO,OAAS,CAAC,EAAE,QAAQ,MAAO,GAAG,EACrDE,EAAUC,EAAaF,CAAG,EAChC,OAAOC,IAAY,IAAM,OAAS,QAAQA,CAAO,GAAG,QAAQ,SAAU,OAAO,CACjF,CCbO,SAASE,EAAqBC,EAAkBC,EAAwB,CAC3E,MAAO,QAAUD,EACZ,MAAM,GAAGC,CAAM,IAAI,MAAM,EACzB,QAAQ,cAAe,EAAE,EACzB,QAAQ,gBAAiB,GAAG,CACrC,CAEO,SAASC,EAAgBF,EAAkBC,EAAgBE,EAAmC,CACjG,MAAO,CACH,SAAAH,EACA,WAAYI,EAAkBJ,EAAUC,CAAM,EAC9C,WAAYF,EAAqBC,EAAUC,CAAM,EACjD,QAAAE,CACJ,CACJ,CAEO,SAASE,EAAkBC,EAAuBL,EAAwB,CAC7E,GAAIK,EAAQ,SAAW,EACnB,MAAO;AAAA;AAAA;AAAA;AAAA,EAGX,IAAMC,EAAUD,EACX,IAAIE,GAAK,CACN,IAAMC,EAAa,MAAQD,EAAE,SAAS,QAAQ,cAAe,EAAE,EAC/D,MAAO,oBAAoBA,EAAE,UAAU,UAAUC,CAAU,GAC/D,CAAC,EACA,KAAK;AAAA,CAAI,EAERC,EAAaJ,EAAQ,QAAQE,GAC/BA,EAAE,QAAQ,IAAIG,GACV,QAAQA,CAAC,IAAIH,EAAE,UAAU,yBAAyBA,EAAE,UAAU,MAAMG,CAAC,KACzE,CACJ,EAAE,KAAK;AAAA,CAAI,EAEX,MAAO;AAAA,EACTJ,CAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBPG,CAAU;AAAA;AAAA;AAAA,CAIZ",
4
+ "sourcesContent": ["export function routePattern(rel: string): string {\n return rel\n .replace(/\\.(tsx|ts|jsx|js)$/, '')\n .replace(/\\(.*?\\)\\//g, '')\n .replace(/^index$|\\/index$/, '')\n .replace(/\\[([^\\]]+)]/g, ':$1')\n || '/'\n}", "import {routePattern} from \"../utils/patterns\";\n\nexport interface ApiRoute {\n path: string\n key: string\n params: string[]\n regex: RegExp\n}\n\nexport interface ApiMiddleware {\n dir: string\n key: string\n}\n\nexport interface ApiResult {\n routes: ApiRoute[]\n middlewares: ApiMiddleware[]\n}\n\nexport function keyToRoutePattern(key: string, apiDir: string): string {\n const rel = key.slice(apiDir.length + 1).replace(/\\\\/g, '/')\n const pattern = routePattern(rel)\n return pattern === '/' ? '/api' : `/api/${pattern}`.replace('/api//', '/api/')\n}\n\nfunction keyToDir(key: string): string {\n return key.slice(0, key.lastIndexOf('/'))\n}\n\nexport function buildRoutes(routeKeys: string[], middlewareKeys: string[], apiDir: string): ApiResult {\n const routes: ApiRoute[] = []\n const middlewares: ApiMiddleware[] = []\n\n for (const key of middlewareKeys) {\n middlewares.push({dir: keyToDir(key), key})\n }\n\n for (const key of routeKeys) {\n const pattern = keyToRoutePattern(key, apiDir)\n const params = [...pattern.matchAll(/:([^/]+)/g)].map(m => m[1])\n const regexStr = pattern\n .replace(/:[^/]+/g, '([^/]+)')\n .replace(/\\//g, '\\\\/')\n routes.push({path: pattern, key, params, regex: new RegExp(`^${regexStr}$`)})\n }\n routes.sort((a, b) => {\n const aScore = (a.path.match(/:/g) || []).length\n const bScore = (b.path.match(/:/g) || []).length\n if (aScore !== bScore) return aScore - bScore\n return b.path.length - a.path.length\n })\n\n return {routes, middlewares}\n}\n\nexport function collectMiddlewareChain(routeKey: string, middlewares: ApiMiddleware[]): ApiMiddleware[] {\n const routeDir = keyToDir(routeKey)\n\n return middlewares\n .filter(mw => routeDir.startsWith(mw.dir))\n .sort((a, b) => a.dir.split('/').length - b.dir.split('/').length)\n}\n\nexport function matchRoute(\n pathname: string,\n routes: ApiRoute[]\n): { route: ApiRoute; params: Record<string, string> } | null {\n for (const route of routes) {\n const match = pathname.match(route.regex)\n if (match) {\n const params: Record<string, string> = {}\n route.params.forEach((name, i) => {\n params[name] = decodeURIComponent(match[i + 1])\n })\n return {route, params}\n }\n }\n return null\n}\n", "import { keyToRoutePattern } from '../../server/api-router'\nimport type { HttpMethod } from './extract-methods'\n\nexport interface RouteEntry {\n filePath: string\n urlPattern: string\n identifier: string\n methods: HttpMethod[]\n}\n\nexport function filePathToIdentifier(filePath: string, apiDir: string): string {\n return '_api_' + filePath\n .slice(`${apiDir}/`.length)\n .replace(/\\.(ts|tsx)$/, '')\n .replace(/[^a-zA-Z0-9]/g, '_')\n}\n\nexport function buildRouteEntry(filePath: string, apiDir: string, methods: HttpMethod[]): RouteEntry {\n return {\n filePath,\n urlPattern: keyToRoutePattern(filePath, apiDir),\n identifier: filePathToIdentifier(filePath, apiDir),\n methods,\n }\n}\n\nexport function generateRoutesDts(entries: RouteEntry[], apiDir: string): string {\n if (entries.length === 0) {\n return `// auto-generado por devix \u2014 no editar\\nexport {}\\ndeclare module '@devlusoft/devix' {\\n interface ApiRoutes {}\\n}\\n`\n }\n\n const imports = entries\n .map(e => {\n const importPath = '../' + e.filePath.replace(/\\.(ts|tsx)$/, '')\n return `import type * as ${e.identifier} from '${importPath}'`\n })\n .join('\\n')\n\n const routeLines = entries.flatMap(e =>\n e.methods.map(m =>\n ` '${m} ${e.urlPattern}': InferRoute<(typeof ${e.identifier})['${m}']>`\n )\n ).join('\\n')\n\n return `// auto-generado por devix \u2014 no editar\n${imports}\n\ntype JsonResponse<T, S extends number = number> = Response & { readonly __body: T; readonly __status: S }\ntype Is2xx<S extends number> = [number] extends [S] ? boolean : S extends 200 | 201 | 202 | 203 | 204 | 205 | 206 ? true : false\ntype UnwrapSuccessJson<T> = T extends JsonResponse<infer U, infer S> ? Is2xx<S> extends false ? never : U : never\ntype UnwrapErrorJson<T> = T extends JsonResponse<infer U, infer S> ? Is2xx<S> extends true ? never : U : never\ntype InferFnSuccess<T> = T extends (...args: any[]) => any ? UnwrapSuccessJson<Awaited<ReturnType<T>>> : never\ntype InferFnErrors<T> = T extends (...args: any[]) => any ? UnwrapErrorJson<Awaited<ReturnType<T>>> : never\ntype InferRoute<T> =\n T extends { readonly __return?: infer TReturn; readonly __body?: infer TBody }\n ? {\n __body: [TBody] extends [undefined] ? never : Exclude<TBody, undefined>\n __response: InferFnSuccess<() => TReturn>\n __errors: InferFnErrors<() => TReturn>\n }\n : InferFnSuccess<T>\n\ndeclare module '@devlusoft/devix' {\n interface ApiRoutes {\n${routeLines}\n }\n}\n`\n}\n"],
5
+ "mappings": "AAAO,SAASA,EAAaC,EAAqB,CAC9C,OAAOA,EACE,QAAQ,qBAAsB,EAAE,EAChC,QAAQ,aAAc,EAAE,EACxB,QAAQ,mBAAoB,EAAE,EAC9B,QAAQ,eAAgB,KAAK,GAC/B,GACX,CCYO,SAASC,EAAkBC,EAAaC,EAAwB,CACnE,IAAMC,EAAMF,EAAI,MAAMC,EAAO,OAAS,CAAC,EAAE,QAAQ,MAAO,GAAG,EACrDE,EAAUC,EAAaF,CAAG,EAChC,OAAOC,IAAY,IAAM,OAAS,QAAQA,CAAO,GAAG,QAAQ,SAAU,OAAO,CACjF,CCbO,SAASE,EAAqBC,EAAkBC,EAAwB,CAC3E,MAAO,QAAUD,EACZ,MAAM,GAAGC,CAAM,IAAI,MAAM,EACzB,QAAQ,cAAe,EAAE,EACzB,QAAQ,gBAAiB,GAAG,CACrC,CAEO,SAASC,EAAgBF,EAAkBC,EAAgBE,EAAmC,CACjG,MAAO,CACH,SAAAH,EACA,WAAYI,EAAkBJ,EAAUC,CAAM,EAC9C,WAAYF,EAAqBC,EAAUC,CAAM,EACjD,QAAAE,CACJ,CACJ,CAEO,SAASE,EAAkBC,EAAuBL,EAAwB,CAC7E,GAAIK,EAAQ,SAAW,EACnB,MAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAGX,IAAMC,EAAUD,EACX,IAAIE,GAAK,CACN,IAAMC,EAAa,MAAQD,EAAE,SAAS,QAAQ,cAAe,EAAE,EAC/D,MAAO,oBAAoBA,EAAE,UAAU,UAAUC,CAAU,GAC/D,CAAC,EACA,KAAK;AAAA,CAAI,EAERC,EAAaJ,EAAQ,QAAQE,GAC/BA,EAAE,QAAQ,IAAIG,GACV,QAAQA,CAAC,IAAIH,EAAE,UAAU,yBAAyBA,EAAE,UAAU,MAAMG,CAAC,KACzE,CACJ,EAAE,KAAK;AAAA,CAAI,EAEX,MAAO;AAAA,EACTJ,CAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBPG,CAAU;AAAA;AAAA;AAAA,CAIZ",
6
6
  "names": ["routePattern", "rel", "keyToRoutePattern", "key", "apiDir", "rel", "pattern", "routePattern", "filePathToIdentifier", "filePath", "apiDir", "buildRouteEntry", "methods", "keyToRoutePattern", "generateRoutesDts", "entries", "imports", "e", "importPath", "routeLines", "m"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{readFileSync as m,readdirSync as x,statSync as y}from"node:fs";import{join as i,relative as T}from"node:path";var f=/export\s+(?:const|async\s+function|function)\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/g;function g(t){return t.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/.*$/gm,"")}function a(t){let e=new Set;for(let r of g(t).matchAll(f))e.add(r[1]);return[...e]}function p(t){return t.replace(/\.(tsx|ts|jsx|js)$/,"").replace(/\(.*?\)\//g,"").replace(/^index$|\/index$/,"").replace(/\[([^\]]+)]/g,":$1")||"/"}function u(t,e){let r=t.slice(e.length+1).replace(/\\/g,"/"),o=p(r);return o==="/"?"/api":`/api/${o}`.replace("/api//","/api/")}function h(t,e){return"_api_"+t.slice(`${e}/`.length).replace(/\.(ts|tsx)$/,"").replace(/[^a-zA-Z0-9]/g,"_")}function c(t,e,r){return{filePath:t,urlPattern:u(t,e),identifier:h(t,e),methods:r}}function d(t,e){let r=[];for(let o of x(t)){let n=i(t,o);y(n).isDirectory()?r.push(...d(n,e)):/\.(ts|tsx)$/.test(o)&&r.push(T(e,n).replace(/\\/g,"/"))}return r}function v(t,e){let r=i(e,t,"api"),o;try{o=d(r,e)}catch{return[]}return o.filter(n=>!n.endsWith("middleware.ts")&&!n.endsWith("middleware.tsx")).flatMap(n=>{try{let l=m(i(e,n),"utf-8"),s=a(l);return s.length===0?[]:[c(n,`${t}/api`,s)]}catch{return[]}})}export{v as scanApiFiles};
1
+ import{readFileSync as m,readdirSync as h,statSync as y}from"node:fs";import{join as o,relative as T}from"node:path";var f=/export\s+(?:const|async\s+function|function)\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/g;function g(e){return e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/.*$/gm,"")}function a(e){let t=new Set;for(let r of g(e).matchAll(f))t.add(r[1]);return[...t]}function p(e){return e.replace(/\.(tsx|ts|jsx|js)$/,"").replace(/\(.*?\)\//g,"").replace(/^index$|\/index$/,"").replace(/\[([^\]]+)]/g,":$1")||"/"}function u(e,t){let r=e.slice(t.length+1).replace(/\\/g,"/"),s=p(r);return s==="/"?"/api":`/api/${s}`.replace("/api//","/api/")}function x(e,t){return"_api_"+e.slice(`${t}/`.length).replace(/\.(ts|tsx)$/,"").replace(/[^a-zA-Z0-9]/g,"_")}function c(e,t,r){return{filePath:e,urlPattern:u(e,t),identifier:x(e,t),methods:r}}function d(e,t){let r=[];for(let s of h(e)){let n=o(e,s);y(n).isDirectory()?r.push(...d(n,t)):/\.(ts|tsx)$/.test(s)&&r.push(T(t,n).replace(/\\/g,"/"))}return r}function I(e,t){let r=o(t,e,"api"),s;try{s=d(r,t)}catch{return[]}return s.filter(n=>!n.endsWith("middleware.ts")&&!n.endsWith("middleware.tsx")).flatMap(n=>{try{let l=m(o(t,n),"utf-8"),i=a(l);return i.length===0?[]:[c(n,`${e}/api`,i)]}catch{return[]}})}export{I as scanApiFiles};
2
2
  //# sourceMappingURL=scan-api.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/vite/codegen/scan-api.ts", "../../../src/vite/codegen/extract-methods.ts", "../../../src/utils/patterns.ts", "../../../src/server/api-router.ts", "../../../src/vite/codegen/routes-dts.ts"],
4
- "sourcesContent": ["import {readFileSync, readdirSync, statSync} from 'node:fs'\nimport {join, relative} from 'node:path'\nimport {extractHttpMethods} from './extract-methods'\nimport {buildRouteEntry} from './routes-dts'\nimport type {RouteEntry} from './routes-dts'\n\nfunction walkDir(dir: string, root: string): string[] {\n const entries: string[] = []\n for (const name of readdirSync(dir)) {\n const full = join(dir, name)\n if (statSync(full).isDirectory()) {\n entries.push(...walkDir(full, root))\n } else if (/\\.(ts|tsx)$/.test(name)) {\n entries.push(relative(root, full).replace(/\\\\/g, '/'))\n }\n }\n return entries\n}\n\nexport function scanApiFiles(appDir: string, projectRoot: string): RouteEntry[] {\n const apiDir = join(projectRoot, appDir, 'api')\n\n let files: string[]\n try {\n files = walkDir(apiDir, projectRoot)\n } catch {\n return []\n }\n\n return files\n .filter(f => !f.endsWith('middleware.ts') && !f.endsWith('middleware.tsx'))\n .flatMap(filePath => {\n try {\n const content = readFileSync(join(projectRoot, filePath), 'utf-8')\n const methods = extractHttpMethods(content)\n if (methods.length === 0) return []\n return [buildRouteEntry(filePath, `${appDir}/api`, methods)]\n } catch {\n return []\n }\n })\n}\n", "const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const\nexport type HttpMethod = (typeof HTTP_METHODS)[number]\n\nconst METHOD_EXPORT_RE = /export\\s+(?:const|async\\s+function|function)\\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\b/g\n\nfunction stripComments(content: string): string {\n return content\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/\\/\\/.*$/gm, '')\n}\n\nexport function extractHttpMethods(content: string): HttpMethod[] {\n const found = new Set<HttpMethod>()\n for (const match of stripComments(content).matchAll(METHOD_EXPORT_RE)) {\n found.add(match[1] as HttpMethod)\n }\n return [...found]\n}\n", "export function routePattern(rel: string): string {\n return rel\n .replace(/\\.(tsx|ts|jsx|js)$/, '')\n .replace(/\\(.*?\\)\\//g, '')\n .replace(/^index$|\\/index$/, '')\n .replace(/\\[([^\\]]+)]/g, ':$1')\n || '/'\n}", "import {routePattern} from \"../utils/patterns\";\n\nexport interface ApiRoute {\n path: string\n key: string\n params: string[]\n regex: RegExp\n}\n\nexport interface ApiMiddleware {\n dir: string\n key: string\n}\n\nexport interface ApiResult {\n routes: ApiRoute[]\n middlewares: ApiMiddleware[]\n}\n\nexport function keyToRoutePattern(key: string, apiDir: string): string {\n const rel = key.slice(apiDir.length + 1).replace(/\\\\/g, '/')\n const pattern = routePattern(rel)\n return pattern === '/' ? '/api' : `/api/${pattern}`.replace('/api//', '/api/')\n}\n\nfunction keyToDir(key: string): string {\n return key.slice(0, key.lastIndexOf('/'))\n}\n\nlet cache: ApiResult | null = null\n\nexport function invalidateApiCache() {\n cache = null\n}\n\nexport function buildRoutes(routeKeys: string[], middlewareKeys: string[], apiDir: string): ApiResult {\n if (cache) return cache\n\n const routes: ApiRoute[] = []\n const middlewares: ApiMiddleware[] = []\n\n for (const key of middlewareKeys) {\n middlewares.push({dir: keyToDir(key), key})\n }\n\n for (const key of routeKeys) {\n const pattern = keyToRoutePattern(key, apiDir)\n const params = [...pattern.matchAll(/:([^/]+)/g)].map(m => m[1])\n const regexStr = pattern\n .replace(/:[^/]+/g, '([^/]+)')\n .replace(/\\//g, '\\\\/')\n routes.push({path: pattern, key, params, regex: new RegExp(`^${regexStr}$`)})\n }\n routes.sort((a, b) => {\n const aScore = (a.path.match(/:/g) || []).length\n const bScore = (b.path.match(/:/g) || []).length\n if (aScore !== bScore) return aScore - bScore\n return b.path.length - a.path.length\n })\n\n cache = {routes, middlewares}\n return cache\n}\n\nexport function collectMiddlewareChain(routeKey: string, middlewares: ApiMiddleware[]): ApiMiddleware[] {\n const routeDir = keyToDir(routeKey)\n\n return middlewares\n .filter(mw => routeDir.startsWith(mw.dir))\n .sort((a, b) => a.dir.split('/').length - b.dir.split('/').length)\n}\n\nexport function matchRoute(\n pathname: string,\n routes: ApiRoute[]\n): {route: ApiRoute; params: Record<string, string>} | null {\n for (const route of routes) {\n const match = pathname.match(route.regex)\n if (match) {\n const params: Record<string, string> = {}\n route.params.forEach((name, i) => {\n params[name] = decodeURIComponent(match[i + 1])\n })\n return {route, params}\n }\n }\n return null\n}\n", "import { keyToRoutePattern } from '../../server/api-router'\nimport type { HttpMethod } from './extract-methods'\n\nexport interface RouteEntry {\n filePath: string\n urlPattern: string\n identifier: string\n methods: HttpMethod[]\n}\n\nexport function filePathToIdentifier(filePath: string, apiDir: string): string {\n return '_api_' + filePath\n .slice(`${apiDir}/`.length)\n .replace(/\\.(ts|tsx)$/, '')\n .replace(/[^a-zA-Z0-9]/g, '_')\n}\n\nexport function buildRouteEntry(filePath: string, apiDir: string, methods: HttpMethod[]): RouteEntry {\n return {\n filePath,\n urlPattern: keyToRoutePattern(filePath, apiDir),\n identifier: filePathToIdentifier(filePath, apiDir),\n methods,\n }\n}\n\nexport function generateRoutesDts(entries: RouteEntry[], apiDir: string): string {\n if (entries.length === 0) {\n return `// auto-generado por devix \u2014 no editar\\ndeclare module '@devlusoft/devix' {\\n interface ApiRoutes {}\\n}\\n`\n }\n\n const imports = entries\n .map(e => {\n const importPath = '../' + e.filePath.replace(/\\.(ts|tsx)$/, '')\n return `import type * as ${e.identifier} from '${importPath}'`\n })\n .join('\\n')\n\n const routeLines = entries.flatMap(e =>\n e.methods.map(m =>\n ` '${m} ${e.urlPattern}': InferRoute<(typeof ${e.identifier})['${m}']>`\n )\n ).join('\\n')\n\n return `// auto-generado por devix \u2014 no editar\n${imports}\n\ntype JsonResponse<T> = Response & { readonly __body: T }\ntype UnwrapJson<T> = T extends JsonResponse<infer U> ? U : never\ntype InferFnReturn<T> = T extends (...args: any[]) => any\n ? UnwrapJson<Awaited<ReturnType<T>>> | Exclude<Awaited<ReturnType<T>>, JsonResponse<any> | null | void | undefined>\n : never\ntype InferRoute<T> =\n T extends { readonly __return?: infer TReturn; readonly __body?: infer TBody }\n ? {\n __body: [TBody] extends [undefined] ? never : Exclude<TBody, undefined>\n __response: InferFnReturn<() => TReturn>\n }\n : InferFnReturn<T>\n\ndeclare module '@devlusoft/devix' {\n interface ApiRoutes {\n${routeLines}\n }\n}\n`\n}\n"],
4
+ "sourcesContent": ["import {readFileSync, readdirSync, statSync} from 'node:fs'\nimport {join, relative} from 'node:path'\nimport {extractHttpMethods} from './extract-methods'\nimport {buildRouteEntry} from './routes-dts'\nimport type {RouteEntry} from './routes-dts'\n\nfunction walkDir(dir: string, root: string): string[] {\n const entries: string[] = []\n for (const name of readdirSync(dir)) {\n const full = join(dir, name)\n if (statSync(full).isDirectory()) {\n entries.push(...walkDir(full, root))\n } else if (/\\.(ts|tsx)$/.test(name)) {\n entries.push(relative(root, full).replace(/\\\\/g, '/'))\n }\n }\n return entries\n}\n\nexport function scanApiFiles(appDir: string, projectRoot: string): RouteEntry[] {\n const apiDir = join(projectRoot, appDir, 'api')\n\n let files: string[]\n try {\n files = walkDir(apiDir, projectRoot)\n } catch {\n return []\n }\n\n return files\n .filter(f => !f.endsWith('middleware.ts') && !f.endsWith('middleware.tsx'))\n .flatMap(filePath => {\n try {\n const content = readFileSync(join(projectRoot, filePath), 'utf-8')\n const methods = extractHttpMethods(content)\n if (methods.length === 0) return []\n return [buildRouteEntry(filePath, `${appDir}/api`, methods)]\n } catch {\n return []\n }\n })\n}\n", "const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const\nexport type HttpMethod = (typeof HTTP_METHODS)[number]\n\nconst METHOD_EXPORT_RE = /export\\s+(?:const|async\\s+function|function)\\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\b/g\n\nfunction stripComments(content: string): string {\n return content\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/\\/\\/.*$/gm, '')\n}\n\nexport function extractHttpMethods(content: string): HttpMethod[] {\n const found = new Set<HttpMethod>()\n for (const match of stripComments(content).matchAll(METHOD_EXPORT_RE)) {\n found.add(match[1] as HttpMethod)\n }\n return [...found]\n}\n", "export function routePattern(rel: string): string {\n return rel\n .replace(/\\.(tsx|ts|jsx|js)$/, '')\n .replace(/\\(.*?\\)\\//g, '')\n .replace(/^index$|\\/index$/, '')\n .replace(/\\[([^\\]]+)]/g, ':$1')\n || '/'\n}", "import {routePattern} from \"../utils/patterns\";\n\nexport interface ApiRoute {\n path: string\n key: string\n params: string[]\n regex: RegExp\n}\n\nexport interface ApiMiddleware {\n dir: string\n key: string\n}\n\nexport interface ApiResult {\n routes: ApiRoute[]\n middlewares: ApiMiddleware[]\n}\n\nexport function keyToRoutePattern(key: string, apiDir: string): string {\n const rel = key.slice(apiDir.length + 1).replace(/\\\\/g, '/')\n const pattern = routePattern(rel)\n return pattern === '/' ? '/api' : `/api/${pattern}`.replace('/api//', '/api/')\n}\n\nfunction keyToDir(key: string): string {\n return key.slice(0, key.lastIndexOf('/'))\n}\n\nexport function buildRoutes(routeKeys: string[], middlewareKeys: string[], apiDir: string): ApiResult {\n const routes: ApiRoute[] = []\n const middlewares: ApiMiddleware[] = []\n\n for (const key of middlewareKeys) {\n middlewares.push({dir: keyToDir(key), key})\n }\n\n for (const key of routeKeys) {\n const pattern = keyToRoutePattern(key, apiDir)\n const params = [...pattern.matchAll(/:([^/]+)/g)].map(m => m[1])\n const regexStr = pattern\n .replace(/:[^/]+/g, '([^/]+)')\n .replace(/\\//g, '\\\\/')\n routes.push({path: pattern, key, params, regex: new RegExp(`^${regexStr}$`)})\n }\n routes.sort((a, b) => {\n const aScore = (a.path.match(/:/g) || []).length\n const bScore = (b.path.match(/:/g) || []).length\n if (aScore !== bScore) return aScore - bScore\n return b.path.length - a.path.length\n })\n\n return {routes, middlewares}\n}\n\nexport function collectMiddlewareChain(routeKey: string, middlewares: ApiMiddleware[]): ApiMiddleware[] {\n const routeDir = keyToDir(routeKey)\n\n return middlewares\n .filter(mw => routeDir.startsWith(mw.dir))\n .sort((a, b) => a.dir.split('/').length - b.dir.split('/').length)\n}\n\nexport function matchRoute(\n pathname: string,\n routes: ApiRoute[]\n): { route: ApiRoute; params: Record<string, string> } | null {\n for (const route of routes) {\n const match = pathname.match(route.regex)\n if (match) {\n const params: Record<string, string> = {}\n route.params.forEach((name, i) => {\n params[name] = decodeURIComponent(match[i + 1])\n })\n return {route, params}\n }\n }\n return null\n}\n", "import { keyToRoutePattern } from '../../server/api-router'\nimport type { HttpMethod } from './extract-methods'\n\nexport interface RouteEntry {\n filePath: string\n urlPattern: string\n identifier: string\n methods: HttpMethod[]\n}\n\nexport function filePathToIdentifier(filePath: string, apiDir: string): string {\n return '_api_' + filePath\n .slice(`${apiDir}/`.length)\n .replace(/\\.(ts|tsx)$/, '')\n .replace(/[^a-zA-Z0-9]/g, '_')\n}\n\nexport function buildRouteEntry(filePath: string, apiDir: string, methods: HttpMethod[]): RouteEntry {\n return {\n filePath,\n urlPattern: keyToRoutePattern(filePath, apiDir),\n identifier: filePathToIdentifier(filePath, apiDir),\n methods,\n }\n}\n\nexport function generateRoutesDts(entries: RouteEntry[], apiDir: string): string {\n if (entries.length === 0) {\n return `// auto-generado por devix \u2014 no editar\\nexport {}\\ndeclare module '@devlusoft/devix' {\\n interface ApiRoutes {}\\n}\\n`\n }\n\n const imports = entries\n .map(e => {\n const importPath = '../' + e.filePath.replace(/\\.(ts|tsx)$/, '')\n return `import type * as ${e.identifier} from '${importPath}'`\n })\n .join('\\n')\n\n const routeLines = entries.flatMap(e =>\n e.methods.map(m =>\n ` '${m} ${e.urlPattern}': InferRoute<(typeof ${e.identifier})['${m}']>`\n )\n ).join('\\n')\n\n return `// auto-generado por devix \u2014 no editar\n${imports}\n\ntype JsonResponse<T, S extends number = number> = Response & { readonly __body: T; readonly __status: S }\ntype Is2xx<S extends number> = [number] extends [S] ? boolean : S extends 200 | 201 | 202 | 203 | 204 | 205 | 206 ? true : false\ntype UnwrapSuccessJson<T> = T extends JsonResponse<infer U, infer S> ? Is2xx<S> extends false ? never : U : never\ntype UnwrapErrorJson<T> = T extends JsonResponse<infer U, infer S> ? Is2xx<S> extends true ? never : U : never\ntype InferFnSuccess<T> = T extends (...args: any[]) => any ? UnwrapSuccessJson<Awaited<ReturnType<T>>> : never\ntype InferFnErrors<T> = T extends (...args: any[]) => any ? UnwrapErrorJson<Awaited<ReturnType<T>>> : never\ntype InferRoute<T> =\n T extends { readonly __return?: infer TReturn; readonly __body?: infer TBody }\n ? {\n __body: [TBody] extends [undefined] ? never : Exclude<TBody, undefined>\n __response: InferFnSuccess<() => TReturn>\n __errors: InferFnErrors<() => TReturn>\n }\n : InferFnSuccess<T>\n\ndeclare module '@devlusoft/devix' {\n interface ApiRoutes {\n${routeLines}\n }\n}\n`\n}\n"],
5
5
  "mappings": "AAAA,OAAQ,gBAAAA,EAAc,eAAAC,EAAa,YAAAC,MAAe,UAClD,OAAQ,QAAAC,EAAM,YAAAC,MAAe,YCE7B,IAAMC,EAAmB,6FAEzB,SAASC,EAAcC,EAAyB,CAC5C,OAAOA,EACF,QAAQ,oBAAqB,EAAE,EAC/B,QAAQ,YAAa,EAAE,CAChC,CAEO,SAASC,EAAmBD,EAA+B,CAC9D,IAAME,EAAQ,IAAI,IAClB,QAAWC,KAASJ,EAAcC,CAAO,EAAE,SAASF,CAAgB,EAChEI,EAAM,IAAIC,EAAM,CAAC,CAAe,EAEpC,MAAO,CAAC,GAAGD,CAAK,CACpB,CCjBO,SAASE,EAAaC,EAAqB,CAC9C,OAAOA,EACE,QAAQ,qBAAsB,EAAE,EAChC,QAAQ,aAAc,EAAE,EACxB,QAAQ,mBAAoB,EAAE,EAC9B,QAAQ,eAAgB,KAAK,GAC/B,GACX,CCYO,SAASC,EAAkBC,EAAaC,EAAwB,CACnE,IAAMC,EAAMF,EAAI,MAAMC,EAAO,OAAS,CAAC,EAAE,QAAQ,MAAO,GAAG,EACrDE,EAAUC,EAAaF,CAAG,EAChC,OAAOC,IAAY,IAAM,OAAS,QAAQA,CAAO,GAAG,QAAQ,SAAU,OAAO,CACjF,CCbO,SAASE,EAAqBC,EAAkBC,EAAwB,CAC3E,MAAO,QAAUD,EACZ,MAAM,GAAGC,CAAM,IAAI,MAAM,EACzB,QAAQ,cAAe,EAAE,EACzB,QAAQ,gBAAiB,GAAG,CACrC,CAEO,SAASC,EAAgBF,EAAkBC,EAAgBE,EAAmC,CACjG,MAAO,CACH,SAAAH,EACA,WAAYI,EAAkBJ,EAAUC,CAAM,EAC9C,WAAYF,EAAqBC,EAAUC,CAAM,EACjD,QAAAE,CACJ,CACJ,CJlBA,SAASE,EAAQC,EAAaC,EAAwB,CAClD,IAAMC,EAAoB,CAAC,EAC3B,QAAWC,KAAQC,EAAYJ,CAAG,EAAG,CACjC,IAAMK,EAAOC,EAAKN,EAAKG,CAAI,EACvBI,EAASF,CAAI,EAAE,YAAY,EAC3BH,EAAQ,KAAK,GAAGH,EAAQM,EAAMJ,CAAI,CAAC,EAC5B,cAAc,KAAKE,CAAI,GAC9BD,EAAQ,KAAKM,EAASP,EAAMI,CAAI,EAAE,QAAQ,MAAO,GAAG,CAAC,CAE7D,CACA,OAAOH,CACX,CAEO,SAASO,EAAaC,EAAgBC,EAAmC,CAC5E,IAAMC,EAASN,EAAKK,EAAaD,EAAQ,KAAK,EAE1CG,EACJ,GAAI,CACAA,EAAQd,EAAQa,EAAQD,CAAW,CACvC,MAAQ,CACJ,MAAO,CAAC,CACZ,CAEA,OAAOE,EACF,OAAOC,GAAK,CAACA,EAAE,SAAS,eAAe,GAAK,CAACA,EAAE,SAAS,gBAAgB,CAAC,EACzE,QAAQC,GAAY,CACjB,GAAI,CACA,IAAMC,EAAUC,EAAaX,EAAKK,EAAaI,CAAQ,EAAG,OAAO,EAC3DG,EAAUC,EAAmBH,CAAO,EAC1C,OAAIE,EAAQ,SAAW,EAAU,CAAC,EAC3B,CAACE,EAAgBL,EAAU,GAAGL,CAAM,OAAQQ,CAAO,CAAC,CAC/D,MAAQ,CACJ,MAAO,CAAC,CACZ,CACJ,CAAC,CACT",
6
6
  "names": ["readFileSync", "readdirSync", "statSync", "join", "relative", "METHOD_EXPORT_RE", "stripComments", "content", "extractHttpMethods", "found", "match", "routePattern", "rel", "keyToRoutePattern", "key", "apiDir", "rel", "pattern", "routePattern", "filePathToIdentifier", "filePath", "apiDir", "buildRouteEntry", "methods", "keyToRoutePattern", "walkDir", "dir", "root", "entries", "name", "readdirSync", "full", "join", "statSync", "relative", "scanApiFiles", "appDir", "projectRoot", "apiDir", "files", "f", "filePath", "content", "readFileSync", "methods", "extractHttpMethods", "buildRouteEntry"]
7
7
  }
@@ -0,0 +1,9 @@
1
+ interface ServerEntryOptions {
2
+ routesPath: string;
3
+ envPath: string;
4
+ honoServerPath: string;
5
+ honoServerStaticPath: string;
6
+ honoPath: string;
7
+ }
8
+ export declare function generateServerEntry({ routesPath, envPath, honoServerPath, honoServerStaticPath, honoPath }: ServerEntryOptions): string;
9
+ export {};
@@ -0,0 +1,73 @@
1
+ function i({routesPath:e,envPath:t,honoServerPath:o,honoServerStaticPath:r,honoPath:n}){return`
2
+ import { readFileSync } from 'node:fs'
3
+ import { serve } from '${o}'
4
+ import { serveStatic } from '${r}'
5
+ import { Hono } from '${n}'
6
+ import { resolve, join, dirname } from 'node:path'
7
+ import { pathToFileURL } from 'node:url'
8
+ import { registerApiRoutes, registerSsrRoute } from '${e}'
9
+ import { loadDotenv } from '${t}'
10
+
11
+ loadDotenv('production')
12
+
13
+ const __dir = dirname(process.argv[1])
14
+
15
+ let renderModule, apiModule, manifest, runtimeConfig
16
+
17
+ try {
18
+ runtimeConfig = JSON.parse(readFileSync(resolve(__dir, '../devix.config.json'), 'utf-8'))
19
+ if (runtimeConfig.output !== 'static') {
20
+ renderModule = await import(pathToFileURL(resolve(__dir, 'render.js')).href)
21
+ apiModule = await import(pathToFileURL(resolve(__dir, 'api.js')).href)
22
+ }
23
+ manifest = JSON.parse(readFileSync(resolve(__dir, '../client/.vite/manifest.json'), 'utf-8'))
24
+ } catch {
25
+ console.error('[devix] Build not found. Run "devix build" first.')
26
+ process.exit(1)
27
+ }
28
+
29
+ const port = Number(process.env.PORT) || runtimeConfig.port || 3000
30
+ const host = typeof runtimeConfig.host === 'string'
31
+ ? runtimeConfig.host
32
+ : runtimeConfig.host ? '0.0.0.0' : (process.env.HOST || '0.0.0.0')
33
+
34
+ const clientRoot = resolve(__dir, '../client')
35
+ const app = new Hono()
36
+
37
+ if (runtimeConfig.output === 'static') {
38
+ app.get('/_data/*', (c) => {
39
+ const pathname = c.req.path.replace(/^\\/_data/, '') || '/'
40
+ const filePath = pathname === '/'
41
+ ? join(clientRoot, '_data/index.json')
42
+ : join(clientRoot, '_data', pathname + '.json')
43
+ try {
44
+ return c.json(JSON.parse(readFileSync(filePath, 'utf-8')))
45
+ } catch {
46
+ return c.json({ error: 'not found' }, 404)
47
+ }
48
+ })
49
+ }
50
+
51
+ app.use('/*', serveStatic({
52
+ root: clientRoot,
53
+ onFound: (_path, c) => {
54
+ c.header('Cache-Control', _path.includes('/assets/')
55
+ ? 'public, immutable, max-age=31536000'
56
+ : 'no-cache')
57
+ }
58
+ }))
59
+
60
+ if (runtimeConfig.output === 'static') {
61
+ console.log('[devix] Static mode \u2014 serving pre-generated files from dist/client')
62
+ } else {
63
+ registerApiRoutes(app, { renderModule, apiModule, manifest })
64
+ registerSsrRoute(app, { renderModule, apiModule, manifest, loaderTimeout: runtimeConfig.loaderTimeout })
65
+ }
66
+
67
+ const server = serve({ fetch: app.fetch, port, hostname: host }, (info) =>
68
+ console.log(\`http://\${info.address}:\${info.port}\`))
69
+
70
+ process.on('SIGTERM', () => server.close())
71
+ process.on('SIGINT', () => server.close())
72
+ `}export{i as generateServerEntry};
73
+ //# sourceMappingURL=server-entry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/vite/codegen/server-entry.ts"],
4
+ "sourcesContent": ["interface ServerEntryOptions {\n routesPath: string\n envPath: string\n honoServerPath: string\n honoServerStaticPath: string\n honoPath: string\n}\n\nexport function generateServerEntry({ routesPath, envPath, honoServerPath, honoServerStaticPath, honoPath }: ServerEntryOptions): string {\n return `\nimport { readFileSync } from 'node:fs'\n import { serve } from '${honoServerPath}'\n import { serveStatic } from '${honoServerStaticPath}'\n import { Hono } from '${honoPath}'\n import { resolve, join, dirname } from 'node:path'\n import { pathToFileURL } from 'node:url'\n import { registerApiRoutes, registerSsrRoute } from '${routesPath}' \n import { loadDotenv } from '${envPath}'\n \n loadDotenv('production')\n \n const __dir = dirname(process.argv[1])\n\n let renderModule, apiModule, manifest, runtimeConfig \n \n try { \n runtimeConfig = JSON.parse(readFileSync(resolve(__dir, '../devix.config.json'), 'utf-8'))\n if (runtimeConfig.output !== 'static') { \n renderModule = await import(pathToFileURL(resolve(__dir, 'render.js')).href)\n apiModule = await import(pathToFileURL(resolve(__dir, 'api.js')).href) \n } \n manifest = JSON.parse(readFileSync(resolve(__dir, '../client/.vite/manifest.json'), 'utf-8')) \n } catch { \n console.error('[devix] Build not found. Run \"devix build\" first.')\n process.exit(1) \n } \n \n const port = Number(process.env.PORT) || runtimeConfig.port || 3000 \n const host = typeof runtimeConfig.host === 'string'\n ? runtimeConfig.host \n : runtimeConfig.host ? '0.0.0.0' : (process.env.HOST || '0.0.0.0') \n \n const clientRoot = resolve(__dir, '../client') \n const app = new Hono()\n \n if (runtimeConfig.output === 'static') {\n app.get('/_data/*', (c) => {\n const pathname = c.req.path.replace(/^\\\\/_data/, '') || '/' \n const filePath = pathname === '/' \n ? join(clientRoot, '_data/index.json') \n : join(clientRoot, '_data', pathname + '.json') \n try { \n return c.json(JSON.parse(readFileSync(filePath, 'utf-8')))\n } catch { \n return c.json({ error: 'not found' }, 404)\n } \n }) \n }\n\n app.use('/*', serveStatic({ \n root: clientRoot,\n onFound: (_path, c) => { \n c.header('Cache-Control', _path.includes('/assets/') \n ? 'public, immutable, max-age=31536000'\n : 'no-cache') \n } \n })) \n \n if (runtimeConfig.output === 'static') {\n console.log('[devix] Static mode \u2014 serving pre-generated files from dist/client')\n } else { \n registerApiRoutes(app, { renderModule, apiModule, manifest })\n registerSsrRoute(app, { renderModule, apiModule, manifest, loaderTimeout: runtimeConfig.loaderTimeout })\n } \n \n const server = serve({ fetch: app.fetch, port, hostname: host }, (info) => \n console.log(\\`http://\\${info.address}:\\${info.port}\\`))\n\nprocess.on('SIGTERM', () => server.close())\nprocess.on('SIGINT', () => server.close())\n`\n}"],
5
+ "mappings": "AAQO,SAASA,EAAoB,CAAE,WAAAC,EAAY,QAAAC,EAAS,eAAAC,EAAgB,qBAAAC,EAAsB,SAAAC,CAAS,EAA+B,CACrI,MAAO;AAAA;AAAA,2BAEgBF,CAAc;AAAA,iCACRC,CAAoB;AAAA,0BAC3BC,CAAQ;AAAA;AAAA;AAAA,yDAGuBJ,CAAU;AAAA,gCACnCC,CAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAgEvC",
6
+ "names": ["generateServerEntry", "routesPath", "envPath", "honoServerPath", "honoServerStaticPath", "honoPath"]
7
+ }
@@ -1,5 +1,5 @@
1
- import{mergeConfig as ot}from"vite";import it from"@vitejs/plugin-react";import{fileURLToPath as at}from"node:url";import{dirname as st,resolve as u}from"node:path";function b({cssUrls:t}){return`
2
- ${t.map(n=>`import '${n}'`).join(`
1
+ import{mergeConfig as Te}from"vite";import Se from"@vitejs/plugin-react";import{fileURLToPath as we}from"node:url";import{dirname as $e,relative as De,resolve as u}from"node:path";import{createRequire as Ae}from"node:module";function L({cssUrls:e}){return`
2
+ ${e.map(r=>`import '${r}'`).join(`
3
3
  `)}
4
4
  import "@vitejs/plugin-react/preamble"
5
5
  import React from "react"
@@ -19,7 +19,20 @@ if (!window.__DEVIX__) {
19
19
 
20
20
  const matched = matchClientRoute(window.location.pathname)
21
21
 
22
- if (matched) {
22
+ if (window.__LOADER_ERROR__) {
23
+ const {statusCode, message, data} = window.__LOADER_ERROR__
24
+ const ErrorPage = await loadErrorPage() ?? getDefaultErrorPage()
25
+ createRoot(root).render(
26
+ React.createElement(RouterProvider, {
27
+ clientEntry,
28
+ initialData: null,
29
+ initialParams: {},
30
+ initialPage: () => null,
31
+ initialError: {statusCode, message, data},
32
+ initialErrorPage: ErrorPage,
33
+ })
34
+ )
35
+ } else if (matched) {
23
36
  const [pageMod, ...layoutMods] = await Promise.all([
24
37
  matched.load(),
25
38
  ...matched.loadLayouts.map(l => l()),
@@ -62,12 +75,12 @@ if (!window.__DEVIX__) {
62
75
  )
63
76
  }
64
77
  }
65
- `}function I({pagesDir:t,matcherPath:e}){return`
78
+ `}function j({pagesDir:e,matcherPath:t}){return`
66
79
  import React from 'react'
67
- import { createMatcher } from '${e}'
68
- const pageFiles = import.meta.glob(['/${t}/**/*.tsx', '!**/error.tsx', '!**/layout.tsx'])
69
- const layoutFiles = import.meta.glob('/${t}/**/layout.tsx')
70
- const errorFiles = import.meta.glob('/${t}/**/error.tsx')
80
+ import { createMatcher } from '${t}'
81
+ const pageFiles = import.meta.glob(['/${e}/**/*.tsx', '!**/error.tsx', '!**/layout.tsx'])
82
+ const layoutFiles = import.meta.glob('/${e}/**/layout.tsx')
83
+ const errorFiles = import.meta.glob('/${e}/**/error.tsx')
71
84
 
72
85
  export const matchClientRoute = createMatcher(pageFiles, layoutFiles)
73
86
 
@@ -90,16 +103,16 @@ export function getDefaultErrorPage() {
90
103
  )
91
104
  }
92
105
  }
93
- `}function L({pagesDir:t,renderPath:e}){return`
94
- import { render as _render, runLoader as _runLoader, getStaticRoutes as _getStaticRoutes } from '${e}'
106
+ `}function U({pagesDir:e,renderPath:t}){return`
107
+ import { render as _render, runLoader as _runLoader, getStaticRoutes as _getStaticRoutes } from '${t}'
95
108
 
96
- const _pages = import.meta.glob(['/${t}/**/*.tsx', '!**/error.tsx', '!**/layout.tsx'])
97
- const _layouts = import.meta.glob('/${t}/**/layout.tsx')
109
+ const _pages = import.meta.glob(['/${e}/**/*.tsx', '!**/error.tsx', '!**/layout.tsx'])
110
+ const _layouts = import.meta.glob('/${e}/**/layout.tsx')
98
111
 
99
112
  const _glob = {
100
113
  pages: _pages,
101
114
  layouts: _layouts,
102
- pagesDir: '/${t}',
115
+ pagesDir: '/${e}',
103
116
  }
104
117
 
105
118
  export function render(url, request, options) {
@@ -113,49 +126,135 @@ export function runLoader(url, request, options) {
113
126
  export function getStaticRoutes() {
114
127
  return _getStaticRoutes(_glob)
115
128
  }
116
- `}function M({apiPath:t,appDir:e}){return`
117
- import { handleApiRequest as _handleApiRequest } from '${t}'
129
+ `}function H({apiPath:e,appDir:t}){return`
130
+ import { handleApiRequest as _handleApiRequest } from '${e}'
118
131
 
119
- const _routes = import.meta.glob(['/${e}/api/**/*.ts', '!**/middleware.ts'])
120
- const _middlewares = import.meta.glob('/${e}/api/**/middleware.ts')
132
+ const _routes = import.meta.glob(['/${t}/api/**/*.ts', '!**/middleware.ts'])
133
+ const _middlewares = import.meta.glob('/${t}/api/**/middleware.ts')
121
134
 
122
135
  const _glob = {
123
136
  routes: _routes,
124
137
  middlewares: _middlewares,
125
- apiDir: '/${e}/api',
138
+ apiDir: '/${t}/api',
126
139
  }
127
140
 
128
141
  export function handleApiRequest(url, request) {
129
142
  return _handleApiRequest(url, request, _glob)
130
143
  }
131
- `}function y(t){return t.replace(/\.(tsx|ts|jsx|js)$/,"").replace(/\(.*?\)\//g,"").replace(/^index$|\/index$/,"").replace(/\[([^\]]+)]/g,":$1")||"/"}var B=null;function R(){B=null}function O(t,e){let n=t.slice(e.length+1).replace(/\\/g,"/"),i=y(n);return i==="/"?"/api":`/api/${i}`.replace("/api//","/api/")}var J=null;function E(){J=null}function U(){return`
144
+ `}function N(){return`
132
145
  export {RouterContext} from '@devlusoft/devix/runtime/context'
133
- `}import{readFileSync as Y,readdirSync as Z,statSync as K}from"node:fs";import{join as _,relative as Q}from"node:path";var X=/export\s+(?:const|async\s+function|function)\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/g;function z(t){return t.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/.*$/gm,"")}function H(t){let e=new Set;for(let n of z(t).matchAll(X))e.add(n[1]);return[...e]}function G(t,e){return"_api_"+t.slice(`${e}/`.length).replace(/\.(ts|tsx)$/,"").replace(/[^a-zA-Z0-9]/g,"_")}function k(t,e,n){return{filePath:t,urlPattern:O(t,e),identifier:G(t,e),methods:n}}function P(t,e){if(t.length===0)return`// auto-generado por devix \u2014 no editar
146
+ `}import{readFileSync as ce,readdirSync as le,statSync as ue}from"node:fs";import{join as D,relative as pe}from"node:path";var ie=/export\s+(?:const|async\s+function|function)\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/g;function se(e){return e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/.*$/gm,"")}function k(e){let t=new Set;for(let r of se(e).matchAll(ie))t.add(r[1]);return[...t]}function V(e){return e.replace(/\.(tsx|ts|jsx|js)$/,"").replace(/\(.*?\)\//g,"").replace(/^index$|\/index$/,"").replace(/\[([^\]]+)]/g,":$1")||"/"}function q(e,t){let r=e.slice(t.length+1).replace(/\\/g,"/"),n=V(r);return n==="/"?"/api":`/api/${n}`.replace("/api//","/api/")}function ae(e,t){return"_api_"+e.slice(`${t}/`.length).replace(/\.(ts|tsx)$/,"").replace(/[^a-zA-Z0-9]/g,"_")}function W(e,t,r){return{filePath:e,urlPattern:q(e,t),identifier:ae(e,t),methods:r}}function $(e,t){if(e.length===0)return`// auto-generado por devix \u2014 no editar
147
+ export {}
134
148
  declare module '@devlusoft/devix' {
135
149
  interface ApiRoutes {}
136
150
  }
137
- `;let n=t.map(o=>{let p="../"+o.filePath.replace(/\.(ts|tsx)$/,"");return`import type * as ${o.identifier} from '${p}'`}).join(`
138
- `),i=t.flatMap(o=>o.methods.map(p=>` '${p} ${o.urlPattern}': InferRoute<(typeof ${o.identifier})['${p}']>`)).join(`
151
+ `;let r=e.map(o=>{let a="../"+o.filePath.replace(/\.(ts|tsx)$/,"");return`import type * as ${o.identifier} from '${a}'`}).join(`
152
+ `),n=e.flatMap(o=>o.methods.map(a=>` '${a} ${o.urlPattern}': InferRoute<(typeof ${o.identifier})['${a}']>`)).join(`
139
153
  `);return`// auto-generado por devix \u2014 no editar
140
- ${n}
154
+ ${r}
141
155
 
142
- type JsonResponse<T> = Response & { readonly __body: T }
143
- type UnwrapJson<T> = T extends JsonResponse<infer U> ? U : never
144
- type InferFnReturn<T> = T extends (...args: any[]) => any
145
- ? UnwrapJson<Awaited<ReturnType<T>>> | Exclude<Awaited<ReturnType<T>>, JsonResponse<any> | null | void | undefined>
146
- : never
156
+ type JsonResponse<T, S extends number = number> = Response & { readonly __body: T; readonly __status: S }
157
+ type Is2xx<S extends number> = [number] extends [S] ? boolean : S extends 200 | 201 | 202 | 203 | 204 | 205 | 206 ? true : false
158
+ type UnwrapSuccessJson<T> = T extends JsonResponse<infer U, infer S> ? Is2xx<S> extends false ? never : U : never
159
+ type UnwrapErrorJson<T> = T extends JsonResponse<infer U, infer S> ? Is2xx<S> extends true ? never : U : never
160
+ type InferFnSuccess<T> = T extends (...args: any[]) => any ? UnwrapSuccessJson<Awaited<ReturnType<T>>> : never
161
+ type InferFnErrors<T> = T extends (...args: any[]) => any ? UnwrapErrorJson<Awaited<ReturnType<T>>> : never
147
162
  type InferRoute<T> =
148
163
  T extends { readonly __return?: infer TReturn; readonly __body?: infer TBody }
149
164
  ? {
150
165
  __body: [TBody] extends [undefined] ? never : Exclude<TBody, undefined>
151
- __response: InferFnReturn<() => TReturn>
166
+ __response: InferFnSuccess<() => TReturn>
167
+ __errors: InferFnErrors<() => TReturn>
152
168
  }
153
- : InferFnReturn<T>
169
+ : InferFnSuccess<T>
154
170
 
155
171
  declare module '@devlusoft/devix' {
156
172
  interface ApiRoutes {
157
- ${i}
173
+ ${n}
158
174
  }
159
175
  }
160
- `}function F(t,e){let n=[];for(let i of Z(t)){let o=_(t,i);K(o).isDirectory()?n.push(...F(o,e)):/\.(ts|tsx)$/.test(i)&&n.push(Q(e,o).replace(/\\/g,"/"))}return n}function v(t,e){let n=_(e,t,"api"),i;try{i=F(n,e)}catch{return[]}return i.filter(o=>!o.endsWith("middleware.ts")&&!o.endsWith("middleware.tsx")).flatMap(o=>{try{let p=Y(_(e,o),"utf-8"),m=H(p);return m.length===0?[]:[k(o,`${t}/api`,m)]}catch{return[]}})}import{mkdirSync as tt,readFileSync as et,writeFileSync as rt,existsSync as nt}from"node:fs";import{join as j}from"node:path";function T(t,e){let n=j(e,".devix"),i=j(n,"routes.d.ts");return tt(n,{recursive:!0}),nt(i)&&et(i,"utf-8")===t?!1:(rt(i,t,"utf-8"),!0)}import{parseSync as ct}from"oxc-parser";var w=st(at(import.meta.url)),$="virtual:devix/entry-client",A="virtual:devix/client-routes",D="virtual:devix/render",C="virtual:devix/api",S="virtual:devix/context",q=new Set(["loader","guard","generateStaticParams","headers"]);function Xt(t){let e=t.appDir??"app",n=`${e}/pages`,i=(t.css??[]).map(r=>r.startsWith("/")?r:`/${r.replace(/^\.\//,"")}`),o=u(w,"../server/render.js").replace(/\\/g,"/"),p=u(w,"../server/api.js").replace(/\\/g,"/"),m=u(w,"../runtime/client-router.js").replace(/\\/g,"/"),V={name:"devix",enforce:"pre",resolveId(r){if(r===$)return`\0${$}`;if(r===A)return`\0${A}`;if(r===D)return`\0${D}`;if(r===C)return`\0${C}`;if(r===S)return`\0${S}`},load(r){if(r===`\0${$}`)return b({cssUrls:i});if(r===`\0${A}`)return I({pagesDir:n,matcherPath:m});if(r===`\0${D}`)return L({pagesDir:n,renderPath:o});if(r===`\0${C}`)return M({apiPath:p,appDir:e});if(r===`\0${S}`)return U()},transform(r,c,d){if(d?.ssr)return;let a=u(process.cwd(),n);if(!c.startsWith(a))return;let N=ct(c,r,{sourceType:"module"}),g=[];for(let s of N.program.body){if(s.type!=="ExportNamedDeclaration"||!s.declaration)continue;let l=s.declaration;if(l.type==="FunctionDeclaration"&&l.id&&q.has(l.id.name)&&g.push({start:s.start,end:s.end,name:l.id.name}),l.type==="VariableDeclaration"){let h=new Set;for(let x of l.declarations)x.id.type==="Identifier"&&q.has(x.id.name)&&(h.has(s.start)||(h.add(s.start),g.push({start:s.start,end:s.end,name:x.id.name})))}}if(g.length===0)return;g.sort((s,l)=>l.start-s.start);let f=r;for(let{start:s,end:l,name:h}of g)f=f.slice(0,s)+`export const ${h} = undefined`+f.slice(l);return{code:f,map:null}},buildStart(){let r=process.cwd(),c=v(e,r);T(P(c,`${e}/api`),r)},configureServer(r){let c=process.cwd(),d=()=>{let a=v(e,c);T(P(a,`${e}/api`),c)};r.watcher.add(u(c,"devix.config.ts")),r.watcher.on("change",a=>{a===u(c,"devix.config.ts")&&(console.log("[devix] Config changed, restarting..."),process.exit(75))}),r.watcher.on("add",a=>{a.startsWith(u(c,n))&&R(),a.includes(`${e}/api`)&&(E(),d())}),r.watcher.on("unlink",a=>{a.startsWith(u(c,n))&&R(),a.includes(`${e}/api`)&&(E(),d())}),r.watcher.on("change",a=>{a.includes(`${e}/api`)&&!a.endsWith("middleware.ts")&&d()})}},W={plugins:[it(),V],publicDir:u(process.cwd(),t.publicDir??"public"),ssr:{noExternal:["@devlusoft/devix"]},...t.envPrefix?{envPrefix:t.envPrefix}:{}};return ot(W,t.vite??{})}export{Xt as devix};
176
+ `}function B(e,t){let r=[];for(let n of le(e)){let o=D(e,n);ue(o).isDirectory()?r.push(...B(o,t)):/\.(ts|tsx)$/.test(n)&&r.push(pe(t,o).replace(/\\/g,"/"))}return r}function A(e,t){let r=D(t,e,"api"),n;try{n=B(r,t)}catch{return[]}return n.filter(o=>!o.endsWith("middleware.ts")&&!o.endsWith("middleware.tsx")).flatMap(o=>{try{let a=ce(D(t,o),"utf-8"),m=k(a);return m.length===0?[]:[W(o,`${e}/api`,m)]}catch{return[]}})}import{mkdirSync as de,readFileSync as me,writeFileSync as fe,existsSync as ge}from"node:fs";import{join as J}from"node:path";function b(e,t){let r=J(t,".devix"),n=J(r,"routes.d.ts");return de(r,{recursive:!0}),ge(n)&&me(n,"utf-8")===e?!1:(fe(n,e,"utf-8"),!0)}import{parseSync as be}from"oxc-parser";function G({routesPath:e,envPath:t,honoServerPath:r,honoServerStaticPath:n,honoPath:o}){return`
177
+ import { readFileSync } from 'node:fs'
178
+ import { serve } from '${r}'
179
+ import { serveStatic } from '${n}'
180
+ import { Hono } from '${o}'
181
+ import { resolve, join, dirname } from 'node:path'
182
+ import { pathToFileURL } from 'node:url'
183
+ import { registerApiRoutes, registerSsrRoute } from '${e}'
184
+ import { loadDotenv } from '${t}'
185
+
186
+ loadDotenv('production')
187
+
188
+ const __dir = dirname(process.argv[1])
189
+
190
+ let renderModule, apiModule, manifest, runtimeConfig
191
+
192
+ try {
193
+ runtimeConfig = JSON.parse(readFileSync(resolve(__dir, '../devix.config.json'), 'utf-8'))
194
+ if (runtimeConfig.output !== 'static') {
195
+ renderModule = await import(pathToFileURL(resolve(__dir, 'render.js')).href)
196
+ apiModule = await import(pathToFileURL(resolve(__dir, 'api.js')).href)
197
+ }
198
+ manifest = JSON.parse(readFileSync(resolve(__dir, '../client/.vite/manifest.json'), 'utf-8'))
199
+ } catch {
200
+ console.error('[devix] Build not found. Run "devix build" first.')
201
+ process.exit(1)
202
+ }
203
+
204
+ const port = Number(process.env.PORT) || runtimeConfig.port || 3000
205
+ const host = typeof runtimeConfig.host === 'string'
206
+ ? runtimeConfig.host
207
+ : runtimeConfig.host ? '0.0.0.0' : (process.env.HOST || '0.0.0.0')
208
+
209
+ const clientRoot = resolve(__dir, '../client')
210
+ const app = new Hono()
211
+
212
+ if (runtimeConfig.output === 'static') {
213
+ app.get('/_data/*', (c) => {
214
+ const pathname = c.req.path.replace(/^\\/_data/, '') || '/'
215
+ const filePath = pathname === '/'
216
+ ? join(clientRoot, '_data/index.json')
217
+ : join(clientRoot, '_data', pathname + '.json')
218
+ try {
219
+ return c.json(JSON.parse(readFileSync(filePath, 'utf-8')))
220
+ } catch {
221
+ return c.json({ error: 'not found' }, 404)
222
+ }
223
+ })
224
+ }
225
+
226
+ app.use('/*', serveStatic({
227
+ root: clientRoot,
228
+ onFound: (_path, c) => {
229
+ c.header('Cache-Control', _path.includes('/assets/')
230
+ ? 'public, immutable, max-age=31536000'
231
+ : 'no-cache')
232
+ }
233
+ }))
234
+
235
+ if (runtimeConfig.output === 'static') {
236
+ console.log('[devix] Static mode \u2014 serving pre-generated files from dist/client')
237
+ } else {
238
+ registerApiRoutes(app, { renderModule, apiModule, manifest })
239
+ registerSsrRoute(app, { renderModule, apiModule, manifest, loaderTimeout: runtimeConfig.loaderTimeout })
240
+ }
241
+
242
+ const server = serve({ fetch: app.fetch, port, hostname: host }, (info) =>
243
+ console.log(\`http://\${info.address}:\${info.port}\`))
244
+
245
+ process.on('SIGTERM', () => server.close())
246
+ process.on('SIGINT', () => server.close())
247
+ `}import{existsSync as Y,mkdirSync as he,readdirSync as xe,readFileSync as X,rmSync as ye,statSync as Re,writeFileSync as ve}from"node:fs";import{join as f,relative as z}from"node:path";import{parseSync as _e}from"oxc-parser";function Z(e,t){let r=[];for(let n of xe(e)){let o=f(e,n);Re(o).isDirectory()?r.push(...Z(o,t)):/\.(ts|tsx)$/.test(n)&&n!=="layout.tsx"&&n!=="error.tsx"&&r.push(z(t,o).replace(/\\/g,"/"))}return r}function Ee(e,t){let r=_e(t,e,{sourceType:"module"});for(let n of r.program.body){if(n.type!=="ExportNamedDeclaration")continue;let o=n.declaration;if(o?.type==="FunctionDeclaration"&&o.id?.name==="loader")return!0;if(o?.type==="VariableDeclaration"){for(let a of o.declarations)if(a.id.type==="Identifier"&&a.id.name==="loader")return!0}for(let a of n.specifiers??[])if(a.exported.type==="Identifier"&&a.exported.name==="loader")return!0}return!1}function Pe(e,t){return t?`// auto-generado por devix \u2014 no editar
248
+ import type { loader } from "${e}"
249
+ import type { Redirect } from "@devlusoft/devix"
250
+
251
+ export type PageData = Exclude<
252
+ Awaited<ReturnType<NonNullable<typeof loader>>>,
253
+ Redirect | void | undefined
254
+ >
255
+ export type PageParams = NonNullable<Parameters<typeof loader>[0]>["params"]
256
+ `:`// auto-generado por devix - no editar
257
+ export type PageData = undefined
258
+ export type PageParams = Record<string, string>
259
+ `}function P(e,t){let r=f(t,e),n=X(r,"utf-8"),o=Ee(n,r),a=f(t,".devix","pages",e.replace(/\.(tsx?|jsx?)$/,"")),m=f(a,"$types.d.ts"),T=r.replace(/\.(tsx?|jsx?)$/,""),S=z(a,T).replace(/\\/g,"/"),g=Pe(S,o);Y(m)&&X(m,"utf-8")===g||(he(a,{recursive:!0}),ve(m,g,"utf-8"))}function K(e,t){let r=f(t,".devix","pages",e.replace(/\.(tsx?|jsx?)$/,"")),n=f(r,"$types.d.ts");Y(n)&&ye(n)}function C(e,t){let r=f(t,e,"pages"),n;try{n=Z(r,t)}catch{return}for(let o of n)try{P(o,t)}catch{}}var R=$e(we(import.meta.url)),I="virtual:devix/entry-client",M="virtual:devix/client-routes",v="virtual:devix/render",_="virtual:devix/api",O="virtual:devix/context",F="virtual:devix/server-entry",Q=new Set(["loader","guard","generateStaticParams","headers"]);function Rt(e){let t=e.appDir??"app",r=`${t}/pages`,n=(e.css??[]).map(i=>i.startsWith("/")?i:`/${i.replace(/^\.\//,"")}`),o=u(R,"../server/render.js").replace(/\\/g,"/"),a=u(R,"../server/api.js").replace(/\\/g,"/"),m=u(R,"../runtime/client-router.js").replace(/\\/g,"/"),T=u(R,"../server/routes.js").replace(/\\/g,"/"),S=u(R,"../utils/env.js").replace(/\\/g,"/"),g=Ae(import.meta.url),ee=g.resolve("@hono/node-server").replace(/\\/g,"/"),te=g.resolve("@hono/node-server/serve-static").replace(/\\/g,"/"),re=g.resolve("hono").replace(/\\/g,"/"),ne={name:"devix",enforce:"pre",resolveId(i){if(i===I)return`\0${I}`;if(i===M)return`\0${M}`;if(i===v)return`\0${v}`;if(i===_)return`\0${_}`;if(i===O)return`\0${O}`;if(i===F)return`\0${F}`},load(i){if(i===`\0${I}`)return L({cssUrls:n});if(i===`\0${M}`)return j({pagesDir:r,matcherPath:m});if(i===`\0${v}`)return U({pagesDir:r,renderPath:o});if(i===`\0${_}`)return H({apiPath:a,appDir:t});if(i===`\0${O}`)return N();if(i===`\0${F}`)return G({routesPath:T,envPath:S,honoServerPath:ee,honoServerStaticPath:te,honoPath:re})},transform(i,c,h){if(h?.ssr)return;let x=u(process.cwd(),r);if(!c.startsWith(x))return;let y=be(c,i,{sourceType:"module"}),p=[];for(let l of y.program.body){if(l.type!=="ExportNamedDeclaration"||!l.declaration)continue;let d=l.declaration;if(d.type==="FunctionDeclaration"&&d.id&&Q.has(d.id.name)&&p.push({start:l.start,end:l.end,name:d.id.name}),d.type==="VariableDeclaration"){let E=new Set;for(let w of d.declarations)w.id.type==="Identifier"&&Q.has(w.id.name)&&(E.has(l.start)||(E.add(l.start),p.push({start:l.start,end:l.end,name:w.id.name})))}}if(p.length===0)return;p.sort((l,d)=>d.start-l.start);let s=i;for(let{start:l,end:d,name:E}of p)s=s.slice(0,l)+`export const ${E} = undefined`+s.slice(d);return{code:s,map:null}},buildStart(){let i=process.cwd(),c=A(t,i);b($(c,`${t}/api`),i),C(t,i)},configureServer(i){let c=process.cwd();C(t,c);let h=()=>{let s=A(t,c);b($(s,`${t}/api`),c)},x=s=>s.startsWith(u(c,r))&&!s.endsWith("layout.tsx")&&!s.endsWith("error.tsx"),y=s=>De(c,s).replace(/\\/g,"/"),p=s=>{let l=i.moduleGraph.getModuleById(`\0${s}`);l&&i.moduleGraph.invalidateModule(l)};i.watcher.add(u(c,"devix.config.ts")),i.watcher.on("change",s=>{s===u(c,"devix.config.ts")&&(console.log("[devix] Config changed, restarting..."),process.exit(75))}),i.watcher.on("add",s=>{s.startsWith(u(c,r))&&p(v),x(s)&&P(y(s),c),s.includes(`${t}/api`)&&(p(_),h())}),i.watcher.on("unlink",s=>{s.startsWith(u(c,r))&&p(v),x(s)&&K(y(s),c),s.includes(`${t}/api`)&&(p(_),h())}),i.watcher.on("change",s=>{x(s)&&P(y(s),c),s.includes(`${t}/api`)&&!s.endsWith("middleware.ts")&&h()})}},oe={plugins:[Se(),ne],publicDir:u(process.cwd(),e.publicDir??"public"),ssr:{noExternal:["@devlusoft/devix"]},...e.envPrefix?{envPrefix:e.envPrefix}:{}};return Te(oe,e.vite??{})}export{Rt as devix};
161
260
  //# sourceMappingURL=index.js.map