create-jen-app 1.2.3 → 1.2.4

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 (355) hide show
  1. package/dist/colors.js +0 -17
  2. package/dist/create.js +5 -17
  3. package/dist/generator.js +14 -31
  4. package/dist/index.js +6 -1
  5. package/package.json +1 -1
  6. package/templates/ssr-isr/README.md +77 -0
  7. package/templates/ssr-isr/build.js +118 -0
  8. package/templates/ssr-isr/jen.config.ts +109 -0
  9. package/templates/ssr-isr/jenjs.d.ts +22 -0
  10. package/templates/ssr-isr/lib/api/(hello).js +9 -0
  11. package/templates/ssr-isr/lib/auth/cookie-utils.js +79 -0
  12. package/templates/ssr-isr/lib/auth/index.js +2 -0
  13. package/templates/ssr-isr/lib/auth/jwt.js +57 -0
  14. package/templates/ssr-isr/lib/auth/session.js +92 -0
  15. package/templates/ssr-isr/lib/build/asset-hashing.d.ts +10 -0
  16. package/templates/ssr-isr/lib/build/asset-hashing.js +25 -0
  17. package/templates/ssr-isr/lib/build/asset-manifest.d.ts +11 -0
  18. package/templates/ssr-isr/lib/build/asset-manifest.js +21 -0
  19. package/templates/{static → ssr-isr}/lib/build/build.d.ts +1 -1
  20. package/templates/ssr-isr/lib/build/build.js +141 -0
  21. package/templates/{static → ssr-isr}/lib/build/island-hydration.d.ts +8 -5
  22. package/templates/ssr-isr/lib/build/island-hydration.js +44 -0
  23. package/templates/ssr-isr/lib/build/minifier.d.ts +20 -0
  24. package/templates/ssr-isr/lib/build/minifier.js +46 -0
  25. package/templates/ssr-isr/lib/build/page-renderer.d.ts +17 -0
  26. package/templates/ssr-isr/lib/build/page-renderer.js +28 -0
  27. package/templates/ssr-isr/lib/build/production-build.d.ts +10 -0
  28. package/templates/ssr-isr/lib/build/production-build.js +13 -0
  29. package/templates/ssr-isr/lib/build/ssg-pipeline.d.ts +15 -0
  30. package/templates/ssr-isr/lib/build/ssg-pipeline.js +113 -0
  31. package/templates/ssr-isr/lib/build-tools/build-site.js +36 -0
  32. package/templates/ssr-isr/lib/cache/index.js +10 -0
  33. package/templates/ssr-isr/lib/cache/memory.js +40 -0
  34. package/templates/ssr-isr/lib/cache/redis.js +61 -0
  35. package/templates/ssr-isr/lib/cli/banner.js +28 -0
  36. package/templates/ssr-isr/lib/cli/templates/ssg/jen.config.js +32 -0
  37. package/templates/ssr-isr/lib/cli/templates/ssg/site/index.js +9 -0
  38. package/templates/ssr-isr/lib/cli/templates/ssr/jen.config.js +32 -0
  39. package/templates/ssr-isr/lib/cli/templates/ssr/site/index.js +9 -0
  40. package/templates/ssr-isr/lib/compilers/esbuild-plugins.js +111 -0
  41. package/templates/ssr-isr/lib/compilers/svelte.js +44 -0
  42. package/templates/ssr-isr/lib/compilers/vue.js +90 -0
  43. package/templates/ssr-isr/lib/core/http.js +71 -0
  44. package/templates/ssr-isr/lib/core/middleware-hooks.js +97 -0
  45. package/templates/ssr-isr/lib/core/paths.js +39 -0
  46. package/templates/ssr-isr/lib/core/routes/match.js +47 -0
  47. package/templates/ssr-isr/lib/core/routes/scan.js +190 -0
  48. package/templates/ssr-isr/lib/core/types.js +1 -0
  49. package/templates/ssr-isr/lib/css/compiler.js +74 -0
  50. package/templates/ssr-isr/lib/db/connector.js +42 -0
  51. package/templates/ssr-isr/lib/db/drivers/jdb.js +44 -0
  52. package/templates/ssr-isr/lib/db/drivers/sql.js +182 -0
  53. package/templates/ssr-isr/lib/db/index.js +48 -0
  54. package/templates/ssr-isr/lib/db/types.js +1 -0
  55. package/templates/ssr-isr/lib/graphql/index.js +52 -0
  56. package/templates/ssr-isr/lib/graphql/resolvers.js +25 -0
  57. package/templates/ssr-isr/lib/graphql/schema.js +35 -0
  58. package/templates/ssr-isr/lib/i18n/en.json +4 -0
  59. package/templates/ssr-isr/lib/i18n/es.json +4 -0
  60. package/templates/ssr-isr/lib/i18n/index.js +15 -0
  61. package/templates/ssr-isr/lib/import/jen-import.js +161 -0
  62. package/templates/ssr-isr/lib/index.js +116 -0
  63. package/templates/ssr-isr/lib/jdb/engine.js +275 -0
  64. package/templates/ssr-isr/lib/jdb/index.js +34 -0
  65. package/templates/ssr-isr/lib/jdb/types.js +1 -0
  66. package/templates/ssr-isr/lib/jdb/utils.js +176 -0
  67. package/templates/{static → ssr-isr}/lib/middleware/builtins/body-parser.js +0 -17
  68. package/templates/ssr-isr/lib/middleware/builtins/cors.js +54 -0
  69. package/templates/{static → ssr-isr}/lib/middleware/builtins/logger.js +0 -17
  70. package/templates/{static → ssr-isr}/lib/middleware/builtins/rate-limit.js +0 -17
  71. package/templates/{static → ssr-isr}/lib/middleware/builtins/request-id.js +0 -17
  72. package/templates/{static → ssr-isr}/lib/middleware/builtins/security-headers.js +0 -17
  73. package/templates/ssr-isr/lib/middleware/context.js +124 -0
  74. package/templates/{static → ssr-isr}/lib/middleware/decorators.js +0 -17
  75. package/templates/{static → ssr-isr}/lib/middleware/errors/handler.js +0 -17
  76. package/templates/ssr-isr/lib/middleware/errors/http-error.js +10 -0
  77. package/templates/ssr-isr/lib/middleware/kernel.js +85 -0
  78. package/templates/ssr-isr/lib/middleware/pipeline.js +148 -0
  79. package/templates/ssr-isr/lib/middleware/registry.js +85 -0
  80. package/templates/ssr-isr/lib/middleware/response.js +107 -0
  81. package/templates/ssr-isr/lib/middleware/types.d.ts +1 -0
  82. package/templates/ssr-isr/lib/middleware/types.js +1 -0
  83. package/templates/ssr-isr/lib/middleware/utils/matcher.js +13 -0
  84. package/templates/{static → ssr-isr}/lib/native/bundle.js +0 -17
  85. package/templates/{static → ssr-isr}/lib/native/dev-server.js +0 -17
  86. package/templates/{static → ssr-isr}/lib/native/index.js +0 -17
  87. package/templates/{static → ssr-isr}/lib/native/optimizer.js +0 -17
  88. package/templates/ssr-isr/lib/native/style-compiler.js +19 -0
  89. package/templates/ssr-isr/lib/plugin/loader.js +36 -0
  90. package/templates/ssr-isr/lib/runtime/client-runtime.js +25 -0
  91. package/templates/ssr-isr/lib/runtime/hmr.js +59 -0
  92. package/templates/ssr-isr/lib/runtime/hydrate.js +55 -0
  93. package/templates/ssr-isr/lib/runtime/island-hydration-client.js +146 -0
  94. package/templates/ssr-isr/lib/runtime/islands.js +110 -0
  95. package/templates/ssr-isr/lib/runtime/render.js +244 -0
  96. package/templates/ssr-isr/lib/server/api-routes.js +237 -0
  97. package/templates/ssr-isr/lib/server/api.js +108 -0
  98. package/templates/ssr-isr/lib/server/app.js +438 -0
  99. package/templates/ssr-isr/lib/server/runtimeServe.js +169 -0
  100. package/templates/ssr-isr/lib/server/ssr.js +202 -0
  101. package/templates/ssr-isr/lib/shared/log.js +64 -0
  102. package/templates/ssr-isr/package.json +23 -0
  103. package/templates/ssr-isr/server.js +128 -0
  104. package/templates/ssr-isr/site/pages/(index).tsx +11 -0
  105. package/templates/ssr-isr/site/styles/global.scss +37 -0
  106. package/templates/ssr-isr/tsconfig.json +39 -0
  107. package/templates/static/build.js +30 -18
  108. package/templates/static/jen.config.ts +0 -18
  109. package/templates/static/jenjs.d.ts +0 -18
  110. package/templates/static/lib/api/(hello).js +0 -17
  111. package/templates/static/lib/api/examples/files/[...slug].js +22 -0
  112. package/templates/static/lib/api/examples/hello.js +11 -0
  113. package/templates/static/lib/api/examples/posts/[id].js +37 -0
  114. package/templates/static/lib/api/examples/posts.js +37 -0
  115. package/templates/static/lib/api/examples/search.js +23 -0
  116. package/templates/static/lib/api/index.js +41 -0
  117. package/templates/static/lib/api/loader.js +234 -0
  118. package/templates/static/lib/api/router.js +259 -0
  119. package/templates/static/lib/assets/types.js +1 -0
  120. package/templates/static/lib/auth/cookie-utils.js +3 -16
  121. package/templates/static/lib/auth/index.js +0 -17
  122. package/templates/static/lib/auth/jwt.js +0 -17
  123. package/templates/static/lib/auth/session.js +0 -17
  124. package/templates/static/lib/build/asset-hashing.js +44 -36
  125. package/templates/static/lib/build/asset-manifest.js +16 -33
  126. package/templates/static/lib/build/build.js +270 -125
  127. package/templates/static/lib/build/bundle-analyzer-ui.js +417 -0
  128. package/templates/static/lib/build/bundle-analyzer.js +945 -0
  129. package/templates/static/lib/build/code-splitter.js +194 -0
  130. package/templates/static/lib/build/feature-analyzer.js +190 -0
  131. package/templates/static/lib/build/feature-gate.js +257 -0
  132. package/templates/static/lib/build/island-hydration.js +17 -35
  133. package/templates/static/lib/build/lazy-loader.js +322 -0
  134. package/templates/static/lib/build/minifier.js +40 -59
  135. package/templates/static/lib/build/page-renderer.js +23 -40
  136. package/templates/static/lib/build/production-build.js +9 -26
  137. package/templates/static/lib/build/rust-hashing.js +71 -0
  138. package/templates/static/lib/build/script-optimizer.js +285 -0
  139. package/templates/static/lib/build/ssg-pipeline.js +100 -106
  140. package/templates/static/lib/build/vercel-output.js +298 -0
  141. package/templates/static/lib/build-tools/build-site.js +0 -17
  142. package/templates/static/lib/cache/index.js +0 -17
  143. package/templates/static/lib/cache/memory.js +0 -17
  144. package/templates/static/lib/cache/redis.js +0 -17
  145. package/templates/static/lib/cli/banner.js +0 -17
  146. package/templates/static/lib/cli/templates/ssg/jen.config.js +0 -17
  147. package/templates/static/lib/cli/templates/ssr/jen.config.js +0 -17
  148. package/templates/static/lib/client/Image.js +42 -0
  149. package/templates/static/lib/client/Link.js +190 -0
  150. package/templates/static/lib/client/PWA.js +46 -0
  151. package/templates/static/lib/client/Seo.js +97 -0
  152. package/templates/static/lib/client/index.js +9 -0
  153. package/templates/static/lib/client/useNavigation.js +25 -0
  154. package/templates/static/lib/client/useRouter.js +64 -0
  155. package/templates/static/lib/client-routing/Link.js +17 -0
  156. package/templates/static/lib/client-routing/index.js +19 -0
  157. package/templates/static/lib/client-routing/router.js +151 -0
  158. package/templates/static/lib/client-routing/signal.js +147 -0
  159. package/templates/static/lib/compilers/esbuild-plugins.js +0 -17
  160. package/templates/static/lib/compilers/svelte.js +0 -17
  161. package/templates/static/lib/compilers/vue.js +0 -17
  162. package/templates/static/lib/core/config.js +0 -17
  163. package/templates/static/lib/core/feature-guard.js +136 -0
  164. package/templates/static/lib/core/features.js +99 -0
  165. package/templates/static/lib/core/http.js +0 -17
  166. package/templates/static/lib/core/layouts/index.js +10 -0
  167. package/templates/static/lib/core/layouts/render.js +158 -0
  168. package/templates/static/lib/core/layouts/scan.js +112 -0
  169. package/templates/static/lib/core/layouts/types.js +1 -0
  170. package/templates/static/lib/core/lifecycle.js +129 -0
  171. package/templates/static/lib/core/loader-schema.js +81 -0
  172. package/templates/static/lib/core/middleware-hooks.js +0 -17
  173. package/templates/static/lib/core/paths.js +0 -17
  174. package/templates/static/lib/core/routes/advanced.js +114 -0
  175. package/templates/static/lib/core/routes/handlers.js +181 -0
  176. package/templates/static/lib/core/routes/match.js +89 -17
  177. package/templates/static/lib/core/routes/orchestrator.js +171 -0
  178. package/templates/static/lib/core/routes/rendering-config.js +131 -0
  179. package/templates/static/lib/core/routes/scan.js +0 -17
  180. package/templates/static/lib/core/types.js +0 -17
  181. package/templates/static/lib/css/compiler.js +1 -18
  182. package/templates/static/lib/data-fetching/cache.js +223 -0
  183. package/templates/static/lib/data-fetching/client.js +202 -0
  184. package/templates/static/lib/data-fetching/feature-guard.js +29 -0
  185. package/templates/static/lib/data-fetching/graphql.js +265 -0
  186. package/templates/static/lib/data-fetching/index.js +57 -0
  187. package/templates/static/lib/data-fetching/rest.js +256 -0
  188. package/templates/static/lib/data-fetching/server.js +182 -0
  189. package/templates/static/lib/data-fetching/types.js +5 -0
  190. package/templates/static/lib/db/connector.js +0 -17
  191. package/templates/static/lib/db/drivers/jdb.js +0 -17
  192. package/templates/static/lib/db/drivers/sql.js +0 -17
  193. package/templates/static/lib/db/index.js +0 -17
  194. package/templates/static/lib/db/types.js +0 -17
  195. package/templates/static/lib/devtools/component-tree.js +106 -0
  196. package/templates/static/lib/devtools/devtools.js +638 -0
  197. package/templates/static/lib/devtools/event-bus.js +29 -0
  198. package/templates/static/lib/devtools/event-logger.js +67 -0
  199. package/templates/static/lib/devtools/index.js +9 -0
  200. package/templates/static/lib/devtools/integration.js +149 -0
  201. package/templates/static/lib/devtools/performance.js +84 -0
  202. package/templates/static/lib/devtools/persistence.js +57 -0
  203. package/templates/static/lib/devtools/plugins.js +97 -0
  204. package/templates/static/lib/devtools/search.js +89 -0
  205. package/templates/static/lib/devtools/ui.js +769 -0
  206. package/templates/static/lib/features/api/handler.js +10 -0
  207. package/templates/static/lib/features/api/index.js +5 -0
  208. package/templates/static/lib/features/api/types.js +4 -0
  209. package/templates/static/lib/features/middleware/compiled.js +7 -0
  210. package/templates/static/lib/features/middleware/index.js +5 -0
  211. package/templates/static/lib/features/middleware/types.js +4 -0
  212. package/templates/static/lib/fonts/index.js +46 -0
  213. package/templates/static/lib/fonts/inject.js +125 -0
  214. package/templates/static/lib/fonts/loader.js +196 -0
  215. package/templates/static/lib/fonts/types.js +1 -0
  216. package/templates/static/lib/graphql/index.js +1 -18
  217. package/templates/static/lib/graphql/resolvers.js +20 -13
  218. package/templates/static/lib/graphql/schema.js +0 -17
  219. package/templates/static/lib/i18n/index.js +7 -19
  220. package/templates/static/lib/import/jen-import.js +1 -18
  221. package/templates/static/lib/index.js +79 -125
  222. package/templates/static/lib/jdb/engine.js +0 -17
  223. package/templates/static/lib/jdb/index.js +1 -18
  224. package/templates/static/lib/jdb/types.js +0 -17
  225. package/templates/static/lib/jdb/utils.js +0 -17
  226. package/templates/static/lib/middleware/builtins/cors.js +3 -16
  227. package/templates/static/lib/middleware/context.js +0 -17
  228. package/templates/static/lib/middleware/kernel.js +117 -25
  229. package/templates/static/lib/middleware/pipeline.js +0 -17
  230. package/templates/static/lib/middleware/registry.js +0 -17
  231. package/templates/static/lib/middleware/response.js +0 -17
  232. package/templates/static/lib/plugin/examples/analytics-plugin.js +183 -0
  233. package/templates/static/lib/plugin/examples/cdn-upload-plugin.js +94 -0
  234. package/templates/static/lib/plugin/loader.js +0 -17
  235. package/templates/static/lib/plugin/plugin-manager.js +177 -0
  236. package/templates/static/lib/plugin/types.js +28 -0
  237. package/templates/static/lib/runtime/client-runtime.js +0 -17
  238. package/templates/static/lib/runtime/hmr.js +0 -17
  239. package/templates/static/lib/runtime/hydrate.js +0 -17
  240. package/templates/static/lib/runtime/island-hydration-client.js +0 -17
  241. package/templates/static/lib/runtime/islands.js +0 -17
  242. package/templates/static/lib/runtime/render.js +208 -50
  243. package/templates/static/lib/security/security-config.js +60 -0
  244. package/templates/static/lib/security/security-middleware.js +229 -0
  245. package/templates/static/lib/server/api-routes.js +153 -43
  246. package/templates/static/lib/server/api.js +0 -17
  247. package/templates/static/lib/server/app.js +539 -223
  248. package/templates/static/lib/server/isr.js +365 -0
  249. package/templates/static/lib/server/runtimeServe.js +31 -24
  250. package/templates/static/lib/server/ssr.js +98 -22
  251. package/templates/static/lib/server-actions/handler.js +180 -0
  252. package/templates/static/lib/server-actions/index.js +19 -0
  253. package/templates/static/lib/server-actions/middleware.js +146 -0
  254. package/templates/static/lib/server-actions/scan.js +152 -0
  255. package/templates/static/lib/server-actions/types.js +1 -0
  256. package/templates/static/lib/server-actions/validators.js +156 -0
  257. package/templates/static/lib/shared/log.js +19 -20
  258. package/templates/static/lib/telemetry/api/rate-limiter.js +32 -0
  259. package/templates/static/lib/telemetry/api/validator.js +67 -0
  260. package/templates/static/lib/telemetry/client.js +121 -0
  261. package/templates/static/lib/telemetry/tests/rate-limiter.test.js +46 -0
  262. package/templates/static/lib/telemetry/tests/validator.test.js +62 -0
  263. package/templates/static/lib/vendor/glob/glob.js +4766 -0
  264. package/templates/static/lib/vendor/preact/LICENSE +21 -0
  265. package/templates/static/lib/vendor/preact/preact.module.js +797 -0
  266. package/templates/static/lib/vendor/sass/sass.node.mjs +212 -0
  267. package/templates/static/package.json +4 -0
  268. package/templates/static/server.js +22 -22
  269. package/templates/static/site/(home).tsx +0 -18
  270. package/templates/static/tsconfig.json +5 -1
  271. package/templates/static/.esbuild/jen.config.js +0 -19
  272. package/templates/static/lib/build/asset-hashing.d.ts +0 -10
  273. package/templates/static/lib/build/asset-manifest.d.ts +0 -11
  274. package/templates/static/lib/build/minifier.d.ts +0 -20
  275. package/templates/static/lib/build/page-renderer.d.ts +0 -17
  276. package/templates/static/lib/build/production-build.d.ts +0 -10
  277. package/templates/static/lib/build/ssg-pipeline.d.ts +0 -15
  278. package/templates/static/lib/middleware/errors/http-error.js +0 -27
  279. package/templates/static/lib/middleware/types.js +0 -18
  280. package/templates/static/lib/middleware/utils/matcher.js +0 -30
  281. package/templates/static/lib/native/style-compiler.js +0 -36
  282. /package/templates/{static → ssr-isr}/lib/api/(hello).d.ts +0 -0
  283. /package/templates/{static → ssr-isr}/lib/auth/cookie-utils.d.ts +0 -0
  284. /package/templates/{static → ssr-isr}/lib/auth/index.d.ts +0 -0
  285. /package/templates/{static → ssr-isr}/lib/auth/jwt.d.ts +0 -0
  286. /package/templates/{static → ssr-isr}/lib/auth/session.d.ts +0 -0
  287. /package/templates/{static → ssr-isr}/lib/build-tools/build-site.d.ts +0 -0
  288. /package/templates/{static → ssr-isr}/lib/cache/index.d.ts +0 -0
  289. /package/templates/{static → ssr-isr}/lib/cache/memory.d.ts +0 -0
  290. /package/templates/{static → ssr-isr}/lib/cache/redis.d.ts +0 -0
  291. /package/templates/{static → ssr-isr}/lib/cli/banner.d.ts +0 -0
  292. /package/templates/{static → ssr-isr}/lib/cli/templates/ssg/jen.config.d.ts +0 -0
  293. /package/templates/{static → ssr-isr}/lib/cli/templates/ssg/site/index.d.ts +0 -0
  294. /package/templates/{static → ssr-isr}/lib/cli/templates/ssr/jen.config.d.ts +0 -0
  295. /package/templates/{static → ssr-isr}/lib/cli/templates/ssr/site/index.d.ts +0 -0
  296. /package/templates/{static → ssr-isr}/lib/compilers/esbuild-plugins.d.ts +0 -0
  297. /package/templates/{static → ssr-isr}/lib/compilers/svelte.d.ts +0 -0
  298. /package/templates/{static → ssr-isr}/lib/compilers/vue.d.ts +0 -0
  299. /package/templates/{static → ssr-isr}/lib/core/config.d.ts +0 -0
  300. /package/templates/{static/lib/middleware/types.d.ts → ssr-isr/lib/core/config.js} +0 -0
  301. /package/templates/{static → ssr-isr}/lib/core/http.d.ts +0 -0
  302. /package/templates/{static → ssr-isr}/lib/core/middleware-hooks.d.ts +0 -0
  303. /package/templates/{static → ssr-isr}/lib/core/paths.d.ts +0 -0
  304. /package/templates/{static → ssr-isr}/lib/core/routes/match.d.ts +0 -0
  305. /package/templates/{static → ssr-isr}/lib/core/routes/scan.d.ts +0 -0
  306. /package/templates/{static → ssr-isr}/lib/core/types.d.ts +0 -0
  307. /package/templates/{static → ssr-isr}/lib/css/compiler.d.ts +0 -0
  308. /package/templates/{static → ssr-isr}/lib/db/connector.d.ts +0 -0
  309. /package/templates/{static → ssr-isr}/lib/db/drivers/jdb.d.ts +0 -0
  310. /package/templates/{static → ssr-isr}/lib/db/drivers/sql.d.ts +0 -0
  311. /package/templates/{static → ssr-isr}/lib/db/index.d.ts +0 -0
  312. /package/templates/{static → ssr-isr}/lib/db/types.d.ts +0 -0
  313. /package/templates/{static → ssr-isr}/lib/graphql/index.d.ts +0 -0
  314. /package/templates/{static → ssr-isr}/lib/graphql/resolvers.d.ts +0 -0
  315. /package/templates/{static → ssr-isr}/lib/graphql/schema.d.ts +0 -0
  316. /package/templates/{static → ssr-isr}/lib/i18n/index.d.ts +0 -0
  317. /package/templates/{static → ssr-isr}/lib/import/jen-import.d.ts +0 -0
  318. /package/templates/{static → ssr-isr}/lib/index.d.ts +0 -0
  319. /package/templates/{static → ssr-isr}/lib/jdb/engine.d.ts +0 -0
  320. /package/templates/{static → ssr-isr}/lib/jdb/index.d.ts +0 -0
  321. /package/templates/{static → ssr-isr}/lib/jdb/types.d.ts +0 -0
  322. /package/templates/{static → ssr-isr}/lib/jdb/utils.d.ts +0 -0
  323. /package/templates/{static → ssr-isr}/lib/middleware/builtins/body-parser.d.ts +0 -0
  324. /package/templates/{static → ssr-isr}/lib/middleware/builtins/cors.d.ts +0 -0
  325. /package/templates/{static → ssr-isr}/lib/middleware/builtins/logger.d.ts +0 -0
  326. /package/templates/{static → ssr-isr}/lib/middleware/builtins/rate-limit.d.ts +0 -0
  327. /package/templates/{static → ssr-isr}/lib/middleware/builtins/request-id.d.ts +0 -0
  328. /package/templates/{static → ssr-isr}/lib/middleware/builtins/security-headers.d.ts +0 -0
  329. /package/templates/{static → ssr-isr}/lib/middleware/context.d.ts +0 -0
  330. /package/templates/{static → ssr-isr}/lib/middleware/decorators.d.ts +0 -0
  331. /package/templates/{static → ssr-isr}/lib/middleware/errors/handler.d.ts +0 -0
  332. /package/templates/{static → ssr-isr}/lib/middleware/errors/http-error.d.ts +0 -0
  333. /package/templates/{static → ssr-isr}/lib/middleware/kernel.d.ts +0 -0
  334. /package/templates/{static → ssr-isr}/lib/middleware/pipeline.d.ts +0 -0
  335. /package/templates/{static → ssr-isr}/lib/middleware/registry.d.ts +0 -0
  336. /package/templates/{static → ssr-isr}/lib/middleware/response.d.ts +0 -0
  337. /package/templates/{static → ssr-isr}/lib/middleware/utils/matcher.d.ts +0 -0
  338. /package/templates/{static → ssr-isr}/lib/native/bundle.d.ts +0 -0
  339. /package/templates/{static → ssr-isr}/lib/native/dev-server.d.ts +0 -0
  340. /package/templates/{static → ssr-isr}/lib/native/index.d.ts +0 -0
  341. /package/templates/{static → ssr-isr}/lib/native/optimizer.d.ts +0 -0
  342. /package/templates/{static → ssr-isr}/lib/native/style-compiler.d.ts +0 -0
  343. /package/templates/{static → ssr-isr}/lib/plugin/loader.d.ts +0 -0
  344. /package/templates/{static → ssr-isr}/lib/runtime/client-runtime.d.ts +0 -0
  345. /package/templates/{static → ssr-isr}/lib/runtime/hmr.d.ts +0 -0
  346. /package/templates/{static → ssr-isr}/lib/runtime/hydrate.d.ts +0 -0
  347. /package/templates/{static → ssr-isr}/lib/runtime/island-hydration-client.d.ts +0 -0
  348. /package/templates/{static → ssr-isr}/lib/runtime/islands.d.ts +0 -0
  349. /package/templates/{static → ssr-isr}/lib/runtime/render.d.ts +0 -0
  350. /package/templates/{static → ssr-isr}/lib/server/api-routes.d.ts +0 -0
  351. /package/templates/{static → ssr-isr}/lib/server/api.d.ts +0 -0
  352. /package/templates/{static → ssr-isr}/lib/server/app.d.ts +0 -0
  353. /package/templates/{static → ssr-isr}/lib/server/runtimeServe.d.ts +0 -0
  354. /package/templates/{static → ssr-isr}/lib/server/ssr.d.ts +0 -0
  355. /package/templates/{static → ssr-isr}/lib/shared/log.d.ts +0 -0
