@jasonshimmy/vite-plugin-cer-app 0.23.1 → 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 (397) 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/commands/preview.d.ts.map +1 -1
  18. package/dist/cli/commands/preview.js +37 -4
  19. package/dist/cli/commands/preview.js.map +1 -1
  20. package/dist/cli/create/index.d.ts +11 -1
  21. package/dist/cli/create/index.d.ts.map +1 -1
  22. package/dist/cli/create/index.js +135 -65
  23. package/dist/cli/create/index.js.map +1 -1
  24. package/dist/cli/create/templates/spa/package.json.tpl +5 -3
  25. package/dist/cli/create/templates/ssg/cer.config.ts.tpl +2 -0
  26. package/dist/cli/create/templates/ssg/package.json.tpl +5 -3
  27. package/dist/cli/create/templates/ssr/package.json.tpl +5 -3
  28. package/dist/cli/index.js +2 -0
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/index.d.ts +3 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/plugin/build-ssg.d.ts +22 -4
  33. package/dist/plugin/build-ssg.d.ts.map +1 -1
  34. package/dist/plugin/build-ssg.js +273 -13
  35. package/dist/plugin/build-ssg.js.map +1 -1
  36. package/dist/plugin/content/parser.d.ts.map +1 -1
  37. package/dist/plugin/content/parser.js +54 -10
  38. package/dist/plugin/content/parser.js.map +1 -1
  39. package/dist/plugin/dev-server.d.ts +6 -1
  40. package/dist/plugin/dev-server.d.ts.map +1 -1
  41. package/dist/plugin/dev-server.js.map +1 -1
  42. package/dist/plugin/dts-generator.d.ts.map +1 -1
  43. package/dist/plugin/dts-generator.js +50 -8
  44. package/dist/plugin/dts-generator.js.map +1 -1
  45. package/dist/plugin/generated-dir.d.ts +2 -0
  46. package/dist/plugin/generated-dir.d.ts.map +1 -1
  47. package/dist/plugin/generated-dir.js +17 -6
  48. package/dist/plugin/generated-dir.js.map +1 -1
  49. package/dist/plugin/html-post-process.d.ts +35 -0
  50. package/dist/plugin/html-post-process.d.ts.map +1 -0
  51. package/dist/plugin/html-post-process.js +112 -0
  52. package/dist/plugin/html-post-process.js.map +1 -0
  53. package/dist/plugin/index.d.ts +9 -0
  54. package/dist/plugin/index.d.ts.map +1 -1
  55. package/dist/plugin/index.js +89 -14
  56. package/dist/plugin/index.js.map +1 -1
  57. package/dist/plugin/transforms/auto-import.d.ts.map +1 -1
  58. package/dist/plugin/transforms/auto-import.js +7 -0
  59. package/dist/plugin/transforms/auto-import.js.map +1 -1
  60. package/dist/plugin/transforms/prune-runtime-css.d.ts +8 -0
  61. package/dist/plugin/transforms/prune-runtime-css.d.ts.map +1 -0
  62. package/dist/plugin/transforms/prune-runtime-css.js +40 -0
  63. package/dist/plugin/transforms/prune-runtime-css.js.map +1 -0
  64. package/dist/runtime/app-template.d.ts +1 -4
  65. package/dist/runtime/app-template.d.ts.map +1 -1
  66. package/dist/runtime/app-template.js +211 -40
  67. package/dist/runtime/app-template.js.map +1 -1
  68. package/dist/runtime/composables/index.d.ts +4 -0
  69. package/dist/runtime/composables/index.d.ts.map +1 -1
  70. package/dist/runtime/composables/index.js +2 -0
  71. package/dist/runtime/composables/index.js.map +1 -1
  72. package/dist/runtime/composables/use-active-headings.d.ts +24 -0
  73. package/dist/runtime/composables/use-active-headings.d.ts.map +1 -0
  74. package/dist/runtime/composables/use-active-headings.js +112 -0
  75. package/dist/runtime/composables/use-active-headings.js.map +1 -0
  76. package/dist/runtime/composables/use-content-page.d.ts +45 -0
  77. package/dist/runtime/composables/use-content-page.d.ts.map +1 -0
  78. package/dist/runtime/composables/use-content-page.js +179 -0
  79. package/dist/runtime/composables/use-content-page.js.map +1 -0
  80. package/dist/runtime/composables/use-content-search.d.ts.map +1 -1
  81. package/dist/runtime/composables/use-content-search.js +1 -5
  82. package/dist/runtime/composables/use-content-search.js.map +1 -1
  83. package/dist/runtime/composables/use-fetch.d.ts +2 -1
  84. package/dist/runtime/composables/use-fetch.d.ts.map +1 -1
  85. package/dist/runtime/composables/use-fetch.js +112 -43
  86. package/dist/runtime/composables/use-fetch.js.map +1 -1
  87. package/dist/runtime/composables/use-head.d.ts.map +1 -1
  88. package/dist/runtime/composables/use-head.js +30 -6
  89. package/dist/runtime/composables/use-head.js.map +1 -1
  90. package/dist/runtime/composables/use-route.d.ts.map +1 -1
  91. package/dist/runtime/composables/use-route.js +39 -11
  92. package/dist/runtime/composables/use-route.js.map +1 -1
  93. package/dist/runtime/content/client.js +2 -2
  94. package/dist/runtime/content/client.js.map +1 -1
  95. package/dist/runtime/entry-server-template.d.ts +16 -1
  96. package/dist/runtime/entry-server-template.d.ts.map +1 -1
  97. package/dist/runtime/entry-server-template.js +120 -13
  98. package/dist/runtime/entry-server-template.js.map +1 -1
  99. package/dist/types/config.d.ts +53 -4
  100. package/dist/types/config.d.ts.map +1 -1
  101. package/dist/types/config.js.map +1 -1
  102. package/dist/types/middleware.d.ts +2 -2
  103. package/dist/types/middleware.d.ts.map +1 -1
  104. package/package.json +22 -19
  105. package/.github/copilot-instructions.md +0 -169
  106. package/.github/workflows/publish.yml +0 -257
  107. package/.nvmrc +0 -1
  108. package/CHANGELOG.md +0 -232
  109. package/CLAUDE.md +0 -113
  110. package/commits.txt +0 -1
  111. package/cypress.config.ts +0 -16
  112. package/docs/authentication.md +0 -285
  113. package/docs/cli.md +0 -324
  114. package/docs/components.md +0 -237
  115. package/docs/composables.md +0 -907
  116. package/docs/configuration.md +0 -667
  117. package/docs/content.md +0 -570
  118. package/docs/data-loading.md +0 -336
  119. package/docs/getting-started.md +0 -237
  120. package/docs/head-management.md +0 -212
  121. package/docs/i18n.md +0 -159
  122. package/docs/layouts.md +0 -224
  123. package/docs/middleware.md +0 -234
  124. package/docs/plugins.md +0 -170
  125. package/docs/rendering-modes.md +0 -377
  126. package/docs/routing.md +0 -485
  127. package/docs/server-api.md +0 -253
  128. package/docs/testing.md +0 -455
  129. package/docs/use-fetch.md +0 -183
  130. package/e2e/cypress/e2e/api.cy.ts +0 -123
  131. package/e2e/cypress/e2e/async-component.cy.ts +0 -27
  132. package/e2e/cypress/e2e/component-splitting.cy.ts +0 -50
  133. package/e2e/cypress/e2e/content.cy.ts +0 -425
  134. package/e2e/cypress/e2e/cookie.cy.ts +0 -85
  135. package/e2e/cypress/e2e/data.cy.ts +0 -158
  136. package/e2e/cypress/e2e/error-boundary.cy.ts +0 -88
  137. package/e2e/cypress/e2e/fouc.cy.ts +0 -254
  138. package/e2e/cypress/e2e/group-meta.cy.ts +0 -36
  139. package/e2e/cypress/e2e/head.cy.ts +0 -89
  140. package/e2e/cypress/e2e/i18n.cy.ts +0 -79
  141. package/e2e/cypress/e2e/interactive.cy.ts +0 -137
  142. package/e2e/cypress/e2e/isr-nested-runtime.cy.ts +0 -102
  143. package/e2e/cypress/e2e/jit-css-custom-colors.cy.ts +0 -43
  144. package/e2e/cypress/e2e/middleware-chain.cy.ts +0 -89
  145. package/e2e/cypress/e2e/middleware-status.cy.ts +0 -30
  146. package/e2e/cypress/e2e/middleware.cy.ts +0 -45
  147. package/e2e/cypress/e2e/navigate.cy.ts +0 -39
  148. package/e2e/cypress/e2e/observability.cy.ts +0 -77
  149. package/e2e/cypress/e2e/per-route-error.cy.ts +0 -42
  150. package/e2e/cypress/e2e/per-route-render.cy.ts +0 -70
  151. package/e2e/cypress/e2e/preview-hardening.cy.ts +0 -88
  152. package/e2e/cypress/e2e/routes.cy.ts +0 -128
  153. package/e2e/cypress/e2e/seo-meta.cy.ts +0 -108
  154. package/e2e/cypress/e2e/server-middleware.cy.ts +0 -46
  155. package/e2e/cypress/e2e/session-rotation.cy.ts +0 -25
  156. package/e2e/cypress/e2e/session.cy.ts +0 -73
  157. package/e2e/cypress/e2e/ssr-render-error.cy.ts +0 -68
  158. package/e2e/cypress/e2e/synthetic-404.cy.ts +0 -45
  159. package/e2e/cypress/e2e/use-page-data.cy.ts +0 -122
  160. package/e2e/cypress/e2e/use-state.cy.ts +0 -138
  161. package/e2e/cypress/support/commands.ts +0 -120
  162. package/e2e/cypress/support/e2e.ts +0 -10
  163. package/e2e/cypress/tsconfig.json +0 -14
  164. package/e2e/kitchen-sink/app/components/ks-badge.ts +0 -8
  165. package/e2e/kitchen-sink/app/composables/useKsCounter.ts +0 -9
  166. package/e2e/kitchen-sink/app/error.ts +0 -18
  167. package/e2e/kitchen-sink/app/layouts/admin.ts +0 -13
  168. package/e2e/kitchen-sink/app/layouts/default.ts +0 -25
  169. package/e2e/kitchen-sink/app/layouts/group.ts +0 -9
  170. package/e2e/kitchen-sink/app/layouts/minimal.ts +0 -7
  171. package/e2e/kitchen-sink/app/loading.ts +0 -9
  172. package/e2e/kitchen-sink/app/middleware/auth.ts +0 -9
  173. package/e2e/kitchen-sink/app/middleware/custom-log.ts +0 -4
  174. package/e2e/kitchen-sink/app/middleware/group-auth.ts +0 -7
  175. package/e2e/kitchen-sink/app/middleware/logger.ts +0 -14
  176. package/e2e/kitchen-sink/app/pages/(auth)/login.ts +0 -13
  177. package/e2e/kitchen-sink/app/pages/(auth)/protected.ts +0 -16
  178. package/e2e/kitchen-sink/app/pages/404.ts +0 -9
  179. package/e2e/kitchen-sink/app/pages/about.ts +0 -17
  180. package/e2e/kitchen-sink/app/pages/admin/_layout.ts +0 -1
  181. package/e2e/kitchen-sink/app/pages/admin/dashboard.ts +0 -11
  182. package/e2e/kitchen-sink/app/pages/async-component-error-test.ts +0 -18
  183. package/e2e/kitchen-sink/app/pages/async-component-test.ts +0 -18
  184. package/e2e/kitchen-sink/app/pages/blog/[slug].ts +0 -59
  185. package/e2e/kitchen-sink/app/pages/blog/index.ts +0 -51
  186. package/e2e/kitchen-sink/app/pages/content-blog.ts +0 -37
  187. package/e2e/kitchen-sink/app/pages/content-doc.ts +0 -42
  188. package/e2e/kitchen-sink/app/pages/content-fallback.ts +0 -36
  189. package/e2e/kitchen-sink/app/pages/content-guides.ts +0 -35
  190. package/e2e/kitchen-sink/app/pages/content-index.ts +0 -39
  191. package/e2e/kitchen-sink/app/pages/content-search.ts +0 -36
  192. package/e2e/kitchen-sink/app/pages/cookie-test.ts +0 -22
  193. package/e2e/kitchen-sink/app/pages/counter.ts +0 -17
  194. package/e2e/kitchen-sink/app/pages/group-auth-blocked.ts +0 -9
  195. package/e2e/kitchen-sink/app/pages/group-meta-test/_layout.ts +0 -6
  196. package/e2e/kitchen-sink/app/pages/group-meta-test/custom-mw.ts +0 -14
  197. package/e2e/kitchen-sink/app/pages/group-meta-test/page.ts +0 -9
  198. package/e2e/kitchen-sink/app/pages/group-meta-test/protected.ts +0 -8
  199. package/e2e/kitchen-sink/app/pages/head.ts +0 -20
  200. package/e2e/kitchen-sink/app/pages/i18n-test.ts +0 -14
  201. package/e2e/kitchen-sink/app/pages/index.ts +0 -27
  202. package/e2e/kitchen-sink/app/pages/isr-test.ts +0 -17
  203. package/e2e/kitchen-sink/app/pages/items/[id].ts +0 -20
  204. package/e2e/kitchen-sink/app/pages/jit-css-custom-colors-test.ts +0 -14
  205. package/e2e/kitchen-sink/app/pages/loader-error-test.ts +0 -13
  206. package/e2e/kitchen-sink/app/pages/loader-props-test.ts +0 -19
  207. package/e2e/kitchen-sink/app/pages/loader-response-error-test.ts +0 -13
  208. package/e2e/kitchen-sink/app/pages/middleware-chain-test.ts +0 -12
  209. package/e2e/kitchen-sink/app/pages/middleware-status-test.ts +0 -9
  210. package/e2e/kitchen-sink/app/pages/navigate-test.ts +0 -16
  211. package/e2e/kitchen-sink/app/pages/observability-test.ts +0 -25
  212. package/e2e/kitchen-sink/app/pages/per-route-error-test.error.ts +0 -13
  213. package/e2e/kitchen-sink/app/pages/per-route-error-test.ts +0 -15
  214. package/e2e/kitchen-sink/app/pages/plugin-dsd-test.ts +0 -10
  215. package/e2e/kitchen-sink/app/pages/render-error-test.ts +0 -12
  216. package/e2e/kitchen-sink/app/pages/render-server-test.ts +0 -12
  217. package/e2e/kitchen-sink/app/pages/render-spa-test.ts +0 -12
  218. package/e2e/kitchen-sink/app/pages/route-info.ts +0 -14
  219. package/e2e/kitchen-sink/app/pages/seo-test.ts +0 -23
  220. package/e2e/kitchen-sink/app/pages/session-rotation-test.ts +0 -25
  221. package/e2e/kitchen-sink/app/pages/use-state-test.ts +0 -37
  222. package/e2e/kitchen-sink/app/plugins/01.setup.ts +0 -7
  223. package/e2e/kitchen-sink/app/plugins/02.ks-plugin-components.ts +0 -27
  224. package/e2e/kitchen-sink/cer-auto-imports.d.ts +0 -51
  225. package/e2e/kitchen-sink/cer-env.d.ts +0 -35
  226. package/e2e/kitchen-sink/cer-tsconfig.json +0 -30
  227. package/e2e/kitchen-sink/cer.config.ts +0 -43
  228. package/e2e/kitchen-sink/content/01.guides/01.index.md +0 -8
  229. package/e2e/kitchen-sink/content/01.guides/02.intro.md +0 -8
  230. package/e2e/kitchen-sink/content/01.guides/10.advanced.md +0 -8
  231. package/e2e/kitchen-sink/content/blog/2026-04-01-hello.md +0 -26
  232. package/e2e/kitchen-sink/content/blog/2026-04-02-draft.md +0 -10
  233. package/e2e/kitchen-sink/content/blog/index.md +0 -8
  234. package/e2e/kitchen-sink/content/blog/no-frontmatter.md +0 -7
  235. package/e2e/kitchen-sink/content/docs/getting-started.md +0 -48
  236. package/e2e/kitchen-sink/content/index.md +0 -16
  237. package/e2e/kitchen-sink/server/api/echo.ts +0 -12
  238. package/e2e/kitchen-sink/server/api/health.ts +0 -3
  239. package/e2e/kitchen-sink/server/api/posts/[slug].ts +0 -11
  240. package/e2e/kitchen-sink/server/api/posts/index.ts +0 -5
  241. package/e2e/kitchen-sink/server/api/session.ts +0 -23
  242. package/e2e/kitchen-sink/server/data/posts.ts +0 -21
  243. package/e2e/kitchen-sink/server/middleware/01-headers.ts +0 -6
  244. package/e2e/kitchen-sink/server/middleware/02-status-test.ts +0 -10
  245. package/e2e/kitchen-sink/tsconfig.json +0 -3
  246. package/e2e/scripts/clean.mjs +0 -14
  247. package/eslint.config.ts +0 -22
  248. package/src/__tests__/cli/adapters/cloudflare-worker.integration.test.ts +0 -132
  249. package/src/__tests__/cli/adapters/cloudflare.test.ts +0 -332
  250. package/src/__tests__/cli/adapters/netlify-bridge.integration.test.ts +0 -139
  251. package/src/__tests__/cli/adapters/netlify.test.ts +0 -286
  252. package/src/__tests__/cli/adapters/vercel-launcher.integration.test.ts +0 -129
  253. package/src/__tests__/cli/adapters/vercel.test.ts +0 -263
  254. package/src/__tests__/cli/preview-hardening.test.ts +0 -265
  255. package/src/__tests__/cli/preview-isr.test.ts +0 -320
  256. package/src/__tests__/index.test.ts +0 -21
  257. package/src/__tests__/plugin/app-template.test.ts +0 -112
  258. package/src/__tests__/plugin/build-ssg-render.test.ts +0 -155
  259. package/src/__tests__/plugin/build-ssg.test.ts +0 -598
  260. package/src/__tests__/plugin/build-ssr.test.ts +0 -192
  261. package/src/__tests__/plugin/cer-app-plugin.test.ts +0 -552
  262. package/src/__tests__/plugin/content/emitter.test.ts +0 -117
  263. package/src/__tests__/plugin/content/loader.test.ts +0 -154
  264. package/src/__tests__/plugin/content/parser.test.ts +0 -381
  265. package/src/__tests__/plugin/content/path-utils.test.ts +0 -69
  266. package/src/__tests__/plugin/content/search.test.ts +0 -119
  267. package/src/__tests__/plugin/dev-server.test.ts +0 -813
  268. package/src/__tests__/plugin/dts-generator.test.ts +0 -394
  269. package/src/__tests__/plugin/entry-server-template.test.ts +0 -385
  270. package/src/__tests__/plugin/generated-dir.test.ts +0 -144
  271. package/src/__tests__/plugin/path-utils.test.ts +0 -411
  272. package/src/__tests__/plugin/resolve-config.test.ts +0 -200
  273. package/src/__tests__/plugin/scanner.test.ts +0 -186
  274. package/src/__tests__/plugin/transforms/auto-import.test.ts +0 -510
  275. package/src/__tests__/plugin/transforms/head-inject.test.ts +0 -178
  276. package/src/__tests__/plugin/virtual/composables.test.ts +0 -57
  277. package/src/__tests__/plugin/virtual/content-components.test.ts +0 -66
  278. package/src/__tests__/plugin/virtual/error.test.ts +0 -71
  279. package/src/__tests__/plugin/virtual/layouts.test.ts +0 -70
  280. package/src/__tests__/plugin/virtual/loading.test.ts +0 -72
  281. package/src/__tests__/plugin/virtual/middleware.test.ts +0 -83
  282. package/src/__tests__/plugin/virtual/plugins.test.ts +0 -129
  283. package/src/__tests__/plugin/virtual/routes.test.ts +0 -884
  284. package/src/__tests__/plugin/virtual/server-api.test.ts +0 -164
  285. package/src/__tests__/plugin/virtual/server-middleware.test.ts +0 -102
  286. package/src/__tests__/runtime/app-template.test.ts +0 -198
  287. package/src/__tests__/runtime/define-middleware.test.ts +0 -64
  288. package/src/__tests__/runtime/define-server-middleware.test.ts +0 -54
  289. package/src/__tests__/runtime/entry-client-template.test.ts +0 -61
  290. package/src/__tests__/runtime/isr-handler.test.ts +0 -338
  291. package/src/__tests__/runtime/oauth/handler.test.ts +0 -358
  292. package/src/__tests__/runtime/oauth/providers.test.ts +0 -47
  293. package/src/__tests__/runtime/oauth-index.test.ts +0 -43
  294. package/src/__tests__/runtime/use-auth.test.ts +0 -169
  295. package/src/__tests__/runtime/use-content-search-composable.test.ts +0 -405
  296. package/src/__tests__/runtime/use-content-search.test.ts +0 -163
  297. package/src/__tests__/runtime/use-content.test.ts +0 -226
  298. package/src/__tests__/runtime/use-cookie.test.ts +0 -277
  299. package/src/__tests__/runtime/use-fetch-component.test.ts +0 -214
  300. package/src/__tests__/runtime/use-fetch.test.ts +0 -377
  301. package/src/__tests__/runtime/use-head.test.ts +0 -266
  302. package/src/__tests__/runtime/use-inject-client.test.ts +0 -67
  303. package/src/__tests__/runtime/use-inject.test.ts +0 -66
  304. package/src/__tests__/runtime/use-locale.test.ts +0 -152
  305. package/src/__tests__/runtime/use-navigate.test.ts +0 -121
  306. package/src/__tests__/runtime/use-page-data.test.ts +0 -298
  307. package/src/__tests__/runtime/use-route.test.ts +0 -156
  308. package/src/__tests__/runtime/use-runtime-config.test.ts +0 -182
  309. package/src/__tests__/runtime/use-seo-meta.test.ts +0 -109
  310. package/src/__tests__/runtime/use-session.test.ts +0 -320
  311. package/src/__tests__/runtime/use-state.test.ts +0 -340
  312. package/src/__tests__/types/config.test.ts +0 -18
  313. package/src/cli/adapters/cloudflare.ts +0 -335
  314. package/src/cli/adapters/netlify.ts +0 -326
  315. package/src/cli/adapters/vercel.ts +0 -311
  316. package/src/cli/commands/adapt.ts +0 -84
  317. package/src/cli/commands/build.ts +0 -165
  318. package/src/cli/commands/dev.ts +0 -125
  319. package/src/cli/commands/generate.ts +0 -91
  320. package/src/cli/commands/preview-isr.ts +0 -172
  321. package/src/cli/commands/preview.ts +0 -489
  322. package/src/cli/create/index.ts +0 -275
  323. package/src/cli/create/templates/shared/.gitignore.tpl +0 -25
  324. package/src/cli/create/templates/shared/app/layouts/default.ts.tpl +0 -15
  325. package/src/cli/create/templates/shared/app/pages/index.ts.tpl +0 -8
  326. package/src/cli/create/templates/shared/index.html.tpl +0 -12
  327. package/src/cli/create/templates/shared/tsconfig.json.tpl +0 -6
  328. package/src/cli/create/templates/spa/cer.config.ts.tpl +0 -6
  329. package/src/cli/create/templates/spa/package.json.tpl +0 -18
  330. package/src/cli/create/templates/ssg/cer.config.ts.tpl +0 -10
  331. package/src/cli/create/templates/ssg/package.json.tpl +0 -19
  332. package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -6
  333. package/src/cli/create/templates/ssr/package.json.tpl +0 -18
  334. package/src/cli/index.ts +0 -22
  335. package/src/index.ts +0 -20
  336. package/src/plugin/build-ssg.ts +0 -336
  337. package/src/plugin/build-ssr.ts +0 -134
  338. package/src/plugin/content/emitter.ts +0 -50
  339. package/src/plugin/content/index.ts +0 -233
  340. package/src/plugin/content/parser.ts +0 -259
  341. package/src/plugin/content/path-utils.ts +0 -55
  342. package/src/plugin/content/scanner.ts +0 -26
  343. package/src/plugin/content/search.ts +0 -28
  344. package/src/plugin/dev-server.ts +0 -377
  345. package/src/plugin/dts-generator.ts +0 -273
  346. package/src/plugin/generated-dir.ts +0 -121
  347. package/src/plugin/index.ts +0 -553
  348. package/src/plugin/path-utils.ts +0 -186
  349. package/src/plugin/scanner.ts +0 -65
  350. package/src/plugin/transforms/auto-import.ts +0 -206
  351. package/src/plugin/transforms/head-inject.ts +0 -161
  352. package/src/plugin/virtual/composables.ts +0 -28
  353. package/src/plugin/virtual/content-components.ts +0 -83
  354. package/src/plugin/virtual/error.ts +0 -34
  355. package/src/plugin/virtual/layouts.ts +0 -41
  356. package/src/plugin/virtual/loading.ts +0 -32
  357. package/src/plugin/virtual/middleware.ts +0 -45
  358. package/src/plugin/virtual/plugins.ts +0 -46
  359. package/src/plugin/virtual/routes.ts +0 -514
  360. package/src/plugin/virtual/server-api.ts +0 -85
  361. package/src/plugin/virtual/server-middleware.ts +0 -44
  362. package/src/runtime/app-template.ts +0 -380
  363. package/src/runtime/composables/define-middleware.ts +0 -17
  364. package/src/runtime/composables/define-server-middleware.ts +0 -18
  365. package/src/runtime/composables/index.ts +0 -27
  366. package/src/runtime/composables/use-auth.ts +0 -125
  367. package/src/runtime/composables/use-content-search.ts +0 -205
  368. package/src/runtime/composables/use-content.ts +0 -146
  369. package/src/runtime/composables/use-cookie.ts +0 -135
  370. package/src/runtime/composables/use-fetch.ts +0 -438
  371. package/src/runtime/composables/use-head.ts +0 -212
  372. package/src/runtime/composables/use-inject.ts +0 -49
  373. package/src/runtime/composables/use-locale.ts +0 -109
  374. package/src/runtime/composables/use-navigate.ts +0 -58
  375. package/src/runtime/composables/use-page-data.ts +0 -93
  376. package/src/runtime/composables/use-route.ts +0 -71
  377. package/src/runtime/composables/use-runtime-config.ts +0 -123
  378. package/src/runtime/composables/use-seo-meta.ts +0 -80
  379. package/src/runtime/composables/use-session.ts +0 -186
  380. package/src/runtime/composables/use-state.ts +0 -92
  381. package/src/runtime/content/client.ts +0 -168
  382. package/src/runtime/entry-client-template.ts +0 -39
  383. package/src/runtime/entry-server-template.ts +0 -493
  384. package/src/runtime/isr-handler.ts +0 -184
  385. package/src/runtime/oauth/handler.ts +0 -407
  386. package/src/runtime/oauth/index.ts +0 -49
  387. package/src/runtime/oauth/providers.ts +0 -38
  388. package/src/types/api.ts +0 -40
  389. package/src/types/config.ts +0 -371
  390. package/src/types/content.ts +0 -66
  391. package/src/types/index.ts +0 -7
  392. package/src/types/middleware.ts +0 -63
  393. package/src/types/page.ts +0 -117
  394. package/src/types/plugin.ts +0 -34
  395. package/tsconfig.build.json +0 -10
  396. package/tsconfig.json +0 -19
  397. 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
- ```