@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.
- package/dist/cli/checks/lighthouse.d.ts +26 -0
- package/dist/cli/checks/lighthouse.d.ts.map +1 -0
- package/dist/cli/checks/lighthouse.js +121 -0
- package/dist/cli/checks/lighthouse.js.map +1 -0
- package/dist/cli/checks/links.d.ts +12 -0
- package/dist/cli/checks/links.d.ts.map +1 -0
- package/dist/cli/checks/links.js +89 -0
- package/dist/cli/checks/links.js.map +1 -0
- package/dist/cli/checks/performance.d.ts +17 -0
- package/dist/cli/checks/performance.d.ts.map +1 -0
- package/dist/cli/checks/performance.js +61 -0
- package/dist/cli/checks/performance.js.map +1 -0
- package/dist/cli/commands/check.d.ts +3 -0
- package/dist/cli/commands/check.d.ts.map +1 -0
- package/dist/cli/commands/check.js +85 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/create/index.d.ts +11 -1
- package/dist/cli/create/index.d.ts.map +1 -1
- package/dist/cli/create/index.js +135 -65
- package/dist/cli/create/index.js.map +1 -1
- package/dist/cli/create/templates/spa/package.json.tpl +5 -3
- package/dist/cli/create/templates/ssg/cer.config.ts.tpl +2 -0
- package/dist/cli/create/templates/ssg/package.json.tpl +5 -3
- package/dist/cli/create/templates/ssr/package.json.tpl +5 -3
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/plugin/build-ssg.d.ts +22 -6
- package/dist/plugin/build-ssg.d.ts.map +1 -1
- package/dist/plugin/build-ssg.js +227 -17
- package/dist/plugin/build-ssg.js.map +1 -1
- package/dist/plugin/content/parser.d.ts.map +1 -1
- package/dist/plugin/content/parser.js +54 -10
- package/dist/plugin/content/parser.js.map +1 -1
- package/dist/plugin/dev-server.d.ts +4 -1
- package/dist/plugin/dev-server.d.ts.map +1 -1
- package/dist/plugin/dev-server.js.map +1 -1
- package/dist/plugin/dts-generator.d.ts.map +1 -1
- package/dist/plugin/dts-generator.js +50 -8
- package/dist/plugin/dts-generator.js.map +1 -1
- package/dist/plugin/generated-dir.d.ts +2 -0
- package/dist/plugin/generated-dir.d.ts.map +1 -1
- package/dist/plugin/generated-dir.js +17 -6
- package/dist/plugin/generated-dir.js.map +1 -1
- package/dist/plugin/html-post-process.d.ts +6 -0
- package/dist/plugin/html-post-process.d.ts.map +1 -1
- package/dist/plugin/html-post-process.js +24 -0
- package/dist/plugin/html-post-process.js.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +60 -13
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/transforms/auto-import.d.ts.map +1 -1
- package/dist/plugin/transforms/auto-import.js +7 -0
- package/dist/plugin/transforms/auto-import.js.map +1 -1
- package/dist/plugin/transforms/prune-runtime-css.d.ts +8 -0
- package/dist/plugin/transforms/prune-runtime-css.d.ts.map +1 -0
- package/dist/plugin/transforms/prune-runtime-css.js +40 -0
- package/dist/plugin/transforms/prune-runtime-css.js.map +1 -0
- package/dist/runtime/app-template.d.ts +1 -1
- package/dist/runtime/app-template.d.ts.map +1 -1
- package/dist/runtime/app-template.js +206 -36
- package/dist/runtime/app-template.js.map +1 -1
- package/dist/runtime/composables/index.d.ts +4 -0
- package/dist/runtime/composables/index.d.ts.map +1 -1
- package/dist/runtime/composables/index.js +2 -0
- package/dist/runtime/composables/index.js.map +1 -1
- package/dist/runtime/composables/use-active-headings.d.ts +24 -0
- package/dist/runtime/composables/use-active-headings.d.ts.map +1 -0
- package/dist/runtime/composables/use-active-headings.js +112 -0
- package/dist/runtime/composables/use-active-headings.js.map +1 -0
- package/dist/runtime/composables/use-content-page.d.ts +45 -0
- package/dist/runtime/composables/use-content-page.d.ts.map +1 -0
- package/dist/runtime/composables/use-content-page.js +179 -0
- package/dist/runtime/composables/use-content-page.js.map +1 -0
- package/dist/runtime/composables/use-content-search.d.ts.map +1 -1
- package/dist/runtime/composables/use-content-search.js +1 -5
- package/dist/runtime/composables/use-content-search.js.map +1 -1
- package/dist/runtime/composables/use-fetch.d.ts +2 -1
- package/dist/runtime/composables/use-fetch.d.ts.map +1 -1
- package/dist/runtime/composables/use-fetch.js +112 -43
- package/dist/runtime/composables/use-fetch.js.map +1 -1
- package/dist/runtime/composables/use-route.d.ts.map +1 -1
- package/dist/runtime/composables/use-route.js +39 -11
- package/dist/runtime/composables/use-route.js.map +1 -1
- package/dist/runtime/content/client.js +2 -2
- package/dist/runtime/content/client.js.map +1 -1
- package/dist/runtime/entry-server-template.d.ts +16 -1
- package/dist/runtime/entry-server-template.d.ts.map +1 -1
- package/dist/runtime/entry-server-template.js +120 -13
- package/dist/runtime/entry-server-template.js.map +1 -1
- package/dist/types/config.d.ts +45 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/dist/types/middleware.d.ts +2 -2
- package/dist/types/middleware.d.ts.map +1 -1
- package/package.json +22 -19
- package/.github/copilot-instructions.md +0 -169
- package/.github/workflows/publish.yml +0 -257
- package/.nvmrc +0 -1
- package/CHANGELOG.md +0 -236
- package/CLAUDE.md +0 -113
- package/commits.txt +0 -1
- package/cypress.config.ts +0 -16
- package/docs/authentication.md +0 -285
- package/docs/cli.md +0 -324
- package/docs/components.md +0 -237
- package/docs/composables.md +0 -907
- package/docs/configuration.md +0 -696
- package/docs/content.md +0 -570
- package/docs/data-loading.md +0 -336
- package/docs/getting-started.md +0 -237
- package/docs/head-management.md +0 -212
- package/docs/i18n.md +0 -159
- package/docs/layouts.md +0 -224
- package/docs/middleware.md +0 -234
- package/docs/plugins.md +0 -170
- package/docs/rendering-modes.md +0 -377
- package/docs/routing.md +0 -485
- package/docs/server-api.md +0 -253
- package/docs/testing.md +0 -455
- package/docs/use-fetch.md +0 -183
- package/e2e/cypress/e2e/api.cy.ts +0 -123
- package/e2e/cypress/e2e/async-component.cy.ts +0 -27
- package/e2e/cypress/e2e/component-splitting.cy.ts +0 -50
- package/e2e/cypress/e2e/content.cy.ts +0 -425
- package/e2e/cypress/e2e/cookie.cy.ts +0 -85
- package/e2e/cypress/e2e/data.cy.ts +0 -158
- package/e2e/cypress/e2e/error-boundary.cy.ts +0 -88
- package/e2e/cypress/e2e/fouc.cy.ts +0 -254
- package/e2e/cypress/e2e/group-meta.cy.ts +0 -36
- package/e2e/cypress/e2e/head.cy.ts +0 -89
- package/e2e/cypress/e2e/i18n.cy.ts +0 -79
- package/e2e/cypress/e2e/interactive.cy.ts +0 -137
- package/e2e/cypress/e2e/isr-nested-runtime.cy.ts +0 -102
- package/e2e/cypress/e2e/jit-css-custom-colors.cy.ts +0 -43
- package/e2e/cypress/e2e/middleware-chain.cy.ts +0 -89
- package/e2e/cypress/e2e/middleware-status.cy.ts +0 -30
- package/e2e/cypress/e2e/middleware.cy.ts +0 -45
- package/e2e/cypress/e2e/navigate.cy.ts +0 -39
- package/e2e/cypress/e2e/observability.cy.ts +0 -77
- package/e2e/cypress/e2e/per-route-error.cy.ts +0 -42
- package/e2e/cypress/e2e/per-route-render.cy.ts +0 -70
- package/e2e/cypress/e2e/preview-hardening.cy.ts +0 -88
- package/e2e/cypress/e2e/routes.cy.ts +0 -128
- package/e2e/cypress/e2e/seo-meta.cy.ts +0 -108
- package/e2e/cypress/e2e/server-middleware.cy.ts +0 -46
- package/e2e/cypress/e2e/session-rotation.cy.ts +0 -25
- package/e2e/cypress/e2e/session.cy.ts +0 -73
- package/e2e/cypress/e2e/ssr-render-error.cy.ts +0 -68
- package/e2e/cypress/e2e/synthetic-404.cy.ts +0 -45
- package/e2e/cypress/e2e/use-page-data.cy.ts +0 -122
- package/e2e/cypress/e2e/use-state.cy.ts +0 -138
- package/e2e/cypress/support/commands.ts +0 -120
- package/e2e/cypress/support/e2e.ts +0 -10
- package/e2e/cypress/tsconfig.json +0 -14
- package/e2e/kitchen-sink/app/components/ks-badge.ts +0 -8
- package/e2e/kitchen-sink/app/composables/useKsCounter.ts +0 -9
- package/e2e/kitchen-sink/app/error.ts +0 -18
- package/e2e/kitchen-sink/app/layouts/admin.ts +0 -13
- package/e2e/kitchen-sink/app/layouts/default.ts +0 -25
- package/e2e/kitchen-sink/app/layouts/group.ts +0 -9
- package/e2e/kitchen-sink/app/layouts/minimal.ts +0 -7
- package/e2e/kitchen-sink/app/loading.ts +0 -9
- package/e2e/kitchen-sink/app/middleware/auth.ts +0 -9
- package/e2e/kitchen-sink/app/middleware/custom-log.ts +0 -4
- package/e2e/kitchen-sink/app/middleware/group-auth.ts +0 -7
- package/e2e/kitchen-sink/app/middleware/logger.ts +0 -14
- package/e2e/kitchen-sink/app/pages/(auth)/login.ts +0 -13
- package/e2e/kitchen-sink/app/pages/(auth)/protected.ts +0 -16
- package/e2e/kitchen-sink/app/pages/404.ts +0 -9
- package/e2e/kitchen-sink/app/pages/about.ts +0 -17
- package/e2e/kitchen-sink/app/pages/admin/_layout.ts +0 -1
- package/e2e/kitchen-sink/app/pages/admin/dashboard.ts +0 -11
- package/e2e/kitchen-sink/app/pages/async-component-error-test.ts +0 -18
- package/e2e/kitchen-sink/app/pages/async-component-test.ts +0 -18
- package/e2e/kitchen-sink/app/pages/blog/[slug].ts +0 -59
- package/e2e/kitchen-sink/app/pages/blog/index.ts +0 -51
- package/e2e/kitchen-sink/app/pages/content-blog.ts +0 -37
- package/e2e/kitchen-sink/app/pages/content-doc.ts +0 -42
- package/e2e/kitchen-sink/app/pages/content-fallback.ts +0 -36
- package/e2e/kitchen-sink/app/pages/content-guides.ts +0 -35
- package/e2e/kitchen-sink/app/pages/content-index.ts +0 -39
- package/e2e/kitchen-sink/app/pages/content-search.ts +0 -36
- package/e2e/kitchen-sink/app/pages/cookie-test.ts +0 -22
- package/e2e/kitchen-sink/app/pages/counter.ts +0 -17
- package/e2e/kitchen-sink/app/pages/group-auth-blocked.ts +0 -9
- package/e2e/kitchen-sink/app/pages/group-meta-test/_layout.ts +0 -6
- package/e2e/kitchen-sink/app/pages/group-meta-test/custom-mw.ts +0 -14
- package/e2e/kitchen-sink/app/pages/group-meta-test/page.ts +0 -9
- package/e2e/kitchen-sink/app/pages/group-meta-test/protected.ts +0 -8
- package/e2e/kitchen-sink/app/pages/head.ts +0 -20
- package/e2e/kitchen-sink/app/pages/i18n-test.ts +0 -14
- package/e2e/kitchen-sink/app/pages/index.ts +0 -27
- package/e2e/kitchen-sink/app/pages/isr-test.ts +0 -17
- package/e2e/kitchen-sink/app/pages/items/[id].ts +0 -20
- package/e2e/kitchen-sink/app/pages/jit-css-custom-colors-test.ts +0 -14
- package/e2e/kitchen-sink/app/pages/loader-error-test.ts +0 -13
- package/e2e/kitchen-sink/app/pages/loader-props-test.ts +0 -19
- package/e2e/kitchen-sink/app/pages/loader-response-error-test.ts +0 -13
- package/e2e/kitchen-sink/app/pages/middleware-chain-test.ts +0 -12
- package/e2e/kitchen-sink/app/pages/middleware-status-test.ts +0 -9
- package/e2e/kitchen-sink/app/pages/navigate-test.ts +0 -16
- package/e2e/kitchen-sink/app/pages/observability-test.ts +0 -25
- package/e2e/kitchen-sink/app/pages/per-route-error-test.error.ts +0 -13
- package/e2e/kitchen-sink/app/pages/per-route-error-test.ts +0 -15
- package/e2e/kitchen-sink/app/pages/plugin-dsd-test.ts +0 -10
- package/e2e/kitchen-sink/app/pages/render-error-test.ts +0 -12
- package/e2e/kitchen-sink/app/pages/render-server-test.ts +0 -12
- package/e2e/kitchen-sink/app/pages/render-spa-test.ts +0 -12
- package/e2e/kitchen-sink/app/pages/route-info.ts +0 -14
- package/e2e/kitchen-sink/app/pages/seo-test.ts +0 -23
- package/e2e/kitchen-sink/app/pages/session-rotation-test.ts +0 -25
- package/e2e/kitchen-sink/app/pages/use-state-test.ts +0 -37
- package/e2e/kitchen-sink/app/plugins/01.setup.ts +0 -7
- package/e2e/kitchen-sink/app/plugins/02.ks-plugin-components.ts +0 -27
- package/e2e/kitchen-sink/cer-auto-imports.d.ts +0 -51
- package/e2e/kitchen-sink/cer-env.d.ts +0 -35
- package/e2e/kitchen-sink/cer-tsconfig.json +0 -30
- package/e2e/kitchen-sink/cer.config.ts +0 -43
- package/e2e/kitchen-sink/content/01.guides/01.index.md +0 -8
- package/e2e/kitchen-sink/content/01.guides/02.intro.md +0 -8
- package/e2e/kitchen-sink/content/01.guides/10.advanced.md +0 -8
- package/e2e/kitchen-sink/content/blog/2026-04-01-hello.md +0 -26
- package/e2e/kitchen-sink/content/blog/2026-04-02-draft.md +0 -10
- package/e2e/kitchen-sink/content/blog/index.md +0 -8
- package/e2e/kitchen-sink/content/blog/no-frontmatter.md +0 -7
- package/e2e/kitchen-sink/content/docs/getting-started.md +0 -48
- package/e2e/kitchen-sink/content/index.md +0 -16
- package/e2e/kitchen-sink/server/api/echo.ts +0 -12
- package/e2e/kitchen-sink/server/api/health.ts +0 -3
- package/e2e/kitchen-sink/server/api/posts/[slug].ts +0 -11
- package/e2e/kitchen-sink/server/api/posts/index.ts +0 -5
- package/e2e/kitchen-sink/server/api/session.ts +0 -23
- package/e2e/kitchen-sink/server/data/posts.ts +0 -21
- package/e2e/kitchen-sink/server/middleware/01-headers.ts +0 -6
- package/e2e/kitchen-sink/server/middleware/02-status-test.ts +0 -10
- package/e2e/kitchen-sink/tsconfig.json +0 -3
- package/e2e/scripts/clean.mjs +0 -14
- package/eslint.config.ts +0 -22
- package/src/__tests__/cli/adapters/cloudflare-worker.integration.test.ts +0 -132
- package/src/__tests__/cli/adapters/cloudflare.test.ts +0 -332
- package/src/__tests__/cli/adapters/netlify-bridge.integration.test.ts +0 -139
- package/src/__tests__/cli/adapters/netlify.test.ts +0 -286
- package/src/__tests__/cli/adapters/vercel-launcher.integration.test.ts +0 -129
- package/src/__tests__/cli/adapters/vercel.test.ts +0 -263
- package/src/__tests__/cli/preview-hardening.test.ts +0 -265
- package/src/__tests__/cli/preview-isr.test.ts +0 -320
- package/src/__tests__/index.test.ts +0 -21
- package/src/__tests__/plugin/app-template.test.ts +0 -166
- package/src/__tests__/plugin/build-ssg-render.test.ts +0 -155
- package/src/__tests__/plugin/build-ssg.test.ts +0 -598
- package/src/__tests__/plugin/build-ssr.test.ts +0 -192
- package/src/__tests__/plugin/cer-app-plugin.test.ts +0 -552
- package/src/__tests__/plugin/content/emitter.test.ts +0 -117
- package/src/__tests__/plugin/content/loader.test.ts +0 -154
- package/src/__tests__/plugin/content/parser.test.ts +0 -381
- package/src/__tests__/plugin/content/path-utils.test.ts +0 -69
- package/src/__tests__/plugin/content/search.test.ts +0 -119
- package/src/__tests__/plugin/dev-server.test.ts +0 -813
- package/src/__tests__/plugin/dts-generator.test.ts +0 -394
- package/src/__tests__/plugin/entry-server-template.test.ts +0 -385
- package/src/__tests__/plugin/generated-dir.test.ts +0 -144
- package/src/__tests__/plugin/html-post-process.test.ts +0 -146
- package/src/__tests__/plugin/path-utils.test.ts +0 -411
- package/src/__tests__/plugin/resolve-config.test.ts +0 -233
- package/src/__tests__/plugin/scanner.test.ts +0 -186
- package/src/__tests__/plugin/transforms/auto-import.test.ts +0 -510
- package/src/__tests__/plugin/transforms/head-inject.test.ts +0 -178
- package/src/__tests__/plugin/virtual/composables.test.ts +0 -57
- package/src/__tests__/plugin/virtual/content-components.test.ts +0 -66
- package/src/__tests__/plugin/virtual/error.test.ts +0 -71
- package/src/__tests__/plugin/virtual/layouts.test.ts +0 -70
- package/src/__tests__/plugin/virtual/loading.test.ts +0 -72
- package/src/__tests__/plugin/virtual/middleware.test.ts +0 -83
- package/src/__tests__/plugin/virtual/plugins.test.ts +0 -129
- package/src/__tests__/plugin/virtual/routes.test.ts +0 -884
- package/src/__tests__/plugin/virtual/server-api.test.ts +0 -164
- package/src/__tests__/plugin/virtual/server-middleware.test.ts +0 -102
- package/src/__tests__/runtime/app-template.test.ts +0 -208
- package/src/__tests__/runtime/define-middleware.test.ts +0 -64
- package/src/__tests__/runtime/define-server-middleware.test.ts +0 -54
- package/src/__tests__/runtime/entry-client-template.test.ts +0 -61
- package/src/__tests__/runtime/isr-handler.test.ts +0 -338
- package/src/__tests__/runtime/oauth/handler.test.ts +0 -358
- package/src/__tests__/runtime/oauth/providers.test.ts +0 -47
- package/src/__tests__/runtime/oauth-index.test.ts +0 -43
- package/src/__tests__/runtime/use-auth.test.ts +0 -169
- package/src/__tests__/runtime/use-content-search-composable.test.ts +0 -405
- package/src/__tests__/runtime/use-content-search.test.ts +0 -163
- package/src/__tests__/runtime/use-content.test.ts +0 -226
- package/src/__tests__/runtime/use-cookie.test.ts +0 -277
- package/src/__tests__/runtime/use-fetch-component.test.ts +0 -214
- package/src/__tests__/runtime/use-fetch.test.ts +0 -377
- package/src/__tests__/runtime/use-head.test.ts +0 -311
- package/src/__tests__/runtime/use-inject-client.test.ts +0 -67
- package/src/__tests__/runtime/use-inject.test.ts +0 -66
- package/src/__tests__/runtime/use-locale.test.ts +0 -152
- package/src/__tests__/runtime/use-navigate.test.ts +0 -121
- package/src/__tests__/runtime/use-page-data.test.ts +0 -298
- package/src/__tests__/runtime/use-route.test.ts +0 -156
- package/src/__tests__/runtime/use-runtime-config.test.ts +0 -182
- package/src/__tests__/runtime/use-seo-meta.test.ts +0 -109
- package/src/__tests__/runtime/use-session.test.ts +0 -320
- package/src/__tests__/runtime/use-state.test.ts +0 -340
- package/src/__tests__/types/config.test.ts +0 -18
- package/src/cli/adapters/cloudflare.ts +0 -335
- package/src/cli/adapters/netlify.ts +0 -326
- package/src/cli/adapters/vercel.ts +0 -311
- package/src/cli/commands/adapt.ts +0 -84
- package/src/cli/commands/build.ts +0 -165
- package/src/cli/commands/dev.ts +0 -125
- package/src/cli/commands/generate.ts +0 -91
- package/src/cli/commands/preview-isr.ts +0 -172
- package/src/cli/commands/preview.ts +0 -523
- package/src/cli/create/index.ts +0 -275
- package/src/cli/create/templates/shared/.gitignore.tpl +0 -25
- package/src/cli/create/templates/shared/app/layouts/default.ts.tpl +0 -15
- package/src/cli/create/templates/shared/app/pages/index.ts.tpl +0 -8
- package/src/cli/create/templates/shared/index.html.tpl +0 -12
- package/src/cli/create/templates/shared/tsconfig.json.tpl +0 -6
- package/src/cli/create/templates/spa/cer.config.ts.tpl +0 -6
- package/src/cli/create/templates/spa/package.json.tpl +0 -18
- package/src/cli/create/templates/ssg/cer.config.ts.tpl +0 -10
- package/src/cli/create/templates/ssg/package.json.tpl +0 -19
- package/src/cli/create/templates/ssr/cer.config.ts.tpl +0 -6
- package/src/cli/create/templates/ssr/package.json.tpl +0 -18
- package/src/cli/index.ts +0 -22
- package/src/index.ts +0 -20
- package/src/plugin/build-ssg.ts +0 -403
- package/src/plugin/build-ssr.ts +0 -134
- package/src/plugin/content/emitter.ts +0 -50
- package/src/plugin/content/index.ts +0 -233
- package/src/plugin/content/parser.ts +0 -259
- package/src/plugin/content/path-utils.ts +0 -55
- package/src/plugin/content/scanner.ts +0 -26
- package/src/plugin/content/search.ts +0 -28
- package/src/plugin/dev-server.ts +0 -379
- package/src/plugin/dts-generator.ts +0 -273
- package/src/plugin/generated-dir.ts +0 -121
- package/src/plugin/html-post-process.ts +0 -96
- package/src/plugin/index.ts +0 -584
- package/src/plugin/path-utils.ts +0 -186
- package/src/plugin/scanner.ts +0 -65
- package/src/plugin/transforms/auto-import.ts +0 -206
- package/src/plugin/transforms/head-inject.ts +0 -161
- package/src/plugin/virtual/composables.ts +0 -28
- package/src/plugin/virtual/content-components.ts +0 -83
- package/src/plugin/virtual/error.ts +0 -34
- package/src/plugin/virtual/layouts.ts +0 -41
- package/src/plugin/virtual/loading.ts +0 -32
- package/src/plugin/virtual/middleware.ts +0 -45
- package/src/plugin/virtual/plugins.ts +0 -46
- package/src/plugin/virtual/routes.ts +0 -514
- package/src/plugin/virtual/server-api.ts +0 -85
- package/src/plugin/virtual/server-middleware.ts +0 -44
- package/src/runtime/app-template.ts +0 -377
- package/src/runtime/composables/define-middleware.ts +0 -17
- package/src/runtime/composables/define-server-middleware.ts +0 -18
- package/src/runtime/composables/index.ts +0 -27
- package/src/runtime/composables/use-auth.ts +0 -125
- package/src/runtime/composables/use-content-search.ts +0 -205
- package/src/runtime/composables/use-content.ts +0 -146
- package/src/runtime/composables/use-cookie.ts +0 -135
- package/src/runtime/composables/use-fetch.ts +0 -438
- package/src/runtime/composables/use-head.ts +0 -234
- package/src/runtime/composables/use-inject.ts +0 -49
- package/src/runtime/composables/use-locale.ts +0 -109
- package/src/runtime/composables/use-navigate.ts +0 -58
- package/src/runtime/composables/use-page-data.ts +0 -93
- package/src/runtime/composables/use-route.ts +0 -71
- package/src/runtime/composables/use-runtime-config.ts +0 -123
- package/src/runtime/composables/use-seo-meta.ts +0 -80
- package/src/runtime/composables/use-session.ts +0 -186
- package/src/runtime/composables/use-state.ts +0 -92
- package/src/runtime/content/client.ts +0 -168
- package/src/runtime/entry-client-template.ts +0 -39
- package/src/runtime/entry-server-template.ts +0 -493
- package/src/runtime/isr-handler.ts +0 -184
- package/src/runtime/oauth/handler.ts +0 -407
- package/src/runtime/oauth/index.ts +0 -49
- package/src/runtime/oauth/providers.ts +0 -38
- package/src/types/api.ts +0 -40
- package/src/types/config.ts +0 -379
- package/src/types/content.ts +0 -66
- package/src/types/index.ts +0 -7
- package/src/types/middleware.ts +0 -63
- package/src/types/page.ts +0 -117
- package/src/types/plugin.ts +0 -34
- package/tsconfig.build.json +0 -10
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -33
package/docs/composables.md
DELETED
|
@@ -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
|
-
```
|