@@ -1,35 +1,36 @@
1
- /*
2
- * This file is part of Jen.js.
3
- * Copyright (C) 2026 oopsio
4
- *
5
- * This program is free software: you can redistribute it and/or modify
6
- * it under the terms of the GNU General Public License as published by
7
- * the Free Software Foundation, either version 3 of the License, or
8
- * (at your option) any later version.
9
- *
10
- * This program is distributed in the hope that it will be useful,
11
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
- * GNU General Public License for more details.
14
- *
15
- * You should have received a copy of the GNU General Public License
16
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
- */
18
1
  import {
19
2
  createRouteMiddlewareContext,
20
3
  executeRouteMiddleware,
21
4
  } from "../core/middleware-hooks.js";
5
+ import {
6
+ scanLayouts,
7
+ buildLayoutHierarchy,
8
+ resolveLayoutStack,
9
+ renderWithLayoutStack,
10
+ collectLayoutHeads,
11
+ } from "../core/layouts/index.js";
22
12
  import { createIslandMarker } from "./islands.js";
23
- import { h } from "preact";
24
13
  import renderToString from "preact-render-to-string";
25
14
  import { pathToFileURL } from "node:url";
26
15
  import { join } from "node:path";
27
- import { mkdirSync, existsSync } from "node:fs";
16
+ import {
17
+ mkdirSync,
18
+ existsSync,
19
+ readFileSync,
20
+ statSync,
21
+ writeFileSync,
22
+ } from "node:fs";
23
+ import { createHash } from "node:crypto";
28
24
  import esbuild from "esbuild";
