@jasonshimmy/vite-plugin-cer-app 0.23.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/dist/cli/checks/lighthouse.d.ts +26 -0
  2. package/dist/cli/checks/lighthouse.d.ts.map +1 -0
  3. package/dist/cli/checks/lighthouse.js +121 -0
  4. package/dist/cli/checks/lighthouse.js.map +1 -0
  5. package/dist/cli/checks/links.d.ts +12 -0
  6. package/dist/cli/checks/links.d.ts.map +1 -0
  7. package/dist/cli/checks/links.js +89 -0
  8. package/dist/cli/checks/links.js.map +1 -0
  9. package/dist/cli/checks/performance.d.ts +17 -0
  10. package/dist/cli/checks/performance.d.ts.map +1 -0
  11. package/dist/cli/checks/performance.js +61 -0
  12. package/dist/cli/checks/performance.js.map +1 -0
  13. package/dist/cli/commands/check.d.ts +3 -0
  14. package/dist/cli/commands/check.d.ts.map +1 -0
  15. package/dist/cli/commands/check.js +85 -0
  16. package/dist/cli/commands/check.js.map +1 -0
  17. package/dist/cli/create/index.d.ts +11 -1
  18. package/dist/cli/create/index.d.ts.map +1 -1
  19. package/dist/cli/create/index.js +135 -65
  20. package/dist/cli/create/index.js.map +1 -1
  21. package/dist/cli/create/templates/spa/package.json.tpl +5 -3
  22. package/dist/cli/create/templates/ssg/cer.config.ts.tpl +2 -0
  23. package/dist/cli/create/templates/ssg/package.json.tpl +5 -3
  24. package/dist/cli/create/templates/ssr/package.json.tpl +5 -3
  25. package/dist/cli/index.js +2 -0
  26. package/dist/cli/index.js.map +1 -1
  27. package/dist/index.d.ts +3 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/plugin/build-ssg.d.ts +22 -6
  30. package/dist/plugin/build-ssg.d.ts.map +1 -1
  31. package/dist/plugin/build-ssg.js +227 -17
  32. package/dist/plugin/build-ssg.js.map +1 -1
  33. package/dist/plugin/content/parser.d.ts.map +1 -1
  34. package/dist/plugin/content/parser.js +54 -10
  35. package/dist/plugin/content/parser.js.map +1 -1
  36. package/dist/plugin/dev-server.d.ts +4 -1
  37. package/dist/plugin/dev-server.d.ts.map +1 -1
  38. package/dist/plugin/dev-server.js.map +1 -1
  39. package/dist/plugin/dts-generator.d.ts.map +1 -1
  40. package/dist/plugin/dts-generator.js +50 -8
  41. package/dist/plugin/dts-generator.js.map +1 -1
  42. package/dist/plugin/generated-dir.d.ts +2 -0
  43. package/dist/plugin/generated-dir.d.ts.map +1 -1
  44. package/dist/plugin/generated-dir.js +17 -6
  45. package/dist/plugin/generated-dir.js.map +1 -1
  46. package/dist/plugin/html-post-process.d.ts +6 -0
  47. package/dist/plugin/html-post-process.d.ts.map +1 -1
  48. package/dist/plugin/html-post-process.js +24 -0
  49. package/dist/plugin/html-post-process.js.map +1 -1
  50. package/dist/plugin/index.d.ts.map +1 -1
  51. package/dist/plugin/index.js +60 -13
  52. package/dist/plugin/index.js.map +1 -1
  53. package/dist/plugin/transforms/auto-import.d.ts.map +1 -1
  54. package/dist/plugin/transforms/auto-import.js +7 -0
  55. package/dist/plugin/transforms/auto-import.js.map +1 -1
  56. package/dist/plugin/transforms/prune-runtime-css.d.ts +8 -0
  57. package/dist/plugin/transforms/prune-runtime-css.d.ts.map +1 -0
  58. package/dist/plugin/transforms/prune-runtime-css.js +40 -0
  59. package/dist/plugin/transforms/prune-runtime-css.js.map +1 -0
  60. package/dist/runtime/app-template.d.ts +1 -1
  61. package/dist/runtime/app-template.d.ts.map +1 -1
  62. package/dist/runtime/app-template.js +206 -36
  63. package/dist/runtime/app-template.js.map +1 -1
  64. package/dist/runtime/composables/index.d.ts +4 -0
  65. package/dist/runtime/composables/index.d.ts.map +1 -1
  66. package/dist/runtime/composables/index.js +2 -0
  67. package/dist/runtime/composables/index.js.map +1 -1
  68. package/dist/runtime/composables/use-active-headings.d.ts +24 -0
  69. package/dist/runtime/composables/use-active-headings.d.ts.map +1 -0
  70. package/dist/runtime/composables/use-active-headings.js +112 -0
  71. package/dist/runtime/composables/use-active-headings.js.map +1 -0
  72. package/dist/runtime/composables/use-content-page.d.ts +45 -0
  73. package/dist/runtime/composables/use-content-page.d.ts.map +1 -0
  74. package/dist/runtime/composables/use-content-page.js +179 -0
  75. package/dist/runtime/composables/use-content-page.js.map +1 -0
  76. package/dist/runtime/composables/use-content-search.d.ts.map +1 -1
  77. package/dist/runtime/composables/use-content-search.js +1 -5
  78. package/dist/runtime/composables/use-content-search.js.map +1 -1
  79. package/dist/runtime/composables/use-fetch.d.ts +2 -1
  80. package/dist/runtime/composables/use-fetch.d.ts.map +1 -1
  81. package/dist/runtime/composables/use-fetch.js +112 -43
  82. package/dist/runtime/composables/use-fetch.js.map +1 -1
  83. package/dist/runtime/composables/use-route.d.ts.map +1 -1
  84. package/dist/runtime/composables/use-route.js +39 -11
  85. package/dist/runtime/composables/use-route.js.map +1 -1
  86. package/dist/runtime/content/client.js +2 -2
  87. package/dist/runtime/content/client.js.map +1 -1
  88. package/dist/runtime/entry-server-template.d.ts +16 -1
  89. package/dist/runtime/entry-server-template.d.ts.map +1 -1
  90. package/dist/runtime/entry-server-template.js +120 -13
  91. package/dist/runtime/entry-server-template.js.map +1 -1
  92. package/dist/types/config.d.ts +45 -4
  93. package/dist/types/config.d.ts.map +1 -1
  94. package/dist/types/config.js.map +1 -1
  95. package/dist/types/middleware.d.ts +2 -2
  96. package/dist/types/middleware.d.ts.map +1 -1
  97. package/package.json +22 -19
  98. package/.github/copilot-instructions.md +0 -169
  99. package/.github/workflows/publish.yml +0 -257
  100. package/.nvmrc +0 -1
  101. package/CHANGELOG.md +0 -236
  102. package/CLAUDE.md +0 -113
  103. package/commits.txt +0 -1
  104. package/cypress.config.ts +0 -16
  105. package/docs/authentication.md +0 -285
  106. package/docs/cli.md +0 -324
  107. package/docs/components.md +0 -237
  108. package/docs/composables.md +0 -907
  109. package/docs/configuration.md +0 -696
  110. package/docs/content.md +0 -570
  111. package/docs/data-loading.md +0 -336
  112. package/docs/getting-started.md +0 -237
  113. package/docs/head-management.md +0 -212
  114. package/docs/i18n.md +0 -159
  115. package/docs/layouts.md +0 -224
  116. package/docs/middleware.md +0 -234
  117. package/docs/plugins.md +0 -170
  118. package/docs/rendering-modes.md +0 -377
  119. package/docs/routing.md +0 -485
  120. package/docs/server-api.md +0 -253
  121. package/docs/testing.md +0 -455
  122. package/docs/use-fetch.md +0 -183
  123. package/e2e/cypress/e2e/api.cy.ts +0 -123
  124. package/e2e/cypress/e2e/async-component.cy.ts +0 -27
  125. package/e2e/cypress/e2e/component-splitting.cy.ts +0 -50
  126. package/e2e/cypress/e2e/content.cy.ts +0 -425
  127. package/e2e/cypress/e2e/cookie.cy.ts +0 -85
  128. package/e2e/cypress/e2e/data.cy.ts +0 -158
  129. package/e2e/cypress/e2e/error-boundary.cy.ts +0 -88
  130. package/e2e/cypress/e2e/fouc.cy.ts +0 -254
  131. package/e2e/cypress/e2e/group-meta.cy.ts +0 -36
  132. package/e2e/cypress/e2e/head.cy.ts +0 -89
  133. package/e2e/cypress/e2e/i18n.cy.ts +0 -79
  134. package/e2e/cypress/e2e/interactive.cy.ts +0 -137
  135. package/e2e/cypress/e2e/isr-nested-runtime.cy.ts +0 -102
  136. package/e2e/cypress/e2e/jit-css-custom-colors.cy.ts +0 -43
  137. package/e2e/cypress/e2e/middleware-chain.cy.ts +0 -89
  138. package/e2e/cypress/e2e/middleware-status.cy.ts +0 -30
  139. package/e2e/cypress/e2e/middleware.cy.ts +0 -45
  140. package/e2e/cypress/e2e/navigate.cy.ts +0 -39
  141. package/e2e/cypress/e2e/observability.cy.ts +0 -77
  142. package/e2e/cypress/e2e/per-route-error.cy.ts +0 -42
  143. package/e2e/cypress/e2e/per-route-render.cy.ts +0 -70
  144. package/e2e/cypress/e2e/preview-hardening.cy.ts +0 -88
  145. package/e2e/cypress/e2e/routes.cy.ts +0 -128
  146. package/e2e/cypress/e2e/seo-meta.cy.ts +0 -108
  147. package/e2e/cypress/e2e/server-middleware.cy.ts +0 -46
  148. package/e2e/cypress/e2e/session-rotation.cy.ts +0 -25
  149. package/e2e/cypress/e2e/session.cy.ts +0 -73
  150. package/e2e/cypress/e2e/ssr-render-error.cy.ts +0 -68
  151. package/e2e/cypress/e2e/synthetic-404.cy.ts +0 -45
  152. package/e2e/cypress/e2e/use-page-data.cy.ts +0 -122
  153. package/e2e/cypress/e2e/use-state.cy.ts +0 -138
  154. package/e2e/cypress/support/commands.ts +0 -120
  155. package/e2e/cypress/support/e2e.ts +0 -10
  156. package/e2e/cypress/tsconfig.json +0 -14
  157. package/e2e/kitchen-sink/app/components/ks-badge.ts +0 -8
  158. package/e2e/kitchen-sink/app/composables/useKsCounter.ts +0 -9
  159. package/e2e/kitchen-sink/app/error.ts +0 -18
  160. package/e2e/kitchen-sink/app/layouts/admin.ts +0 -13
  161. package/e2e/kitchen-sink/app/layouts/default.ts +0 -25
  162. package/e2e/kitchen-sink/app/layouts/group.ts +0 -9
  163. package/e2e/kitchen-sink/app/layouts/minimal.ts +0 -7
  164. package/e2e/kitchen-sink/app/loading.ts +0 -9
  165. package/e2e/kitchen-sink/app/middleware/auth.ts +0 -9
  166. package/e2e/kitchen-sink/app/middleware/custom-log.ts +0 -4
  167. package/e2e/kitchen-sink/app/middleware/group-auth.ts +0 -7
  168. package/e2e/kitchen-sink/app/middleware/logger.ts +0 -14
  169. package/e2e/kitchen-sink/app/pages/(auth)/login.ts +0 -13
  170. package/e2e/kitchen-sink/app/pages/(auth)/protected.ts +0 -16
  171. package/e2e/kitchen-sink/app/pages/404.ts +0 -9
  172. package/e2e/kitchen-sink/app/pages/about.ts +0 -17
  173. package/e2e/kitchen-sink/app/pages/admin/_layout.ts +0 -1
  174. package/e2e/kitchen-sink/app/pages/admin/dashboard.ts +0 -11
  175. package/e2e/kitchen-sink/app/pages/async-component-error-test.ts +0 -18
  176. package/e2e/kitchen-sink/app/pages/async-component-test.ts +0 -18
  177. package/e2e/kitchen-sink/app/pages/blog/[slug].ts +0 -59
  178. package/e2e/kitchen-sink/app/pages/blog/index.ts +0 -51
  179. package/e2e/kitchen-sink/app/pages/content-blog.ts +0 -37
  180. package/e2e/kitchen-sink/app/pages/content-doc.ts +0 -42
  181. package/e2e/kitchen-sink/app/pages/content-fallback.ts +0 -36
  182. package/e2e/kitchen-sink/app/pages/content-guides.ts +0 -35
  183. package/e2e/kitchen-sink/app/pages/content-index.ts +0 -39
  184. package/e2e/kitchen-sink/app/pages/content-search.ts +0 -36
  185. package/e2e/kitchen-sink/app/pages/cookie-test.ts +0 -22
  186. package/e2e/kitchen-sink/app/pages/counter.ts +0 -17
  187. package/e2e/kitchen-sink/app/pages/group-auth-blocked.ts +0 -9
  188. package/e2e/kitchen-sink/app/pages/group-meta-test/_layout.ts +0 -6
  189. package/e2e/kitchen-sink/app/pages/group-meta-test/custom-mw.ts +0 -14
  190. package/e2e/kitchen-sink/app/pages/group-meta-test/page.ts +0 -9
  191. package/e2e/kitchen-sink/app/pages/group-meta-test/protected.ts +0 -8
  192. package/e2e/kitchen-sink/app/pages/head.ts +0 -20
  193. package/e2e/kitchen-sink/app/pages/i18n-test.ts +0 -14
  194. package/e2e/kitchen-sink/app/pages/index.ts +0 -27
  195. package/e2e/kitchen-sink/app/pages/isr-test.ts +0 -17
  196. package/e2e/kitchen-sink/app/pages/items/[id].ts +0 -20
  197. package/e2e/kitchen-sink/app/pages/jit-css-custom-colors-test.ts +0 -14
  198. package/e2e/kitchen-sink/app/pages/loader-error-test.ts +0 -13
  199. package/e2e/kitchen-sink/app/pages/loader-props-test.ts +0 -19
  200. package/e2e/kitchen-sink/app/pages/loader-response-error-test.ts +0 -13
  201. package/e2e/kitchen-sink/app/pages/middleware-chain-test.ts +0 -12
  202. package/e2e/kitchen-sink/app/pages/middleware-status-test.ts +0 -9
  203. package/e2e/kitchen-sink/app/pages/navigate-test.ts +0 -16
  204. package/e2e/kitchen-sink/app/pages/observability-test.ts +0 -25
  205. package/e2e/kitchen-sink/app/pages/per-route-error-test.error.ts +0 -13
  206. package/e2e/kitchen-sink/app/pages/per-route-error-test.ts +0 -15
  207. package/e2e/kitchen-sink/app/pages/plugin-dsd-test.ts +0 -10
  208. package/e2e/kitchen-sink/app/pages/render-error-test.ts +0 -12
  209. package/e2e/kitchen-sink/app/pages/render-server-test.ts +0 -12
  210. package/e2e/kitchen-sink/app/pages/render-spa-test.ts +0 -12
  211. package/e2e/kitchen-sink/app/pages/route-info.ts +0 -14
  212. package/e2e/kitchen-sink/app/pages/seo-test.ts +0 -23
  213. package/e2e/kitchen-sink/app/pages/session-rotation-test.ts +0 -25
  214. package/e2e/kitchen-sink/app/pages/use-state-test.ts +0 -37
  215. package/e2e/kitchen-sink/app/plugins/01.setup.ts +0 -7
  216. package/e2e/kitchen-sink/app/plugins/02.ks-plugin-components.ts +0 -27
  217. package/e2e/kitchen-sink/cer-auto-imports.d.ts +0 -51
  218. package/e2e/kitchen-sink/cer-env.d.ts +0 -35
  219. package/e2e/kitchen-sink/cer-tsconfig.json +0 -30
  220. package/e2e/kitchen-sink/cer.config.ts +0 -43
  221. package/e2e/kitchen-sink/content/01.guides/01.index.md +0 -8
  222. package/e2e/kitchen-sink/content/01.guides/02.intro.md +0 -8
  223. package/e2e/kitchen-sink/content/01.guides/10.advanced.md +0 -8
  224. package/e2e/kitchen-sink/content/blog/2026-04-01-hello.md +0 -26
  225. package/e2e/kitchen-sink/content/blog/2026-04-02-draft.md +0 -10
  226. package/e2e/kitchen-sink/content/blog/index.md +0 -8
  227. package/e2e/kitchen-sink/content/blog/no-frontmatter.md +0 -7
  228. package/e2e/kitchen-sink/content/docs/getting-started.md +0 -48
  229. package/e2e/kitchen-sink/content/index.md +0 -16
  230. package/e2e/kitchen-sink/server/api/echo.ts +0 -12
  231. package/e2e/kitchen-sink/server/api/health.ts +0 -3
  232. package/e2e/kitchen-sink/server/api/posts/[slug].ts +0 -11
  233. package/e2e/kitchen-sink/server/api/posts/index.ts +0 -5
  234. package/e2e/kitchen-sink/server/api/session.ts +0 -23
  235. package/e2e/kitchen-sink/server/data/posts.ts +0 -21
  236. package/e2e/kitchen-sink/server/middleware/01-headers.ts +0 -6
  237. package/e2e/kitchen-sink/server/middleware/02-status-test.ts +0 -10
  238. package/e2e/kitchen-sink/tsconfig.json +0 -3
  239. package/e2e/scripts/clean.mjs +0 -14
  240. package/eslint.config.ts +0 -22
  241. package/src/__tests__/cli/adapters/cloudflare-worker.integration.test.ts +0 -132
  242. package/src/__tests__/cli/adapters/cloudflare.test.ts +0 -332
  243. package/src/__tests__/cli/adapters/netlify-bridge.integration.test.ts +0 -139
  244. package/src/__tests__/cli/adapters/netlify.test.ts +0 -286
  245. package/src/__tests__/cli/adapters/vercel-launcher.integration.test.ts +0 -129
  246. package/src/__tests__/cli/adapters/vercel.test.ts +0 -263
  247. package/src/__tests__/cli/preview-hardening.test.ts +0 -265
  248. package/src/__tests__/cli/preview-isr.test.ts +0 -320
  249. package/src/__tests__/index.test.ts +0 -21
  250. package/src/__tests__/plugin/app-template.test.ts +0 -166
  251. package/src/__tests__/plugin/build-ssg-render.test.ts +0 -155
  252. package/src/__tests__/plugin/build-ssg.test.ts +0 -598
  253. package/src/__tests__/plugin/build-ssr.test.ts +0 -192
  254. package/src/__tests__/plugin/cer-app-plugin.test.ts +0 -552
  255. package/src/__tests__/plugin/content/emitter.test.ts +0 -117
  256. package/src/__tests__/plugin/content/loader.test.ts +0 -154
  257. package/src/__tests__/plugin/content/parser.test.ts +0 -381
  258. package/src/__tests__/plugin/content/path-utils.test.ts +0 -69
  259. package/src/__tests__/plugin/content/search.test.ts +0 -119
  260. package/src/__tests__/plugin/dev-server.test.ts +0 -813
  261. package/src/__tests__/plugin/dts-generator.test.ts +0 -394
  262. package/src/__tests__/plugin/entry-server-template.test.ts +0 -385
  263. package/src/__tests__/plugin/generated-dir.test.ts +0 -144
  264. package/src/__tests__/plugin/html-post-process.test.ts +0 -146
  265. package/src/__tests__/plugin/path-utils.test.ts +0 -411
  266. package/src/__tests__/plugin/resolve-config.test.ts +0 -233
  267. package/src/__tests__/plugin/scanner.test.ts +0 -186
  268. package/src/__tests__/plugin/transforms/auto-import.test.ts +0 -510
  269. package/src/__tests__/plugin/transforms/head-inject.test.ts +0 -178
  270. package/src/__tests__/plugin/virtual/composables.test.ts +0 -57
  271. package/src/__tests__/plugin/virtual/content-components.test.ts +0 -66
  272. package/src/__tests__/plugin/virtual/error.test.ts +0 -71
  273. package/src/__tests__/plugin/virtual/layouts.test.ts +0 -70
  274. package/src/__tests__/plugin/virtual/loading.test.ts +0 -72
  275. package/src/__tests__/plugin/virtual/middleware.test.ts +0 -83
  276. package/src/__tests__/plugin/virtual/plugins.test.ts +0 -129
  277. package/src/__tests__/plugin/virtual/routes.test.ts +0 -884
  278. package/src/__tests__/plugin/virtual/server-api.test.ts +0 -164
  279. package/src/__tests__/plugin/virtual/server-middleware.test.ts +0 -102
  280. package/src/__tests__/runtime/app-template.test.ts +0 -208
  281. package/src/__tests__/runtime/define-middleware.test.ts +0 -64
  282. package/src/__tests__/runtime/define-server-middleware.test.ts +0 -54
  283. package/src/__tests__/runtime/entry-client-template.test.ts +0 -61
  284. package/src/__tests__/runtime/isr-handler.test.ts +0 -338
  285. package/src/__tests__/runtime/oauth/handler.test.ts +0 -358
  286. package/src/__tests__/runtime/oauth/providers.test.ts +0 -47
  287. package/src/__tests__/runtime/oauth-index.test.ts +0 -43
  288. package/src/__tests__/runtime/use-auth.test.ts +0 -169
  289. package/src/__tests__/runtime/use-content-search-composable.test.ts +0 -405
  290. package/src/__tests__/runtime/use-content-search.test.ts +0 -163
  291. package/src/__tests__/runtime/use-content.test.ts +0 -226
  292. package/src/__tests__/runtime/use-cookie.test.ts +0 -277
  293. package/src/__tests__/runtime/use-fetch-component.test.ts +0 -214
  294. package/src/__tests__/runtime/use-fetch.test.ts +0 -377
  295. package/src/__tests__/runtime/use-head.test.ts +0 -311
  296. package/src/__tests__/runtime/use-inject-client.test.ts +0 -67
  297. package/src/__tests__/runtime/use-inject.test.ts +0 -66
  298. package/src/__tests__/runtime/use-locale.test.ts +0 -152
  299. package/src/__tests__/runtime/use-navigate.test.ts +0 -121
  300. package/src/__tests__/runtime/use-page-data.test.ts +0 -298
  301. package/src/__tests__/runtime/use-route.test.ts +0 -156
  302. package/src/__tests__/runtime/use-runtime-config.test.ts +0 -182
  303. package/src/__tests__/runtime/use-seo-meta.test.ts +0 -109
  304. package/src/__tests__/runtime/use-session.test.ts +0 -320
  305. package/src/__tests__/runtime/use-state.test.ts +0 -340
  306. package/src/__tests__/types/config.test.ts +0 -18
  307. package/src/cli/adapters/cloudflare.ts +0 -335
  308. package/src/cli/adapters/netlify.ts +0 -326
  309. package/src/cli/adapters/vercel.ts +0 -311
  310. package/src/cli/commands/adapt.ts +0 -84
  311. package/src/cli/commands/build.ts +0 -165
  312. package/src/cli/commands/dev.ts +0 -125
  313. package/src/cli/commands/generate.ts +0 -91
  314. package/src/cli/commands/preview-isr.ts +0 -172
  315. package/src/cli/commands/preview.ts +0 -523
  316. package/src/cli/create/index.ts +0 -275
  317. package/src/cli/create/templates/shared/.gitignore.tpl +0 -25
  318. package/src/cli/create/templates/shared/app/layouts/default.ts.tpl +0 -15
  319. package/src/cli/create/templates/shared/app/pages/index.ts.tpl +0 -8
  320. package/src/cli/create/templates/shared/index.html.tpl +0 -12
  321. package/src/cli/create/templates/shared/tsconfig.json.tpl +0 -6
  322. package/src/cli/create/templates/spa/cer.config.ts.tpl +0 -6
  323. package/src/cli/create/templates/spa/package.json.tpl +0 -18
  324. package/src/cli/create/templates/ssg/cer.config.ts.tpl +0 -10
  325. package/src/cli/create/templates/ssg/package.json.tpl +0 -19
  326. package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -6
  327. package/src/cli/create/templates/ssr/package.json.tpl +0 -18
  328. package/src/cli/index.ts +0 -22
  329. package/src/index.ts +0 -20
  330. package/src/plugin/build-ssg.ts +0 -403
  331. package/src/plugin/build-ssr.ts +0 -134
  332. package/src/plugin/content/emitter.ts +0 -50
  333. package/src/plugin/content/index.ts +0 -233
  334. package/src/plugin/content/parser.ts +0 -259
  335. package/src/plugin/content/path-utils.ts +0 -55
  336. package/src/plugin/content/scanner.ts +0 -26
  337. package/src/plugin/content/search.ts +0 -28
  338. package/src/plugin/dev-server.ts +0 -379
  339. package/src/plugin/dts-generator.ts +0 -273
  340. package/src/plugin/generated-dir.ts +0 -121
  341. package/src/plugin/html-post-process.ts +0 -96
  342. package/src/plugin/index.ts +0 -584
  343. package/src/plugin/path-utils.ts +0 -186
  344. package/src/plugin/scanner.ts +0 -65
  345. package/src/plugin/transforms/auto-import.ts +0 -206
  346. package/src/plugin/transforms/head-inject.ts +0 -161
  347. package/src/plugin/virtual/composables.ts +0 -28
  348. package/src/plugin/virtual/content-components.ts +0 -83
  349. package/src/plugin/virtual/error.ts +0 -34
  350. package/src/plugin/virtual/layouts.ts +0 -41
  351. package/src/plugin/virtual/loading.ts +0 -32
  352. package/src/plugin/virtual/middleware.ts +0 -45
  353. package/src/plugin/virtual/plugins.ts +0 -46
  354. package/src/plugin/virtual/routes.ts +0 -514
  355. package/src/plugin/virtual/server-api.ts +0 -85
  356. package/src/plugin/virtual/server-middleware.ts +0 -44
  357. package/src/runtime/app-template.ts +0 -377
  358. package/src/runtime/composables/define-middleware.ts +0 -17
  359. package/src/runtime/composables/define-server-middleware.ts +0 -18
  360. package/src/runtime/composables/index.ts +0 -27
  361. package/src/runtime/composables/use-auth.ts +0 -125
  362. package/src/runtime/composables/use-content-search.ts +0 -205
  363. package/src/runtime/composables/use-content.ts +0 -146
  364. package/src/runtime/composables/use-cookie.ts +0 -135
  365. package/src/runtime/composables/use-fetch.ts +0 -438
  366. package/src/runtime/composables/use-head.ts +0 -234
  367. package/src/runtime/composables/use-inject.ts +0 -49
  368. package/src/runtime/composables/use-locale.ts +0 -109
  369. package/src/runtime/composables/use-navigate.ts +0 -58
  370. package/src/runtime/composables/use-page-data.ts +0 -93
  371. package/src/runtime/composables/use-route.ts +0 -71
  372. package/src/runtime/composables/use-runtime-config.ts +0 -123
  373. package/src/runtime/composables/use-seo-meta.ts +0 -80
  374. package/src/runtime/composables/use-session.ts +0 -186
  375. package/src/runtime/composables/use-state.ts +0 -92
  376. package/src/runtime/content/client.ts +0 -168
  377. package/src/runtime/entry-client-template.ts +0 -39
  378. package/src/runtime/entry-server-template.ts +0 -493
  379. package/src/runtime/isr-handler.ts +0 -184
  380. package/src/runtime/oauth/handler.ts +0 -407
  381. package/src/runtime/oauth/index.ts +0 -49
  382. package/src/runtime/oauth/providers.ts +0 -38
  383. package/src/types/api.ts +0 -40
  384. package/src/types/config.ts +0 -379
  385. package/src/types/content.ts +0 -66
  386. package/src/types/index.ts +0 -7
  387. package/src/types/middleware.ts +0 -63
  388. package/src/types/page.ts +0 -117
  389. package/src/types/plugin.ts +0 -34
  390. package/tsconfig.build.json +0 -10
  391. package/tsconfig.json +0 -19
  392. package/vitest.config.ts +0 -33