29
25
  import {
30
26
  vueEsbuildPlugin,
31
27
  svelteEsbuildPlugin,
32
28
  } from "../compilers/esbuild-plugins.js";
29
+ /**
30
+ * Maximum data size for serialization (1MB).
31
+ * Prevents DoS attacks via extremely large payloads.
32
+ */
33
+ const MAX_DATA_SIZE = 1024 * 1024; // 1MB
33
34
  /**
34
35
  * Escapes HTML special characters to prevent injection attacks.
35
36
  * Used when serializing user data or dynamic values into HTML attributes or script content.
@@ -45,23 +46,134 @@ function escapeHtml(s) {
45
46
  .replaceAll('"', "&quot;")
46
47
  .replaceAll("'", "&#39;");
47
48
  }
49
+ /**
50
+ * Recursively escapes all strings in an object to prevent XSS attacks.
51
+ * Traverses nested objects, arrays, and all string values.
52
+ * Non-string, non-object values are returned as-is.
53
+ *
54
+ * @param value The value to escape (can be any type).
55
+ * @returns A new object with all strings escaped.
56
+ */
57
+ function recursivelyEscapeStrings(value) {
58
+ if (typeof value === "string") {
59
+ return escapeHtml(value);
60
+ }
61
+ if (Array.isArray(value)) {
62
+ return value.map(recursivelyEscapeStrings);
63
+ }
64
+ if (value !== null && typeof value === "object") {
65
+ const escaped = {};
66
+ for (const [key, val] of Object.entries(value)) {
67
+ escaped[key] = recursivelyEscapeStrings(val);
68
+ }
69
+ return escaped;
70
+ }
71
+ // Return primitives (numbers, booleans, null, undefined) as-is
72
+ return value;
73
+ }
74
+ /**
75
+ * In-memory deduplication map for concurrent transpile requests.
76
+ * Prevents race conditions where multiple concurrent requests transpile the same file.
77
+ * Maps from cache key to a Promise that resolves to the output file path.
78
+ */
79
+ const transpileInProgress = new Map();
80
+ /**
81
+ * Computes a hash of the file content to track changes.
82
+ * Uses SHA-256 to create a unique hash of the source file.
83
+ * This enables cache invalidation when the file changes.
84
+ *
85
+ * @param filePath The absolute path to the file.
86
+ * @returns A short hash of the file content (first 8 chars).
87
+ */
88
+ function getFileHash(filePath) {
89
+ try {
90
+ const content = readFileSync(filePath, "utf-8");
91
+ const hash = createHash("sha256").update(content).digest("hex");
92
+ return hash.slice(0, 8);
93
+ } catch {
94
+ return "unknown";
95
+ }
96
+ }
97
+ /**
98
+ * Gets the modification time of a file.
99
+ * Used to detect stale cache entries.
100
+ *
101
+ * @param filePath The absolute path to the file.
102
+ * @returns The modification time in milliseconds, or 0 if file doesn't exist.
103
+ */
104
+ function getFileMtime(filePath) {
105
+ try {
106
+ return statSync(filePath).mtimeMs;
107
+ } catch {
108
+ return 0;
109
+ }
110
+ }
48
111
  /**
49
112
  * Resolves the cache directory path for compiled route modules.
50
- * Transpiled TypeScript/JSX/Vue/Svelte routes are cached to avoid repeated compilation.
113
+ * Cache key includes file hash to track changes.
51
114
  * Cache is stored in node_modules/.jen/cache to leverage .gitignore and keep the workspace clean.
52
115
  * Path names are flattened to avoid nested directory creation issues on Windows.
53
116
  *
117
+ * Cache metadata file (.meta) stores:
118
+ * - Original file modification time
119
+ * - File content hash
120
+ * - Cache creation time
121
+ *
54
122
  * @param filePath The absolute path to the original source file.
55
- * @returns The absolute path to the cached compiled output file.
123
+ * @returns Object with cache file path and metadata file path.
56
124
  */