@@ -1,907 +0,0 @@
1
- # Composables
2
-
3
- Composables are reusable reactive logic functions. Files in `app/composables/` are automatically re-exported as a barrel — you can import from `virtual:cer-composables` anywhere in your app.
4
-
5
- ---
6
-
7
- ## Creating a composable
8
-
9
- ```ts
10
- // app/composables/useTheme.ts
11
- import { ref, computed } from '@jasonshimmy/custom-elements-runtime'
12
-
13
- export function useTheme() {
14
- const theme = ref<'light' | 'dark'>('light')
15
-
16
- const isDark = computed(() => theme.value === 'dark')
17
-
18
- function toggle() {
19
- theme.value = theme.value === 'light' ? 'dark' : 'light'
20
- document.documentElement.setAttribute('data-theme', theme.value)
21
- }
22
-
23
- return { theme, isDark, toggle }
24
- }
25
- ```
26
-
27
- ---
28
-
29
- ## Using a composable in a page
30
-
31
- When `autoImports.composables` is `true` (the default), composables are **automatically imported** in page, layout, and component files — you don't need to write the import statement at all:
32
-
33
- ```ts
34
- // app/pages/index.ts
35
- // No import needed — useTheme is auto-imported from virtual:cer-composables
36
- component('page-index', () => {
37
- const { isDark, toggle } = useTheme()
38
-
39
- return html`
40
- <button @click="${toggle}">
41
- Switch to ${isDark.value ? 'light' : 'dark'} mode
42
- </button>
43
- `
44
- })
45
- ```
46
-
47
- If you need to import explicitly (e.g. in a file outside `app/pages/`, `app/layouts/`, or `app/components/`), import from `virtual:cer-composables`:
48
-
49
- ```ts
50
- import { useTheme } from 'virtual:cer-composables'
51
- ```
52
-
53
- ---
54
-
55
- ## Virtual module
56
-
57
- `virtual:cer-composables` is a generated barrel that re-exports everything from all files in `app/composables/`:
58
-
59
- ```ts
60
- // virtual:cer-composables (auto-generated)
61
- export * from "/project/app/composables/useTheme.ts"
62
- export * from "/project/app/composables/useAuth.ts"
63
- ```
64
-
65
- If `app/composables/` does not exist or is empty, the module exports nothing (no error).
66
-
67
- ---
68
-
69
- ## File naming
70
-
71
- Any `.ts` file in `app/composables/` (including subdirectories) is included. No naming convention is enforced, but `use` prefix is conventional:
72
-
73
- ```
74
- app/composables/
75
- useTheme.ts
76
- useAuth.ts
77
- data/
78
- usePosts.ts
79
- ```
80
-
81
- ---
82
-
83
- ## Side-effect safety
84
-
85
- Composables must be side-effect-free at import time. The barrel module is imported during app initialization, before components render. Any code that runs immediately at module scope will execute at that point.
86
-
87
- **Safe:**
88
-
89
- ```ts
90
- // app/composables/useCounter.ts
91
- export function useCounter() {
92
- const count = ref(0)
93
- return { count }
94
- }
95
- ```
96
-
97
- **Avoid:**
98
-
99
- ```ts
100
- // app/composables/useSession.ts
101
- // ⚠️ This runs at import time, before the DOM is ready
102
- const session = await fetchSession()
103
- export function useSession() { return session }
104
- ```
105
-
106
- Use `useOnConnected` or lazy initialization inside the function body for side effects.
107
-
108
- ---
109
-
110
- ## Built-in framework composables
111
-
112
- These composables are provided by the framework and auto-imported alongside the runtime. They do **not** live in `app/composables/` — they are injected from `@jasonshimmy/vite-plugin-cer-app/composables`.
113
-
114
- ### Auto-import scope
115
-
116
- Built-in composables are auto-imported in the following directories:
117
-
118
- | Directory | Auto-imported |
119
- |---|---|
120
- | `app/pages/` | ✅ |
121
- | `app/layouts/` | ✅ |
122
- | `app/components/` | ✅ |
123
- | `app/middleware/` | ✅ |
124
- | `app/composables/` | ✅ |
125
- | `server/middleware/` | ✅ |
126
- | `server/api/` | ❌ — import explicitly |
127
-
128
- Files in `server/api/` are not in the auto-import scope. To use composables there, import them explicitly:
129
-
130
- ```ts
131
- import { useSession, useCookie } from '@jasonshimmy/vite-plugin-cer-app/composables'
132
- ```
133
-
134
- See [server-api.md](./server-api.md) for details and usage examples.
135
-
136
- ### `useFetch<T>(url, options?)`
137
-
138
- Isomorphic data-fetching composable:
139
-
140
- - **Inside a `component()` render function** — returns reactive `data`, `pending`, and `error` refs that re-render the component automatically when the request settles.
141
- - **Inside a `loader` or other async context** — returns a thenable result you can `await` to block SSR rendering until data is ready.
142
-
143
- ```ts
144
- component('page-posts', () => {
145
- const { data: posts, pending, error } = useFetch<Post[]>('/api/posts')
146
-
147
- return html`
148
- ${when(pending.value, () => html`<p>Loading…</p>`)}
149
- ${when(!!error.value, () => html`<p>Error: ${error.value!.message}</p>`)}
150
- <ul>${each(posts.value ?? [], p => html`<li>${p.title}</li>`)}</ul>
151
- `
152
- })
153
- ```
154
-
155
- See [use-fetch.md](./use-fetch.md) for full documentation including options, lazy fetching, POST requests, and TypeScript types.
156
-
157
- ---
158
-
159
- ### `useAuth(sessionKey?)`
160
-
161
- Returns the authenticated user and helpers for login/logout. Works isomorphically — on the server it reads the auth session from the per-request context; on the client it reads from the value injected into the HTML at render time.
162
-
163
- ```ts
164
- component('page-nav', () => {
165
- const { user, loggedIn, login, logout } = useAuth()
166
-
167
- return html`
168
- ${match()
169
- .when(loggedIn, () => html`<span>${user?.name}</span><button @click="${logout}">Log out</button>`)
170
- .otherwise(() => html`<button @click="${() => login('github')}">Log in</button>`)
171
- .done()}
172
- `
173
- })
174
- ```
175
-
176
- Requires `auth` configuration in `cer.config.ts`. See [authentication.md](./authentication.md) for setup, OAuth providers, middleware guards, and TypeScript types.
177
-
178
- ---
179
-
180
- ### `useHead(input)`
181
-
182
- Sets document head tags (`<title>`, `<meta>`, `<link>`, etc.). Works in SPA, SSR, and SSG modes. See [head-management.md](./head-management.md).
183
-
184
- ### `usePageData<T>()`
185
-
186
- Returns the serialized loader data for the current page, hydrated from `window.__CER_DATA__` on the client or from the per-request `AsyncLocalStorage` context during SSR/SSG. See [data-loading.md](./data-loading.md).
187
-
188
- ### `useInject<T>(key, defaultValue?): T | undefined`
189
-
190
- Reads a value provided by a plugin via `app.provide(key, value)`. Works consistently in all rendering modes:
191
-
192
- - **SPA / client** — resolves via `inject()` from the component context tree.
193
- - **SSR / SSG** — reads from `globalThis.__cerPluginProvides`, populated by the server entry before the first render.
194
-
195
- ```ts
196
- // app/pages/dashboard.ts
197
- component('page-dashboard', () => {
198
- const store = useInject<Store>('store')
199
- return html`<p>Count: ${store?.state.count ?? 0}</p>`
200
- })
201
- ```
202
-
203
- If you need it outside auto-imported directories, import explicitly:
204
-
205
- ```ts
206
- import { useInject } from '@jasonshimmy/vite-plugin-cer-app/composables'
207
- ```
208
-
209
- > **Note:** Prefer `useInject` over the raw `inject()` primitive whenever reading plugin-provided values. Raw `inject()` works in SPA mode but returns `undefined` in SSR and SSG because the server renders components without `<cer-layout-view>`'s provide context.
210
-
211
- ### `useRuntimeConfig()`
212
-
213
- Returns the runtime configuration set in `cer.config.ts`. Returns `{ public, private? }`:
214
- - `public` — available everywhere (server and client)
215
- - `private` — server-only secrets resolved from `process.env` at startup; `undefined` on the client
216
-
217
- ```ts
218
- // cer.config.ts
219
- export default defineConfig({
220
- runtimeConfig: {
221
- public: {
222
- apiBase: process.env.VITE_API_BASE ?? '/api',
223
- },
224
- private: {
225
- dbUrl: '', // resolved from process.env.DB_URL at server startup
226
- },
227
- },
228
- })
229
- ```
230
-
231
- ```ts
232
- // app/pages/index.ts — public config, works on client and server
233
- component('page-index', () => {
234
- const { public: cfg } = useRuntimeConfig()
235
- return html`<p>API base: ${cfg.apiBase}</p>`
236
- })
237
- ```
238
-
239
- ```ts
240
- // app/pages/data.ts — private config, server-only (loader)
241
- export const loader = async () => {
242
- const { private: priv } = useRuntimeConfig()
243
- const rows = await db.query(priv?.dbUrl)
244
- return { rows }
245
- }
246
- ```
247
-
248
- **Only use `runtimeConfig.public` for values safe to expose to the browser.** Use `runtimeConfig.private` for secrets — they are never sent to the client.
249
-
250
- Keys declared in `runtimeConfig.private` with an empty-string default are treated as **required** secrets. If the corresponding environment variable is not set at server startup, a warning is logged:
251
-
252
- ```
253
- [cer-app] runtimeConfig.private: "dbUrl" is an empty string — set DB_URL in the environment to provide a value.
254
- ```
255
-
256
- If the default is a non-empty string it is used as a genuine fallback — no warning is emitted.
257
-
258
- If you need it outside auto-imported directories:
259
-
260
- ```ts
261
- import { useRuntimeConfig } from '@jasonshimmy/vite-plugin-cer-app/composables'
262
- ```
263
-
264
- ---
265
-
266
- ### `useSeoMeta(input)`
267
-
268
- Thin wrapper over `useHead()` for the most common SEO tags — Open Graph, Twitter Card, meta description, and canonical URL. Works in SPA, SSR, and SSG modes.
269
-
270
- ```ts
271
- // app/pages/about.ts
272
- component('page-about', () => {
273
- useSeoMeta({
274
- title: 'About Us',
275
- description: 'Learn more about our team.',
276
- ogTitle: 'About Us — My Site',
277
- ogDescription: 'Learn more about our team.',
278
- ogImage: 'https://example.com/og/about.png',
279
- ogUrl: 'https://example.com/about',
280
- ogType: 'website',
281
- ogSiteName: 'My Site',
282
- twitterCard: 'summary_large_image',
283
- twitterSite: '@mysite',
284
- canonical: 'https://example.com/about',
285
- })
286
- return html`<h1>About Us</h1>`
287
- })
288
- ```
289
-
290
- Only properties you set are emitted — passing `undefined` (or omitting a property entirely) skips that tag.
291
-
292
- #### `SeoMetaInput` fields
293
-
294
- | Field | Tag emitted |
295
- |---|---|
296
- | `title` | `<title>` |
297
- | `description` | `<meta name="description">` |
298
- | `ogTitle` | `<meta property="og:title">` |
299
- | `ogDescription` | `<meta property="og:description">` |
300
- | `ogImage` | `<meta property="og:image">` |
301
- | `ogUrl` | `<meta property="og:url">` |
302
- | `ogType` | `<meta property="og:type">` |
303
- | `ogSiteName` | `<meta property="og:site_name">` |
304
- | `twitterCard` | `<meta name="twitter:card">` |
305
- | `twitterTitle` | `<meta name="twitter:title">` |
306
- | `twitterDescription` | `<meta name="twitter:description">` |
307
- | `twitterImage` | `<meta name="twitter:image">` |
308
- | `twitterSite` | `<meta name="twitter:site">` |
309
- | `canonical` | `<link rel="canonical">` |
310
-
311
- If you need it outside auto-imported directories:
312
-
313
- ```ts
314
- import { useSeoMeta } from '@jasonshimmy/vite-plugin-cer-app/composables'
315
- ```
316
-
317
- TypeScript types:
318
-
319
- ```ts
320
- import type { SeoMetaInput } from '@jasonshimmy/vite-plugin-cer-app/types'
321
- ```
322
-
323
- ---
324
-
325
- ### `useCookie(name, options?)`
326
-
327
- Isomorphic cookie composable. Reads and writes cookies transparently on both server and client:
328
-
329
- - **Server (SSR/SSG):** reads `req.headers.cookie`; writes `Set-Cookie` response headers via `res.setHeader`.
330
- - **Client:** reads and writes `document.cookie`.
331
-
332
- ```ts
333
- // app/pages/profile.ts
334
- component('page-profile', () => {
335
- const session = useCookie('session')
336
-
337
- // Read
338
- console.log(session.value) // 'abc123' | undefined
339
-
340
- // Write
341
- session.set('abc123', { httpOnly: true, sameSite: 'Strict' })
342
-
343
- // Remove
344
- session.remove()
345
-
346
- return html`<p>Session: ${session.value ?? 'none'}</p>`
347
- })
348
- ```
349
-
350
- #### `CookieRef`
351
-
352
- | Member | Type | Description |
353
- |---|---|---|
354
- | `value` | `string \| undefined` | Current cookie value (read at call time) |
355
- | `set(value, options?)` | `void` | Write the cookie |
356
- | `remove(options?)` | `void` | Delete the cookie (sets `Max-Age=0`) |
357
-
358
- #### `CookieOptions`
359
-
360
- | Option | Type | Description |
361
- |---|---|---|
362
- | `path` | `string` | Cookie path (defaults to `/` when setting/removing) |
363
- | `domain` | `string` | Cookie domain |
364
- | `maxAge` | `number` | Max age in seconds |
365
- | `expires` | `Date` | Expiry date |
366
- | `httpOnly` | `boolean` | Set `HttpOnly` flag |
367
- | `secure` | `boolean` | Set `Secure` flag |
368
- | `sameSite` | `'Strict' \| 'Lax' \| 'None'` | `SameSite` attribute |
369
-
370
- Default options can be passed as the second argument to `useCookie` — they are merged with options passed to `set()`/`remove()`:
371
-
372
- ```ts
373
- const auth = useCookie('auth', { httpOnly: true, secure: true, sameSite: 'Strict' })
374
- auth.set('tok') // inherits httpOnly, secure, sameSite automatically
375
- ```
376
-
377
- If you need it outside auto-imported directories:
378
-
379
- ```ts
380
- import { useCookie } from '@jasonshimmy/vite-plugin-cer-app/composables'
381
- ```
382
-
383
- TypeScript types:
384
-
385
- ```ts
386
- import type { CookieOptions, CookieRef } from '@jasonshimmy/vite-plugin-cer-app/types'
387
- ```
388
-
389
- ---
390
-
391
- ### `defineMiddleware(fn)`
392
-
393
- Identity helper that gives TypeScript the correct `MiddlewareFn` type. Auto-imported — no import needed in `app/middleware/` files.
394
-
395
- ```ts
396
- // app/middleware/auth.ts
397
- export default defineMiddleware(async (to, from) => {
398
- const { loggedIn } = useAuth()
399
- return loggedIn ? true : '/login'
400
- })
401
- ```
402
-
403
- ---
404
-
405
- ### `defineServerMiddleware(fn)`
406
-
407
- Identity helper for server-side middleware. Files in `server/middleware/` export a default `defineServerMiddleware()` function. They run in **alphabetical filename order** on every SSR and API request, before routing — in all environments (dev server, Vercel, Netlify, Cloudflare).
408
-
409
- ```ts
410
- // server/middleware/01-cors.ts
411
- export default defineServerMiddleware((req, res, next) => {
412
- res.setHeader('Access-Control-Allow-Origin', '*')
413
- next()
414
- })
415
-
416
- // server/middleware/02-auth.ts
417
- export default defineServerMiddleware(async (req, res, next) => {
418
- const session = useSession<{ userId: string }>()
419
- const data = await session.get()
420
- if (!data?.userId) { res.statusCode = 401; res.end('Unauthorized'); return }
421
- ;(req as any).user = data
422
- next()
423
- })
424
- ```
425
-
426
- Call `next()` to continue to the next middleware or request handler. Write the response without calling `next()` to short-circuit the chain. Calling `next(err)` with an error sends a `500` response.
427
-
428
- If you need it outside auto-imported directories:
429
-
430
- ```ts
431
- import { defineServerMiddleware } from '@jasonshimmy/vite-plugin-cer-app/composables'
432
- ```
433
-
434
- ---
435
-
436
- ### `useSession<T>(options?)`
437
-
438
- HMAC-SHA-256 signed cookie session. Stores JSON-serialisable session data in a single `httpOnly` cookie, signed with a secret key. Works isomorphically: on the server it reads/writes HTTP headers; on the client it reads the cookie from `document.cookie`.
439
-
440
- **Setup:** declare the signing key in `runtimeConfig.private`:
441
-
442
- ```ts
443
- // cer.config.ts
444
- export default defineConfig({
445
- runtimeConfig: {
446
- private: { sessionSecret: '' }, // resolved from SESSION_SECRET env var at startup
447
- },
448
- })
449
- ```
450
-
451
- **Secret rotation:** `sessionSecret` accepts a `string | string[]`. When an array is provided, the framework signs new sessions with the **first** secret and verifies incoming tokens against **all** secrets in order. This lets you rotate keys without immediately invalidating all existing sessions:
452
-
453
- ```ts
454
- // cer.config.ts
455
- export default defineConfig({
456
- runtimeConfig: {
457
- private: {
458
- sessionSecret: [
459
- process.env.SESSION_SECRET_NEW!, // active key — signs all new sessions
460
- process.env.SESSION_SECRET_OLD!, // accepted during rotation window
461
- ],
462
- },
463
- },
464
- })
465
- ```
466
-
467
- Rotation workflow:
468
- 1. Generate a new secret and add it as the **first** element; keep the old secret as the second.
469
- 2. Deploy. New sessions are signed with the new key; existing sessions signed with the old key are still accepted.
470
- 3. After all old sessions have expired (or after your desired rotation window), remove the second element.
471
-
472
- A token is rejected only when it fails verification against every secret in the array.
473
-
474
- **Usage:**
475
-
476
- ```ts
477
- // server/middleware/auth.ts — validate session on every request
478
- export default defineServerMiddleware(async (req, res, next) => {
479
- const session = useSession<{ userId: string; role: string }>()
480
- const data = await session.get()
481
- if (!data?.userId) { res.statusCode = 401; res.end(); return }
482
- next()
483
- })
484
-
485
- // app/pages/login.ts — create session after verifying credentials
486
- export const loader = async ({ req }) => {
487
- // ... verify credentials
488
- const session = useSession<{ userId: string }>()
489
- await session.set({ userId: user.id })
490
- return { ok: true }
491
- }
492
-
493
- // app/pages/logout.ts
494
- export const loader = async () => {
495
- await useSession().clear()
496
- return { ok: true }
497
- }
498
- ```
499
-
500
- | Method | Returns | Description |
501
- |---|---|---|
502
- | `get()` | `Promise<T \| null>` | Reads and verifies the session cookie. Returns data or `null` if absent/invalid/tampered. |
503
- | `set(data)` | `Promise<void>` | Signs `data` and writes the session cookie. Replaces any existing session. |
504
- | `clear()` | `Promise<void>` | Clears the session cookie by setting `maxAge = 0`. |
505
-
506
- #### `SessionOptions`
507
-
508
- | Option | Type | Default | Description |
509
- |---|---|---|---|
510
- | `name` | `string` | `'session'` | Cookie name |
511
- | `maxAge` | `number` | `604800` (7 days) | Cookie max-age in seconds |
512
-
513
- If you need it outside auto-imported directories:
514
-
515
- ```ts
516
- import { useSession } from '@jasonshimmy/vite-plugin-cer-app/composables'
517
- import type { SessionOptions, SessionComposable } from '@jasonshimmy/vite-plugin-cer-app/composables'
518
- ```
519
-
520
-
521
- See [Middleware](./middleware.md) for full documentation.
522
-
523
- ---
524
-
525
- ### `useRoute()`
526
-
527
- Returns the current route's `path`, `params`, `query`, and `meta` — works isomorphically in all rendering modes.
528
-
529
- - **Server (SSR/SSG)** — reads from the per-request `AsyncLocalStorage` context populated before the page renders.
530
- - **Client** — reads from the router instance exposed by the framework.
531
-
532
- ```ts
533
- // app/layouts/default.ts — display page title from route meta
534
- component('layout-default', () => {
535
- const route = useRoute()
536
-
537
- return html`
538
- <header>
539
- <h1>${route.meta?.title ?? 'My App'}</h1>
540
- </header>
541
- <main><slot></slot></main>
542
- `
543
- })
544
- ```
545
-
546
- ```ts
547
- // app/pages/post.ts — use dynamic route params
548
- component('page-post', () => {
549
- const { params } = useRoute()
550
- const { data: post } = useFetch(`/api/posts/${params.id}`)
551
- return html`<h1>${post.value?.title}</h1>`
552
- })
553
- ```
554
-
555
- #### `RouteInfo`
556
-
557
- | Field | Type | Description |
558
- |---|---|---|
559
- | `path` | `string` | Current URL path, e.g. `'/posts/42'` |
560
- | `params` | `Record<string, string>` | Dynamic route params, e.g. `{ id: '42' }` |
561
- | `query` | `Record<string, string>` | Parsed query string, e.g. `{ page: '2' }` |
562
- | `meta` | `Record<string, unknown> \| null` | The raw `meta` object exported by the matched page file. Custom fields like `title`, `render`, `hydrate`, etc., are accessible here. |
563
-
564
- If you need it outside auto-imported directories:
565
-
566
- ```ts
567
- import { useRoute } from '@jasonshimmy/vite-plugin-cer-app/composables'
568
- import type { RouteInfo } from '@jasonshimmy/vite-plugin-cer-app/composables'
569
- ```
570
-
571
- ---
572
-
573
- ### `useState<T>(key, init?)`
574
-
575
- Globally-keyed reactive state shared across layouts, pages, and components. Any two components that call `useState` with the same key get the **same reactive ref** — mutating `.value` in one component automatically re-renders all components that read it.
576
-
577
- Works isomorphically:
578
- - **SSR/SSG** — state is scoped per-request via `AsyncLocalStorage`. Set initial values inside a page `loader` (which runs before rendering) so the layout can read them synchronously during the server render pass. After rendering, all state values are serialized into `window.__CER_STATE_INIT__` and hydrated on the client.
579
- - **Client** — state lives in a singleton `Map` on `globalThis`. On first use, the Map is pre-populated from `window.__CER_STATE_INIT__` (the SSR snapshot) so there is no flash to default values after hydration. Mutations propagate reactively to all components sharing the key.
580
-
581
- **Key contract:** the `init` value (or factory) is only evaluated when the key does not yet exist. Subsequent calls with the same key return the existing ref; `init` is ignored.
582
-
583
- > **SSR → client:** State set in a page `loader` is serialized from server to client via `window.__CER_STATE_INIT__`. On first `useState()` call, the client Map is pre-populated from this snapshot — the layout renders with the correct value immediately after hydration, with no flash to default values.
584
- >
585
- > State set in a component render function IS included in the snapshot (render functions execute during SSR before state is serialized), but the layout's initial HTML will still show its fallback value — the layout has already rendered its HTML by the time the page component executes. The reactive system updates the layout after client hydration.
586
- >
587
- > State set in `useOnConnected` is **not** in the snapshot — `useOnConnected` never fires during SSR since there is no DOM. **Always set page-specific state in the `loader`** — it runs before rendering begins, so both the initial SSR HTML and the hydration snapshot contain the correct value.
588
-
589
- #### Page-to-layout communication
590
-
591
- The primary use case is passing reactive metadata (title, breadcrumbs, etc.) from a page to its layout:
592
-
593
- ```ts
594
- // app/pages/about.ts
595
- // Set in the loader so it's available during SSR rendering (layout renders before page)
596
- export const loader = async () => {
597
- useState<string>('pageTitle').value = 'About Us'
598
- return {}
599
- }
600
-
601
- component('page-about', () => {
602
- const title = useState<string>('pageTitle')
603
- return html`<h1>${title.value}</h1>`
604
- })
605
- ```
606
-
607
- ```ts
608
- // app/layouts/default.ts
609
- component('layout-default', () => {
610
- // 'My App' is the fallback shown before any page sets a title
611
- const pageTitle = useState('pageTitle', 'My App')
612
-
613
- return html`
614
- <header><h1>${pageTitle.value}</h1></header>
615
- <slot></slot>
616
- `
617
- })
618
- ```
619
-
620
- On **SSR/SSG**: the loader runs first → sets the state → layout reads it synchronously → initial HTML already contains the correct title.
621
- On **client**: the page sets the state → the layout's reactive dependency fires → layout re-renders.
622
-
623
- #### Shared counter across components
624
-
625
- ```ts
626
- // app/composables/useSharedCount.ts
627
- export function useSharedCount() {
628
- const count = useState('sharedCount', 0)
629
- const increment = () => { count.value++ }
630
- return { count, increment }
631
- }
632
- ```
633
-
634
- ```ts
635
- // app/components/counter-a.ts
636
- component('counter-a', () => {
637
- const { count, increment } = useSharedCount()
638
- return html`<button @click="${increment}">Count: ${count.value}</button>`
639
- })
640
-
641
- // app/components/counter-b.ts — same key, same ref, same value
642
- component('counter-b', () => {
643
- const { count } = useSharedCount()
644
- return html`<p>Count seen by B: ${count.value}</p>`
645
- })
646
- ```
647
-
648
- Clicking the button in `counter-a` updates `counter-b` automatically.
649
-
650
- #### SSR rendering order
651
-
652
- On SSR, layouts render **before** pages (outer-to-inner vnode tree). If state is set inside a page's `component()` render function it will be too late for the layout to read it during SSR. **Always set page-specific state in the `loader`** so it is written before rendering begins.
653
-
654
- #### Hard refresh behavior
655
-
656
- `useState` is **in-memory only**. A hard browser refresh wipes `globalThis.__CER_STATE__` and starts fresh — exactly like the first page load. Whether values are available immediately after a refresh depends on where they are set:
657
-
658
- | Where value is set | Available to layout on SSR? | Available after hard refresh? |
659
- |---|---|---|
660
- | Page `loader` | ✅ | ✅ (loader re-runs) |
661
- | `init` param of `useState` | ✅ (fallback default) | ✅ (init re-evaluated) |
662
- | Component render function | ❌ (layout renders first) | ✅ (brief flash, then reactive update) |
663
- | `useOnConnected` | ❌ (never fires during SSR) | ✅ (brief flash, then reactive update) |
664
-
665
- **Values are not persisted across hard refreshes.** If a user changes a theme from `'light'` to `'dark'` and then hard-refreshes, they get `'light'` (the init) again.
666
-
667
- To persist state across refreshes, seed the init from a persistent store using the **factory form** — it is only evaluated once per session (when the key does not yet exist in the Map):
668
-
669
- ```ts
670
- export function useTheme() {
671
- const theme = useState<string>('theme', () =>
672
- (typeof localStorage !== 'undefined' ? localStorage.getItem('theme') : null) ?? 'light'
673
- )
674
-
675
- function setTheme(value: string) {
676
- theme.value = value
677
- if (typeof localStorage !== 'undefined') {
678
- localStorage.setItem('theme', value)
679
- }
680
- }
681
-
682
- return { theme, setTheme }
683
- }
684
- ```
685
-
686
- #### TypeScript
687
-
688
- ```ts
689
- const pageTitle = useState<string>('pageTitle', 'My App')
690
- // pageTitle.value is typed as string
691
- ```
692
-
693
- If you need it outside auto-imported directories:
694
-
695
- ```ts
696
- import { useState } from '@jasonshimmy/vite-plugin-cer-app/composables'
697
- ```
698
-
699
- ---
700
-
701
- ### `useLocale()`
702
-
703
- Returns locale information for the current request and helpers to build locale-aware URLs. Works isomorphically — on the server it reads the URL path via `AsyncLocalStorage`; on the client it reads `window.location.pathname`.
704
-
705
- **Requires `i18n` configuration in `cer.config.ts`.** If `i18n` is not configured, `locale` and `defaultLocale` both return `'en'` and `locales` returns `['en']`.
706
-
707
- ```ts
708
- const {
709
- locale, // 'fr' — current locale code
710
- locales, // ['en', 'fr'] — all configured locales
711
- defaultLocale, // 'en' — the default locale
712
- switchLocalePath, // function — build a URL in another locale
713
- } = useLocale()
714
- ```
715
-
716
- #### `switchLocalePath(targetLocale, path?)`
717
-
718
- Returns the equivalent path in the target locale. When `path` is omitted the current pathname is used.
719
-
720
- ```ts
721
- // On /fr/about
722
- switchLocalePath('en') // → '/about'
723
- switchLocalePath('en', '/fr/about') // → '/about'
724
- switchLocalePath('de', '/fr/about') // → '/de/about'
725
-
726
- // On /about (default locale)
727
- switchLocalePath('fr') // → '/fr/about'
728
- ```
729
-
730
- #### Locale switcher example
731
-
732
- ```ts
733
- // app/components/locale-switcher.ts
734
- component('locale-switcher', () => {
735
- const { locale, locales, switchLocalePath } = useLocale()
736
-
737
- return html`
738
- <nav>
739
- ${each(locales, (l) => html`
740
- <a
741
- :href="${switchLocalePath(l)}"
742
- :aria-current="${l === locale ? 'true' : 'false'}"
743
- >${l.toUpperCase()}</a>
744
- `)}
745
- </nav>
746
- `
747
- })
748
- ```
749
-
750
- #### `LocaleComposable`
751
-
752
- | Field | Type | Description |
753
- |---|---|---|
754
- | `locale` | `string` | Current locale code detected from the URL |
755
- | `locales` | `string[]` | All configured locale codes |
756
- | `defaultLocale` | `string` | The configured default locale |
757
- | `switchLocalePath` | `(target: string, path?: string) => string` | Returns the path rewritten for `target` locale |
758
-
759
- If you need it outside auto-imported directories:
760
-
761
- ```ts
762
- import { useLocale } from '@jasonshimmy/vite-plugin-cer-app/composables'
763
- import type { LocaleComposable } from '@jasonshimmy/vite-plugin-cer-app/types'
764
- ```
765
-
766
- See [i18n.md](./i18n.md) for full documentation including strategies, SSR/SSG behavior, and edge cases.
767
-
768
- ---
769
-
770
- ### `navigateTo(path): Promise<void>`
771
-
772
- Programmatic navigation — works isomorphically:
773
-
774
- - **Server context** (inside a loader or middleware): sends a `302` redirect immediately via the request's response object.
775
- - **Client context**: delegates to the framework router so the full navigation pipeline (middleware, loaders, loading indicator) runs normally.
776
-
777
- ```ts
778
- // app/middleware/require-auth.ts
779
- export default defineMiddleware(() => {
780
- const { loggedIn } = useAuth()
781
- if (!loggedIn) return navigateTo('/login')
782
- })
783
- ```
784
-
785
- ```ts
786
- // app/pages/dashboard.ts — navigate programmatically on a button click
787
- component('page-dashboard', () => {
788
- return html`
789
- <button @click="${() => navigateTo('/settings')}">
790
- Open Settings
791
- </button>
792
- `
793
- })
794
- ```
795
-
796
- If you need it outside auto-imported directories:
797
-
798
- ```ts
799
- import { navigateTo } from '@jasonshimmy/vite-plugin-cer-app/composables'
800
- ```
801
-
802
- ---
803
-
804
- ### `queryContent(path?)`
805
-
806
- Queries content items from the file-based content layer. Returns a `QueryBuilder` that can be filtered, sorted, paginated, and terminated with `.find()`, `.first()`, or `.count()`.
807
-
808
- Requires `content: {}` in `cer.config.ts`. See [content.md](./content.md) for full configuration, type reference, and rendering-mode behavior.
809
-
810
- ```ts
811
- // All items
812
- const all = await queryContent().find()
813
-
814
- // Blog posts only (path prefix)
815
- const posts = await queryContent('/blog').sortBy('date', 'desc').find()
816
-
817
- // Single full document (includes body + TOC)
818
- const doc = await queryContent('/docs/getting-started').first()
819
-
820
- // Count
821
- const total = await queryContent().count()
822
- ```
823
-
824
- **QueryBuilder methods:**
825
-
826
- | Method | Returns | Description |
827
- |---|---|---|
828
- | `.where(predicate)` | `QueryBuilder` | Predicate function — `(doc: ContentMeta) => boolean`. |
829
- | `.sortBy(field, order?)` | `QueryBuilder` | Sort ascending (`'asc'`) or descending (`'desc'`). |
830
- | `.limit(n)` | `QueryBuilder` | Return at most `n` items. |
831
- | `.skip(n)` | `QueryBuilder` | Skip the first `n` items (pagination). |
832
- | `.find()` | `Promise<ContentMeta[]>` | Execute, return lean metadata array. |
833
- | `.first()` | `Promise<ContentItem \| null>` | Execute, return first full document with body and TOC. |
834
- | `.count()` | `Promise<number>` | Execute, return count only. |
835
-
836
- **Usage with a page loader:**
837
-
838
- ```ts
839
- component('page-blog', () => {
840
- const ssrData = usePageData<{ posts: ContentMeta[] }>()
841
- const posts = ref<ContentMeta[]>(ssrData?.posts ?? [])
842
-
843
- useOnConnected(async () => {
844
- if (ssrData) return // hydrated from loader
845
- posts.value = await queryContent('/blog').find()
846
- })
847
-
848
- return html`
849
- <ul>
850
- ${each(posts.value, p => html`<li><a :href="${p._path}">${p.title}</a></li>`)}
851
- </ul>
852
- `
853
- })
854
-
855
- export const loader = async () => {
856
- const posts = await queryContent('/blog').find()
857
- return { posts }
858
- }
859
- ```
860
-
861
- If you need it outside auto-imported directories:
862
-
863
- ```ts
864
- import { queryContent } from '@jasonshimmy/vite-plugin-cer-app/composables'
865
- ```
866
-
867
- ---
868
-
869
- ### `useContentSearch()`
870
-
871
- Reactive full-text search over the content layer. Loads the MiniSearch index lazily on first use. Returns `query`, `results`, and `loading` refs that update reactively as the user types.
872
-
873
- Requires `content: {}` in `cer.config.ts`. See [content.md](./content.md) for full documentation.
874
-
875
- ```ts
876
- component('page-search', () => {
877
- const { query, results, loading } = useContentSearch()
878
-
879
- return html`
880
- <input type="search" :model="${query}" placeholder="Search…" />
881
- ${loading.value ? html`<p>Searching…</p>` : ''}
882
- <ul>
883
- ${each(results.value, r => html`
884
- <li><a :href="${r._path}">${r.title}</a></li>
885
- `)}
886
- </ul>
887
- `
888
- })
889
- ```
890
-
891
- **Return value:**
892
-
893
- ```ts
894
- interface UseContentSearchReturn {
895
- query: Ref<string> // bind with :model
896
- results: Ref<ContentSearchResult[]> // reactive search results
897
- loading: Ref<boolean> // true while debounce is pending or search is in flight
898
- }
899
- ```
900
-
901
- Search is debounced (200 ms) so the index is not queried on every keystroke. `loading` is set to `true` as soon as the user starts typing and returns to `false` once results arrive or the query is cleared. An empty query clears results immediately and cancels any in-flight search. MiniSearch is loaded once and cached for the lifetime of the page. Searched fields are `title` and `description`.
902
-
903
- If you need it outside auto-imported directories:
904
-
905
- ```ts
906
- import { useContentSearch } from '@jasonshimmy/vite-plugin-cer-app/composables'
907
- ```