57
125
  function getCachePath(filePath) {
58
126
  const cacheDir = join(process.cwd(), "node_modules", ".jen", "cache");
59
127
  if (!existsSync(cacheDir)) {
60
128
  mkdirSync(cacheDir, { recursive: true });
61
129
  }
62
- // Flatten path to avoid directory structure issues.
130
+ // Flatten path and append file hash for uniqueness
63
131
  const flatName = filePath.replace(/[\\/:]/g, "_").replace(/^_+/, "");
64
- return join(cacheDir, flatName + ".mjs");
132
+ const fileHash = getFileHash(filePath);
133
+ const cacheName = `${flatName}_${fileHash}`;
134
+ const cachePath = join(cacheDir, cacheName + ".mjs");
135
+ const metaPath = join(cacheDir, cacheName + ".meta");
136
+ return { cachePath, metaPath };
137
+ }
138
+ /**
139
+ * Checks if cache is still valid (file hasn't changed).
140
+ * Compares file hash and modification time from metadata.
141
+ *
142
+ * @param filePath The absolute path to the source file.
143
+ * @param metaPath The absolute path to the metadata file.
144
+ * @returns true if cache is valid, false if file changed or metadata missing.
145
+ */
146
+ function isCacheValid(filePath, metaPath) {
147
+ try {
148
+ const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
149
+ const currentHash = getFileHash(filePath);
150
+ const currentMtime = getFileMtime(filePath);
151
+ // Check if file hash or mtime changed
152
+ return meta.fileHash === currentHash && meta.fileMtime === currentMtime;
153
+ } catch {
154
+ return false;
155
+ }
156
+ }
157
+ /**
158
+ * Writes cache metadata to track file state.
159
+ * Stores file hash, mtime, and cache time for future validation.
160
+ *
161
+ * @param filePath The absolute path to the source file.
162
+ * @param metaPath The absolute path to the metadata file.
163
+ */
164
+ function writeCacheMeta(filePath, metaPath) {
165
+ try {
166
+ const meta = {
167
+ filePath,
168
+ fileHash: getFileHash(filePath),
169
+ fileMtime: getFileMtime(filePath),
170
+ cacheTime: Date.now(),
171
+ };
172
+ writeFileSync(metaPath, JSON.stringify(meta, null, 2));
173
+ } catch (err) {
174
+ // Non-critical: log but don't fail transpilation
175
+ console.warn(`Failed to write cache metadata: ${metaPath}`, err);
176
+ }
65
177
  }
66
178
  /**
67
179
  * Renders a route module to a complete HTML document.
@@ -92,6 +204,14 @@ function getCachePath(filePath) {
92
204
  */
93
205
  export async function renderRouteToHtml(opts) {
94
206
  const { config, route, url, params, query, headers, cookies } = opts;
207
+ // Scan and build layout hierarchy for this route
208
+ const layoutEntries = scanLayouts(config);
209
+ const applicableLayouts = buildLayoutHierarchy(
210
+ layoutEntries,
211
+ route.filePath,
212
+ config.siteDir,
213
+ );
214
+ const layoutStack = await resolveLayoutStack(applicableLayouts);
95
215
  // Transpile route file if needed. TypeScript and JSX require compilation to JavaScript.
96
216
  // Vue and Svelte components also need transpilation to Preact-compatible JavaScript.
97
217
  let moduleUrl = route.filePath;
@@ -100,19 +220,43 @@ export async function renderRouteToHtml(opts) {
100
220
  route.filePath.toLowerCase().endsWith(e),
101
221
  );
102
222
  if (requiresTranspile) {
103
- const outfile = getCachePath(route.filePath);
104
- await esbuild.build({
105
- entryPoints: [route.filePath],
106
- outfile,
107
- format: "esm",
108
- platform: "node", // Node platform for SSR supports built-ins like fs, path, etc.
109
- target: "es2022",
110
- bundle: true, // Bundle all local imports into a single file for simplicity.
111
- external: ["preact", "preact-render-to-string", "jenjs"], // Keep framework imports external.
112
- write: true,
113
- plugins: [vueEsbuildPlugin(), svelteEsbuildPlugin()],
114
- });
115
- moduleUrl = outfile;
223
+ const { cachePath, metaPath } = getCachePath(route.filePath);
224
+ // Check if valid cache exists (file hasn't changed)
225
+ if (existsSync(cachePath) && isCacheValid(route.filePath, metaPath)) {
226
+ moduleUrl = cachePath;
227
+ } else {
228
+ // Use deduplication map to prevent concurrent transpile races
229
+ const cacheKey = cachePath;
230
+ if (transpileInProgress.has(cacheKey)) {
231
+ // Another request is already transpiling this file, wait for it
232
+ moduleUrl = await transpileInProgress.get(cacheKey);
233
+ } else {
234
+ // Start transpilation and store promise for deduplication
235
+ const transpilePromise = (async () => {
236
+ try {
237
+ await esbuild.build({
238
+ entryPoints: [route.filePath],
239
+ outfile: cachePath,
240
+ format: "esm",
241
+ platform: "node", // Node platform for SSR supports built-ins like fs, path, etc.
242
+ target: "es2022",
243
+ bundle: true, // Bundle all local imports into a single file for simplicity.
244
+ external: ["preact", "preact-render-to-string", "jenjs"], // Keep framework imports external.
245
+ write: true,
246
+ plugins: [vueEsbuildPlugin(), svelteEsbuildPlugin()],
247
+ });
248
+ // Write metadata after successful transpilation
249
+ writeCacheMeta(route.filePath, metaPath);
250
+ return cachePath;
251
+ } finally {
252
+ // Remove from in-progress map when done
253
+ transpileInProgress.delete(cacheKey);
254
+ }
255
+ })();
256
+ transpileInProgress.set(cacheKey, transpilePromise);
257
+ moduleUrl = await transpilePromise;
258
+ }
259
+ }
116
260
  }
117
261
  // Cache busting via query parameter ensures fresh module evaluation even if file is unchanged.
118
262
  // This is critical because esbuild may use cached builds and we need the latest code for SSR.
@@ -181,8 +325,13 @@ export async function renderRouteToHtml(opts) {
181
325
  const Page = mod.default;
182
326
  // Check if hydration is disabled. Set to false for purely static pages with no client-side interactivity.
183
327
  const shouldHydrate = mod.hydrate !== false;
184
- const app = h(Page, { data, params, query });
185
- // Render the page component to a static HTML string.
328
+ // Render with layout hierarchy wrapping the page component
329
+ const app = renderWithLayoutStack(layoutStack, Page, {
330
+ data,
331
+ params,
332
+ query,
333
+ });
334
+ // Render the page component (with layouts) to a static HTML string.
186
335
  // Preact rendering at this stage is purely static; hydration happens on the client.
187
336
  let bodyHtml = renderToString(app);
188
337
  // Check for island components that need selective hydration on the client.
@@ -202,18 +351,23 @@ export async function renderRouteToHtml(opts) {
202
351
  bodyHtml = bodyHtml.replace("</div>", `${marker}</div>`);
203
352
  }
204
353
  }
205
- // Collect all head elements from configuration and the route's Head component.
206
- // Head components allow per-route customization of meta tags, title, links, etc.
354
+ // Collect all head elements from configuration, layout components, and the route's Head component.
355
+ // Head components are collected from root layout to page, allowing each layer to contribute meta tags.
207
356
  const headParts = [];
208
357
  headParts.push(...config.inject.head);
209
- if (mod.Head) {
358
+ // Collect heads from layout stack
359
+ const layoutHeads = collectLayoutHeads(layoutStack, mod.Head, {
360
+ data,
361
+ params,
362
+ query,
363
+ });
364
+ for (const headNode of layoutHeads) {
210
365
  try {
211
- const headNode = h(mod.Head, { data, params, query });
212
366
  const headHtml = renderToString(headNode);
213
367
  headParts.push(headHtml);
214
368
  } catch (err) {
215
369
  console.error(
216
- `Failed to render Head component for ${route.filePath}:`,
370
+ `Failed to render Head component:`,
217
371
  err instanceof Error ? err.message : String(err),
218
372
  );
219
373
  }
@@ -232,13 +386,17 @@ ${headParts.join("\n")}
232
386
  // Only inject hydration script if enabled for this route.
233
387
  // Hydration-disabled routes are purely static and require no JavaScript.
234
388
  if (shouldHydrate) {
235
- // Serialize loader and route data for the client. Must escape </script to prevent injection attacks.
236
- // The client uses this data to reconstruct the component tree and populate props.
237
- const frameworkDataStr = JSON.stringify(
238
- { data, params, query },
239
- null,
240
- 2,
241
- ).replace(/<\/script/gi, "<\\/script");
389
+ // Validate data size before serialization to prevent DoS attacks.
390
+ const dataToSerialize = { data, params, query };
391
+ const frameworkData = recursivelyEscapeStrings(dataToSerialize);
392
+ const frameworkDataStr = JSON.stringify(frameworkData, null, 2);
393
+ // Check size of serialized data
394
+ if (frameworkDataStr.length > MAX_DATA_SIZE) {
395
+ throw new Error(
396
+ `Framework data exceeds maximum size of ${MAX_DATA_SIZE} bytes. ` +
397
+ `Current size: ${frameworkDataStr.length} bytes. This may indicate a DoS attempt or excessive data in loader/middleware.`,
398
+ );
399
+ }
242
400
  const hydrateFile = `/__hydrate?file=${encodeURIComponent(route.filePath)}`;
243
401
  html += `
244
402
  <script id="__FRAMEWORK_DATA__" type="application/json">
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Default security configuration (sensible defaults for production).
3
+ */
4
+ export const DEFAULT_SECURITY_CONFIG = {
5
+ headers: {
6
+ csp: {
7
+ enabled: true,
8
+ directives: {
9
+ "default-src": ["'self'"],
10
+ "script-src": ["'self'", "'unsafe-inline'"],
11
+ "style-src": ["'self'", "'unsafe-inline'"],
12
+ "img-src": ["'self'", "data:", "https:"],
13
+ "font-src": ["'self'", "data:"],
14
+ "connect-src": ["'self'"],
15
+ },
16
+ reportOnly: false,
17
+ },
18
+ hsts: {
19
+ enabled: true,
20
+ maxAge: 31536000,
21
+ includeSubDomains: true,
22
+ preload: false,
23
+ },
24
+ cors: {
25
+ enabled: false,
26
+ origins: ["*"],
27
+ methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
28
+ credentials: false,
29
+ },
30
+ frameOptions: {
31
+ enabled: true,
32
+ value: "SAMEORIGIN",
33
+ },
34
+ contentTypeOptions: true,
35
+ referrerPolicy: "strict-origin-when-cross-origin",
36
+ permissionsPolicy: {
37
+ enabled: true,
38
+ directives: {
39
+ geolocation: ["none"],
40
+ microphone: ["none"],
41
+ camera: ["none"],
42
+ },
43
+ },
44
+ },
45
+ csrf: {
46
+ enabled: true,
47
+ cookieName: "__jen_csrf",
48
+ headerName: "X-CSRF-Token",
49
+ },
50
+ validation: {
51
+ enabled: true,
52
+ maxBodySize: 10 * 1024 * 1024, // 10MB
53
+ },
54
+ rateLimit: {
55
+ enabled: false,
56
+ maxRequests: 100,
57
+ windowSeconds: 60,
58
+ skipPaths: ["/health"],
59
+ },
60
+ };
@@ -0,0 +1,229 @@
1
+ import { DEFAULT_SECURITY_CONFIG } from "./security-config.js";
2
+ /**
3
+ * Security headers middleware for Jen.js.
4
+ *
5
+ * Applies security HTTP headers to responses based on configuration.
6
+ * Includes CSP, HSTS, CORS, and other protection headers.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { securityHeadersMiddleware } from "jenjs";
11
+ *
12
+ * // Use in middleware pipeline
13
+ * app.use(securityHeadersMiddleware(securityConfig));
14
+ * ```
15
+ */
16
+ export function securityHeadersMiddleware(config) {
17
+ const mergedConfig = {
18
+ ...DEFAULT_SECURITY_CONFIG,
19
+ ...config,
20
+ headers: {
21
+ ...DEFAULT_SECURITY_CONFIG.headers,
22
+ ...config?.headers,
23
+ },
24
+ };
25
+ return async (context) => {
26
+ const headers = mergedConfig.headers || {};
27
+ // Content Security Policy
28
+ if (headers.csp?.enabled) {
29
+ const cspValue = buildCSPHeader(headers.csp);
30
+ const headerName = headers.csp.reportOnly
31
+ ? "Content-Security-Policy-Report-Only"
32
+ : "Content-Security-Policy";
33
+ context.res.setHeader(headerName, cspValue);
34
+ }
35
+ // HTTP Strict Transport Security
36
+ if (headers.hsts?.enabled) {
37
+ const hstsValue = buildHSTSHeader(headers.hsts);
38
+ context.res.setHeader("Strict-Transport-Security", hstsValue);
39
+ }
40
+ // X-Frame-Options
41
+ if (headers.frameOptions?.enabled) {
42
+ const frameValue =
43
+ headers.frameOptions.value === "ALLOW-FROM"
44
+ ? `${headers.frameOptions.value} ${headers.frameOptions.uri}`
45
+ : headers.frameOptions.value || "SAMEORIGIN";
46
+ context.res.setHeader("X-Frame-Options", frameValue);
47
+ }
48
+ // X-Content-Type-Options
49
+ if (headers.contentTypeOptions) {
50
+ context.res.setHeader("X-Content-Type-Options", "nosniff");
51
+ }
52
+ // Referrer-Policy
53
+ if (headers.referrerPolicy) {
54
+ context.res.setHeader("Referrer-Policy", headers.referrerPolicy);
55
+ }
56
+ // Permissions-Policy
57
+ if (headers.permissionsPolicy?.enabled) {
58
+ const policyValue = buildPermissionsPolicyHeader(
59
+ headers.permissionsPolicy,
60
+ );
61
+ if (policyValue) {
62
+ context.res.setHeader("Permissions-Policy", policyValue);
63
+ }
64
+ }
65
+ // CORS Headers
66
+ if (headers.cors?.enabled) {
67
+ applyCORSHeaders(context, headers.cors);
68
+ }
69
+ };
70
+ }
71
+ /**
72
+ * Build Content Security Policy header value.
73
+ */
74
+ function buildCSPHeader(config) {
75
+ if (!config.directives) {
76
+ return "";
77
+ }
78
+ return Object.entries(config.directives)
79
+ .map(([key, values]) => `${key} ${values.join(" ")}`)
80
+ .join("; ");
81
+ }
82
+ /**
83
+ * Build Strict-Transport-Security header value.
84
+ */
85
+ function buildHSTSHeader(config) {
86
+ const parts = [];
87
+ if (config.maxAge) {
88
+ parts.push(`max-age=${config.maxAge}`);
89
+ }
90
+ if (config.includeSubDomains) {
91
+ parts.push("includeSubDomains");
92
+ }
93
+ if (config.preload) {
94
+ parts.push("preload");
95
+ }
96
+ return parts.join("; ");
97
+ }
98
+ /**
99
+ * Build Permissions-Policy header value.
100
+ */
101
+ function buildPermissionsPolicyHeader(config) {
102
+ if (!config.directives) {
103
+ return "";
104
+ }
105
+ return Object.entries(config.directives)
106
+ .map(([key, value]) => {
107
+ if (typeof value === "string") {
108
+ return `${key}=(${value})`;
109
+ }
110
+ return `${key}=(${value.join(" ")})`;
111
+ })
112
+ .join(", ");
113
+ }
114
+ /**
115
+ * Apply CORS headers to response.
116
+ */
117
+ function applyCORSHeaders(context, config) {
118
+ const origin = context.req.headers.origin || "";
119
+ const allowedOrigins = Array.isArray(config.origins)
120
+ ? config.origins
121
+ : [config.origins || "*"];
122
+ if (allowedOrigins.includes("*") || allowedOrigins.includes(origin)) {
123
+ context.res.setHeader("Access-Control-Allow-Origin", origin || "*");
124
+ }
125
+ if (config.credentials) {
126
+ context.res.setHeader("Access-Control-Allow-Credentials", "true");
127
+ }
128
+ if (config.methods) {
129
+ context.res.setHeader(
130
+ "Access-Control-Allow-Methods",
131
+ config.methods.join(", "),
132
+ );
133
+ }
134
+ if (config.allowedHeaders) {
135
+ context.res.setHeader(
136
+ "Access-Control-Allow-Headers",
137
+ config.allowedHeaders.join(", "),
138
+ );
139
+ }
140
+ if (config.exposedHeaders) {
141
+ context.res.setHeader(
142
+ "Access-Control-Expose-Headers",
143
+ config.exposedHeaders.join(", "),
144
+ );
145
+ }
146
+ if (config.maxAge) {
147
+ context.res.setHeader("Access-Control-Max-Age", config.maxAge.toString());
148
+ }
149
+ // Handle preflight requests
150
+ if (context.req.method === "OPTIONS") {
151
+ context.res.writeHead(204);
152
+ context.res.end();
153
+ }
154
+ }
155
+ /**
156
+ * CSRF token generation and validation middleware.
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const csrfMiddleware = createCSRFMiddleware();
161
+ * app.use(csrfMiddleware);
162
+ * ```
163
+ */
164
+ export function createCSRFMiddleware(config) {
165
+ const cookieName = config?.cookieName || "__jen_csrf";
166
+ const headerName = config?.headerName || "X-CSRF-Token";
167
+ return async (context) => {
168
+ // Generate CSRF token if not present
169
+ const existingToken = context.req.headers.cookie
170
+ ?.split(";")
171
+ .find((c) => c.trim().startsWith(`${cookieName}=`));
172
+ if (!existingToken) {
173
+ const token = generateCSRFToken();
174
+ context.res.setHeader(
175
+ "Set-Cookie",
176
+ `${cookieName}=${token}; HttpOnly; Path=/; SameSite=Strict`,
177
+ );
178
+ context.req.csrfToken = token;
179
+ }
180
+ // Validate CSRF token on state-changing requests
181
+ if (["POST", "PUT", "DELETE", "PATCH"].includes(context.req.method || "")) {
182
+ const tokenFromHeader = context.req.headers[headerName.toLowerCase()];
183
+ const tokenFromCookie = getCookieValue(
184
+ context.req.headers.cookie,
185
+ cookieName,
186
+ );
187
+ if (
188
+ tokenFromHeader &&
189
+ tokenFromCookie &&
190
+ tokenFromHeader === tokenFromCookie
191
+ ) {
192
+ // Token is valid, continue
193
+ return;
194
+ }
195
+ // Token is missing or invalid
196
+ context.res.writeHead(403, { "Content-Type": "application/json" });
197
+ context.res.end(
198
+ JSON.stringify({ error: "CSRF token validation failed" }),
199
+ );
200
+ }
201
+ };
202
+ }
203
+ /**
204
+ * Generate a random CSRF token.
205
+ */
206
+ function generateCSRFToken() {
207
+ const array = new Uint8Array(32);
208
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.getRandomValues) {
209
+ globalThis.crypto.getRandomValues(array);
210
+ } else {
211
+ // Fallback for environments without crypto
212
+ for (let i = 0; i < array.length; i++) {
213
+ array[i] = Math.floor(Math.random() * 256);
214
+ }
215
+ }
216
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
217
+ "",
218
+ );
219
+ }
220
+ /**
221
+ * Extract cookie value by name.
222
+ */
223
+ function getCookieValue(cookieHeader, name) {
224
+ if (!cookieHeader) return undefined;
225
+ const cookie = cookieHeader
226
+ .split(";")
227
+ .find((c) => c.trim().startsWith(`${name}=`));
228
+ return cookie ? cookie.trim().substring(name.length + 1) : undefined;
229
+ }