@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
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useFetch — isomorphic data fetching composable.
|
|
3
|
-
*
|
|
4
|
-
* Behaves differently based on the calling context:
|
|
5
|
-
*
|
|
6
|
-
* **Component context** (inside a `component()` render function):
|
|
7
|
-
* Uses `createComposable` internally so that `useOnConnected` is bound to the
|
|
8
|
-
* calling component's lifecycle. Returns reactive `ReactiveState` refs for
|
|
9
|
-
* `data`, `pending`, and `error` — the component re-renders automatically
|
|
10
|
-
* when the fetch settles. The fetch is triggered by `useOnConnected` (i.e.
|
|
11
|
-
* when the component mounts), unless `lazy: true` / `server: false`.
|
|
12
|
-
*
|
|
13
|
-
* **Loader / SSR context** (inside an async `load()` or middleware):
|
|
14
|
-
* Returns a *thenable* `UseFetchResult` — you can `await useFetch(...)` to
|
|
15
|
-
* block SSR rendering until data is ready. The result is serialised into
|
|
16
|
-
* `window.__CER_FETCH_DATA__` for client hydration.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* // app/pages/posts.ts — loader context (SSR + client navigation)
|
|
21
|
-
* export const loader = async () => {
|
|
22
|
-
* const { data: posts } = await useFetch<Post[]>('/api/posts')
|
|
23
|
-
* return { posts }
|
|
24
|
-
* }
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* // Inside a component — reactive, auto-fetches on mount
|
|
30
|
-
* component('post-list', () => {
|
|
31
|
-
* const { data: posts, pending, error } = useFetch<Post[]>('/api/posts')
|
|
32
|
-
*
|
|
33
|
-
* return html`
|
|
34
|
-
* ${pending.value ? html`<p>Loading…</p>` : ''}
|
|
35
|
-
* ${error.value ? html`<p>Error: ${error.value.message}</p>` : ''}
|
|
36
|
-
* ${posts.value?.map(p => html`<li>${p.title}</li>`)}
|
|
37
|
-
* `
|
|
38
|
-
* })
|
|
39
|
-
* ```
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```ts
|
|
43
|
-
* // Lazy — skip auto-fetch, call refresh() manually
|
|
44
|
-
* component('post-list', () => {
|
|
45
|
-
* const { data: posts, refresh } = useFetch<Post[]>('/api/posts', { lazy: true })
|
|
46
|
-
*
|
|
47
|
-
* useOnConnected(async () => {
|
|
48
|
-
* await refresh()
|
|
49
|
-
* })
|
|
50
|
-
* })
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
import { createComposable, getCurrentComponentContext, ref, useOnConnected } from '@jasonshimmy/custom-elements-runtime'
|
|
55
|
-
import type { ReactiveState } from '@jasonshimmy/custom-elements-runtime'
|
|
56
|
-
|
|
57
|
-
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Return value when `useFetch` is called inside a component render function.
|
|
61
|
-
* All fields are reactive (`ReactiveState`) — the component re-renders
|
|
62
|
-
* automatically when the fetch settles or `refresh()` is called.
|
|
63
|
-
*/
|
|
64
|
-
export interface UseFetchReactiveReturn<T = unknown> {
|
|
65
|
-
/** Reactive fetch result. Access via `.data.value`. */
|
|
66
|
-
data: ReactiveState<T | null>
|
|
67
|
-
/** `true` while the request is in-flight. */
|
|
68
|
-
pending: ReactiveState<boolean>
|
|
69
|
-
/** Set when the request fails; `null` on success. */
|
|
70
|
-
error: ReactiveState<Error | null>
|
|
71
|
-
/** Re-issues the fetch and updates `data`, `pending`, and `error`. */
|
|
72
|
-
refresh(): Promise<void>
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Options for `useFetch()`. Controls caching, SSR behaviour, data transformation, and HTTP request details. */
|
|
76
|
-
export interface UseFetchOptions<T = unknown> {
|
|
77
|
-
/**
|
|
78
|
-
* Unique cache key. Defaults to the full URL string (including query params).
|
|
79
|
-
*
|
|
80
|
-
* On the server, calls sharing the same key within one request reuse the
|
|
81
|
-
* first result without issuing a second network request. On the client,
|
|
82
|
-
* the key is used to consume the hydrated `window.__CER_FETCH_DATA__` entry
|
|
83
|
-
* exactly once — subsequent calls with the same key issue a fresh fetch.
|
|
84
|
-
*/
|
|
85
|
-
key?: string
|
|
86
|
-
/**
|
|
87
|
-
* When `true`, skip the SSR fetch entirely and only fetch on the client.
|
|
88
|
-
* Equivalent to `server: false`.
|
|
89
|
-
*
|
|
90
|
-
* In component context this also skips the `useOnConnected` auto-fetch —
|
|
91
|
-
* call `refresh()` manually when you want the request to fire.
|
|
92
|
-
*/
|
|
93
|
-
lazy?: boolean
|
|
94
|
-
/**
|
|
95
|
-
* When `false`, equivalent to `lazy: true` — skip SSR, fetch client-side.
|
|
96
|
-
*/
|
|
97
|
-
server?: boolean
|
|
98
|
-
/**
|
|
99
|
-
* Factory that returns the initial value before the fetch completes.
|
|
100
|
-
* Called once per `useFetch()` invocation. Defaults to `() => null`.
|
|
101
|
-
*
|
|
102
|
-
* @example `{ default: () => [] }` — start with an empty array
|
|
103
|
-
*/
|
|
104
|
-
default?: () => T
|
|
105
|
-
/**
|
|
106
|
-
* Transform the raw JSON response before storing / returning it.
|
|
107
|
-
* Applied after `pick` (if both are set).
|
|
108
|
-
*/
|
|
109
|
-
transform?: (data: unknown) => T
|
|
110
|
-
/**
|
|
111
|
-
* Pick a subset of keys from an object response.
|
|
112
|
-
* Applied before `transform`. Keys absent in the response become
|
|
113
|
-
* `undefined` in the result.
|
|
114
|
-
*/
|
|
115
|
-
pick?: string[]
|
|
116
|
-
/** HTTP method. Defaults to `'GET'`. */
|
|
117
|
-
method?: string
|
|
118
|
-
/**
|
|
119
|
-
* Request body for POST / PUT / PATCH. Serialised to JSON automatically;
|
|
120
|
-
* sets `Content-Type: application/json` if not already provided.
|
|
121
|
-
*/
|
|
122
|
-
body?: unknown
|
|
123
|
-
/** Additional request headers merged with auto-generated ones. */
|
|
124
|
-
headers?: Record<string, string>
|
|
125
|
-
/**
|
|
126
|
-
* Query parameters appended to the URL via `URLSearchParams`.
|
|
127
|
-
* Included in the default cache `key`.
|
|
128
|
-
*/
|
|
129
|
-
query?: Record<string, string>
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export interface UseFetchReturn<T = unknown> {
|
|
133
|
-
data: T | null
|
|
134
|
-
pending: boolean
|
|
135
|
-
error: Error | null
|
|
136
|
-
/** Re-issues the fetch and updates `data`, `pending`, and `error` in place. */
|
|
137
|
-
refresh(): Promise<UseFetchReturn<T>>
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** A UseFetchReturn that is also awaitable (resolves once the fetch completes). */
|
|
141
|
-
export type UseFetchResult<T = unknown> = UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>
|
|
142
|
-
|
|
143
|
-
// ─── Client-side in-flight deduplication ─────────────────────────────────────
|
|
144
|
-
// P2-3: When two components (or two concurrent refresh() calls) fetch the same
|
|
145
|
-
// key simultaneously, only one HTTP request is issued. The second caller awaits
|
|
146
|
-
// the shared Promise and applies its own transform to the raw response.
|
|
147
|
-
// The map is module-level so all component instances share it.
|
|
148
|
-
// Keys are removed when the Promise settles (resolve or reject).
|
|
149
|
-
const _inflight = new Map<string, Promise<unknown>>()
|
|
150
|
-
|
|
151
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
152
|
-
|
|
153
|
-
// Access fetch via globalThis so that vi.stubGlobal('fetch', mock) works in tests.
|
|
154
|
-
// A direct `fetch(...)` call in an ESM module may bind to the runtime's fetch
|
|
155
|
-
// at module-evaluation time and bypass globalThis mutations.
|
|
156
|
-
function _fetch(url: string, init?: RequestInit): Promise<Response> {
|
|
157
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
-
return ((globalThis as any)['fetch'] as typeof fetch)(url, init)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function applyPick(data: unknown, pick: string[]): unknown {
|
|
162
|
-
if (typeof data !== 'object' || data === null) return data
|
|
163
|
-
return Object.fromEntries(pick.map((k) => [k, (data as Record<string, unknown>)[k]]))
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function applyTransform<T>(raw: unknown, options?: UseFetchOptions<T>): T {
|
|
167
|
-
let result = raw
|
|
168
|
-
if (options?.pick) result = applyPick(result, options.pick)
|
|
169
|
-
if (options?.transform) result = options.transform(result)
|
|
170
|
-
return result as T
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function buildInit(options?: UseFetchOptions): RequestInit {
|
|
174
|
-
const init: RequestInit = { method: options?.method ?? 'GET' }
|
|
175
|
-
const headers: Record<string, string> = { ...options?.headers }
|
|
176
|
-
if (options?.body !== undefined) {
|
|
177
|
-
init.body = JSON.stringify(options.body)
|
|
178
|
-
headers['Content-Type'] = 'application/json'
|
|
179
|
-
}
|
|
180
|
-
if (Object.keys(headers).length > 0) init.headers = headers
|
|
181
|
-
return init
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function appendQuery(url: string, query?: Record<string, string>): string {
|
|
185
|
-
if (!query || Object.keys(query).length === 0) return url
|
|
186
|
-
const sep = url.includes('?') ? '&' : '?'
|
|
187
|
-
return url + sep + new URLSearchParams(query).toString()
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function makeResult<T>(
|
|
191
|
-
state: UseFetchReturn<T>,
|
|
192
|
-
settling: Promise<void> | null,
|
|
193
|
-
): UseFetchResult<T> {
|
|
194
|
-
// Return a wrapper with live getters so callers see state mutations (e.g.
|
|
195
|
-
// after doClientFetch sets state.data), PLUS a custom .then so the result
|
|
196
|
-
// is directly awaitable.
|
|
197
|
-
//
|
|
198
|
-
// Critically, we do NOT add .then to `state` itself — doing so would cause
|
|
199
|
-
// async functions that `return state` (e.g. doClientFetch) to deadlock:
|
|
200
|
-
// the async function's Promise would try to assimilate state as a thenable,
|
|
201
|
-
// calling state.then which waits for settling, which can't resolve until
|
|
202
|
-
// the async function finishes — a circular dependency.
|
|
203
|
-
return {
|
|
204
|
-
get data() { return state.data },
|
|
205
|
-
get pending() { return state.pending },
|
|
206
|
-
get error() { return state.error },
|
|
207
|
-
get refresh() { return state.refresh },
|
|
208
|
-
then(
|
|
209
|
-
onFulfilled?: (v: UseFetchReturn<T>) => unknown,
|
|
210
|
-
onRejected?: (e: unknown) => unknown,
|
|
211
|
-
) {
|
|
212
|
-
const p = settling ?? Promise.resolve()
|
|
213
|
-
// Remove .then from this wrapper BEFORE resolving so the Promise
|
|
214
|
-
// machinery won't treat it as a thenable when resolve(this) is called.
|
|
215
|
-
// After deletion the wrapper becomes a plain UseFetchReturn<T> with
|
|
216
|
-
// live getters — callers can still read .data, .error, etc. and get
|
|
217
|
-
// up-to-date values (e.g. after .refresh()).
|
|
218
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
219
|
-
delete (this as any).then
|
|
220
|
-
const self = this as UseFetchReturn<T>
|
|
221
|
-
return p.then(() => {
|
|
222
|
-
if (onFulfilled) return onFulfilled(self)
|
|
223
|
-
}, onRejected)
|
|
224
|
-
},
|
|
225
|
-
} as UseFetchResult<T>
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ─── Component-context composable factory ─────────────────────────────────────
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Called when `useFetch` detects it is running inside a component render
|
|
232
|
-
* function. The factory is created fresh per call so each component instance
|
|
233
|
-
* gets isolated reactive state; `createComposable` binds lifecycle hooks
|
|
234
|
-
* (`useOnConnected`) to the calling component.
|
|
235
|
-
*/
|
|
236
|
-
function makeComponentFetch<T>(
|
|
237
|
-
url: string | (() => string),
|
|
238
|
-
options: UseFetchOptions<T> | undefined,
|
|
239
|
-
ctx: Record<string, unknown>,
|
|
240
|
-
): UseFetchReactiveReturn<T> {
|
|
241
|
-
const factory = createComposable((): UseFetchReactiveReturn<T> => {
|
|
242
|
-
const isLazy = options?.lazy === true || options?.server === false
|
|
243
|
-
const resolvedUrl = appendQuery(
|
|
244
|
-
typeof url === 'function' ? url() : url,
|
|
245
|
-
options?.query,
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
const data = ref<T | null>(options?.default ? options.default() : null)
|
|
249
|
-
const pending = ref(false)
|
|
250
|
-
const error = ref<Error | null>(null)
|
|
251
|
-
|
|
252
|
-
const doFetch = async (): Promise<void> => {
|
|
253
|
-
pending.value = true
|
|
254
|
-
error.value = null
|
|
255
|
-
try {
|
|
256
|
-
const res = await _fetch(resolvedUrl, buildInit(options))
|
|
257
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`)
|
|
258
|
-
const raw = await res.json()
|
|
259
|
-
data.value = applyTransform<T>(raw, options)
|
|
260
|
-
} catch (err) {
|
|
261
|
-
error.value = err instanceof Error ? err : new Error(String(err))
|
|
262
|
-
} finally {
|
|
263
|
-
pending.value = false
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (!isLazy) {
|
|
268
|
-
useOnConnected(doFetch)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return { data, pending, error, refresh: doFetch }
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
return factory(ctx)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ─── Core implementation ──────────────────────────────────────────────────────
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Isomorphic data fetching composable.
|
|
281
|
-
*
|
|
282
|
-
* - **Inside a `component()` render function**: returns reactive
|
|
283
|
-
* {@link UseFetchReactiveReturn} — `data`, `pending`, and `error` are
|
|
284
|
-
* `ReactiveState` refs that trigger re-renders automatically. The fetch
|
|
285
|
-
* fires via `useOnConnected` unless `lazy: true`.
|
|
286
|
-
* - **Inside a loader / SSR context**: returns a thenable
|
|
287
|
-
* {@link UseFetchResult} — `await useFetch(...)` blocks rendering until data
|
|
288
|
-
* is ready.
|
|
289
|
-
*/
|
|
290
|
-
export function useFetch<T = unknown>(
|
|
291
|
-
url: string | (() => string),
|
|
292
|
-
options?: UseFetchOptions<T>,
|
|
293
|
-
): UseFetchResult<T> | UseFetchReactiveReturn<T> {
|
|
294
|
-
// ── Component context ───────────────────────────────────────────────────────
|
|
295
|
-
// When called inside a component render function, use createComposable so
|
|
296
|
-
// that useOnConnected is bound to the calling component's lifecycle and
|
|
297
|
-
// state is isolated per component instance.
|
|
298
|
-
const componentCtx = getCurrentComponentContext()
|
|
299
|
-
if (componentCtx) {
|
|
300
|
-
return makeComponentFetch(url, options, componentCtx)
|
|
301
|
-
}
|
|
302
|
-
const g = globalThis as Record<string, unknown>
|
|
303
|
-
const resolvedUrl = appendQuery(
|
|
304
|
-
typeof url === 'function' ? url() : url,
|
|
305
|
-
options?.query,
|
|
306
|
-
)
|
|
307
|
-
const key = options?.key ?? resolvedUrl
|
|
308
|
-
const isLazy = options?.lazy === true || options?.server === false
|
|
309
|
-
const defaultValue = (options?.default ? options.default() : null) as T | null
|
|
310
|
-
|
|
311
|
-
// ── Server path ─────────────────────────────────────────────────────────────
|
|
312
|
-
// __CER_FETCH_STORE__ is only present in Node.js (tree-shaken from client bundle).
|
|
313
|
-
const fetchStoreAls = g['__CER_FETCH_STORE__'] as { getStore(): unknown } | undefined
|
|
314
|
-
if (fetchStoreAls) {
|
|
315
|
-
const fetchMap = fetchStoreAls.getStore() as Map<string, unknown> | null
|
|
316
|
-
|
|
317
|
-
if (fetchMap) {
|
|
318
|
-
// Already fetched in this request — return cached result synchronously.
|
|
319
|
-
if (fetchMap.has(key)) {
|
|
320
|
-
const state: UseFetchReturn<T> = {
|
|
321
|
-
data: applyTransform(fetchMap.get(key), options),
|
|
322
|
-
pending: false,
|
|
323
|
-
error: null,
|
|
324
|
-
refresh: async () => state,
|
|
325
|
-
}
|
|
326
|
-
return makeResult(state, null)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (isLazy) {
|
|
330
|
-
const state: UseFetchReturn<T> = {
|
|
331
|
-
data: defaultValue,
|
|
332
|
-
pending: false,
|
|
333
|
-
error: null,
|
|
334
|
-
refresh: async () => state,
|
|
335
|
-
}
|
|
336
|
-
return makeResult(state, null)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Perform the SSR fetch.
|
|
340
|
-
const state: UseFetchReturn<T> = {
|
|
341
|
-
data: defaultValue,
|
|
342
|
-
pending: true,
|
|
343
|
-
error: null,
|
|
344
|
-
refresh: async () => state,
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const settling = (async () => {
|
|
348
|
-
try {
|
|
349
|
-
const res = await _fetch(resolvedUrl, buildInit(options))
|
|
350
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`)
|
|
351
|
-
const raw = await res.json()
|
|
352
|
-
const transformed = applyTransform<T>(raw, options)
|
|
353
|
-
// Store in per-request map so the same key isn't fetched twice.
|
|
354
|
-
fetchMap.set(key, transformed)
|
|
355
|
-
// Also write to the flat accumulator that the entry-server serialises.
|
|
356
|
-
const acc = (g['__CER_FETCH_DATA__'] ?? {}) as Record<string, unknown>
|
|
357
|
-
acc[key] = transformed
|
|
358
|
-
;(g as Record<string, unknown>)['__CER_FETCH_DATA__'] = acc
|
|
359
|
-
state.data = transformed
|
|
360
|
-
state.pending = false
|
|
361
|
-
} catch (err) {
|
|
362
|
-
state.error = err instanceof Error ? err : new Error(String(err))
|
|
363
|
-
state.pending = false
|
|
364
|
-
}
|
|
365
|
-
})()
|
|
366
|
-
|
|
367
|
-
return makeResult(state, settling)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// ── Client hydration path ───────────────────────────────────────────────────
|
|
372
|
-
// Read the pre-fetched data injected by the server into window.__CER_FETCH_DATA__.
|
|
373
|
-
const fetchData = g['__CER_FETCH_DATA__'] as Record<string, unknown> | undefined
|
|
374
|
-
if (fetchData && key in fetchData) {
|
|
375
|
-
const raw = fetchData[key]
|
|
376
|
-
// Consume the key — next navigation will fetch fresh.
|
|
377
|
-
delete fetchData[key]
|
|
378
|
-
const hydrated = applyTransform<T>(raw, options)
|
|
379
|
-
const state: UseFetchReturn<T> = {
|
|
380
|
-
data: hydrated,
|
|
381
|
-
pending: false,
|
|
382
|
-
error: null,
|
|
383
|
-
refresh: () => doClientFetch(key, resolvedUrl, options, state),
|
|
384
|
-
}
|
|
385
|
-
return makeResult(state, null)
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// ── Client fetch ─────────────────────────────────────────────────────────────
|
|
389
|
-
const state: UseFetchReturn<T> = {
|
|
390
|
-
data: defaultValue,
|
|
391
|
-
pending: !isLazy,
|
|
392
|
-
error: null,
|
|
393
|
-
refresh: async () => state,
|
|
394
|
-
}
|
|
395
|
-
state.refresh = () => doClientFetch(key, resolvedUrl, options, state)
|
|
396
|
-
|
|
397
|
-
let settling: Promise<void> | null = null
|
|
398
|
-
if (!isLazy) {
|
|
399
|
-
settling = doClientFetch(key, resolvedUrl, options, state).then(() => undefined)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return makeResult(state, settling)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
async function doClientFetch<T>(
|
|
406
|
-
key: string,
|
|
407
|
-
resolvedUrl: string,
|
|
408
|
-
options: UseFetchOptions<T> | undefined,
|
|
409
|
-
state: UseFetchReturn<T>,
|
|
410
|
-
): Promise<UseFetchReturn<T>> {
|
|
411
|
-
// P2-3: Deduplicate concurrent requests for the same key.
|
|
412
|
-
// If another request for this key is already in-flight, share its raw Promise.
|
|
413
|
-
let rawPromise: Promise<unknown>
|
|
414
|
-
|
|
415
|
-
if (_inflight.has(key)) {
|
|
416
|
-
rawPromise = _inflight.get(key)!
|
|
417
|
-
} else {
|
|
418
|
-
state.pending = true
|
|
419
|
-
state.error = null
|
|
420
|
-
rawPromise = _fetch(resolvedUrl, buildInit(options))
|
|
421
|
-
.then(async (res) => {
|
|
422
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`)
|
|
423
|
-
return res.json()
|
|
424
|
-
})
|
|
425
|
-
.finally(() => { _inflight.delete(key) })
|
|
426
|
-
_inflight.set(key, rawPromise)
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
try {
|
|
430
|
-
const raw = await rawPromise
|
|
431
|
-
state.data = applyTransform<T>(raw, options)
|
|
432
|
-
state.pending = false
|
|
433
|
-
} catch (err) {
|
|
434
|
-
state.error = err instanceof Error ? err : new Error(String(err))
|
|
435
|
-
state.pending = false
|
|
436
|
-
}
|
|
437
|
-
return state
|
|
438
|
-
}
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Describes the `<head>` tags to inject for a page or component.
|
|
3
|
-
* Pass to `useHead()`. All fields are optional; only set fields are emitted.
|
|
4
|
-
*/
|
|
5
|
-
export interface HeadInput {
|
|
6
|
-
title?: string
|
|
7
|
-
meta?: Array<Record<string, string>>
|
|
8
|
-
link?: Array<Record<string, string>>
|
|
9
|
-
script?: Array<Record<string, string>>
|
|
10
|
-
style?: Array<Record<string, string>>
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// SSR: store collector on globalThis so all module instances (server entry +
|
|
14
|
-
// dynamically-imported page chunks) share the same reference. A module-level
|
|
15
|
-
// variable would create separate instances per chunk, breaking the singleton.
|
|
16
|
-
const _g = globalThis as Record<string, unknown>
|
|
17
|
-
const _KEY = '__CER_HEAD_COLLECTOR__'
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Begin collecting head tags for an SSR render pass.
|
|
21
|
-
* Call this before invoking the render function.
|
|
22
|
-
*/
|
|
23
|
-
export function beginHeadCollection(): void {
|
|
24
|
-
_g[_KEY] = [] as HeadInput[]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* End collecting head tags and return the collected inputs.
|
|
29
|
-
* Resets the collector to null.
|
|
30
|
-
*/
|
|
31
|
-
export function endHeadCollection(): HeadInput[] {
|
|
32
|
-
const collected = (_g[_KEY] as HeadInput[]) ?? []
|
|
33
|
-
_g[_KEY] = null
|
|
34
|
-
return collected
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Serializes an array of HeadInput objects into an HTML string
|
|
39
|
-
* suitable for injection into the document <head>.
|
|
40
|
-
*/
|
|
41
|
-
export function serializeHeadTags(heads: HeadInput[]): string {
|
|
42
|
-
const parts: string[] = []
|
|
43
|
-
|
|
44
|
-
// Merge titles — last one wins
|
|
45
|
-
let title: string | undefined
|
|
46
|
-
for (const head of heads) {
|
|
47
|
-
if (head.title !== undefined) {
|
|
48
|
-
title = head.title
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (title !== undefined) {
|
|
52
|
-
parts.push(`<title>${escapeHtml(title)}</title>`)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Merge meta tags, deduplicate by name/property/charset
|
|
56
|
-
const metaMap = new Map<string, Record<string, string>>()
|
|
57
|
-
for (const head of heads) {
|
|
58
|
-
if (head.meta) {
|
|
59
|
-
for (const meta of head.meta) {
|
|
60
|
-
const key = meta.name ?? meta.property ?? meta.charset ?? JSON.stringify(meta)
|
|
61
|
-
metaMap.set(key, meta)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
for (const attrs of metaMap.values()) {
|
|
66
|
-
parts.push(`<meta ${renderAttrs(attrs)}>`)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Merge link tags, deduplicate by rel+href
|
|
70
|
-
const linkMap = new Map<string, Record<string, string>>()
|
|
71
|
-
for (const head of heads) {
|
|
72
|
-
if (head.link) {
|
|
73
|
-
for (const link of head.link) {
|
|
74
|
-
const key = `${link.rel ?? ''}:${link.href ?? ''}`
|
|
75
|
-
linkMap.set(key, link)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
for (const attrs of linkMap.values()) {
|
|
80
|
-
parts.push(`<link ${renderAttrs(attrs)}>`)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Script tags
|
|
84
|
-
for (const head of heads) {
|
|
85
|
-
if (head.script) {
|
|
86
|
-
for (const script of head.script) {
|
|
87
|
-
const { innerHTML, ...rest } = script as Record<string, string>
|
|
88
|
-
if (innerHTML !== undefined) {
|
|
89
|
-
parts.push(`<script ${renderAttrs(rest)}>${innerHTML}</script>`)
|
|
90
|
-
} else {
|
|
91
|
-
parts.push(`<script ${renderAttrs(rest)}></script>`)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Style tags
|
|
98
|
-
for (const head of heads) {
|
|
99
|
-
if (head.style) {
|
|
100
|
-
for (const style of head.style) {
|
|
101
|
-
const { innerHTML, ...rest } = style as Record<string, string>
|
|
102
|
-
if (innerHTML !== undefined) {
|
|
103
|
-
parts.push(`<style ${renderAttrs(rest)}>${innerHTML}</style>`)
|
|
104
|
-
} else {
|
|
105
|
-
parts.push(`<style ${renderAttrs(rest)}></style>`)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return parts.join('\n')
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* useHead composable.
|
|
116
|
-
*
|
|
117
|
-
* - During SSR: pushes to the request-scoped collector (set up via beginHeadCollection)
|
|
118
|
-
* - On the client: imperatively updates document.title and meta/link tags
|
|
119
|
-
*/
|
|
120
|
-
export function useHead(input: HeadInput): void {
|
|
121
|
-
const collector = _g[_KEY] as HeadInput[] | null | undefined
|
|
122
|
-
if (collector != null) {
|
|
123
|
-
// SSR mode: push to the shared globalThis collector
|
|
124
|
-
collector.push(input)
|
|
125
|
-
} else if (typeof document !== 'undefined') {
|
|
126
|
-
// Client-side
|
|
127
|
-
if (input.title !== undefined) {
|
|
128
|
-
document.title = input.title
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (input.meta) {
|
|
132
|
-
for (const meta of input.meta) {
|
|
133
|
-
const name = meta.name ?? meta.property
|
|
134
|
-
if (name) {
|
|
135
|
-
// Try to update existing meta, or create a new one
|
|
136
|
-
let el = document.querySelector(
|
|
137
|
-
meta.name ? `meta[name="${meta.name}"]` : `meta[property="${meta.property}"]`,
|
|
138
|
-
)
|
|
139
|
-
if (!el) {
|
|
140
|
-
el = document.createElement('meta')
|
|
141
|
-
document.head.appendChild(el)
|
|
142
|
-
}
|
|
143
|
-
for (const [k, v] of Object.entries(meta)) {
|
|
144
|
-
el.setAttribute(k, v)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (input.link) {
|
|
151
|
-
for (const link of input.link) {
|
|
152
|
-
const rel = link.rel
|
|
153
|
-
const href = link.href
|
|
154
|
-
if (rel && href) {
|
|
155
|
-
// Canonical and icon links are singletons — find by rel alone so that
|
|
156
|
-
// updating the URL overwrites the existing tag rather than adding a
|
|
157
|
-
// duplicate. All other link types are matched by rel+href (e.g.
|
|
158
|
-
// stylesheets, where multiple hrefs are valid).
|
|
159
|
-
const isSingleton = rel === 'canonical' || rel === 'icon' || rel === 'shortcut icon'
|
|
160
|
-
let el = isSingleton
|
|
161
|
-
? document.querySelector(`link[rel="${rel}"]`)
|
|
162
|
-
: document.querySelector(`link[rel="${rel}"][href="${href}"]`)
|
|
163
|
-
if (!el) {
|
|
164
|
-
el = document.createElement('link')
|
|
165
|
-
document.head.appendChild(el)
|
|
166
|
-
}
|
|
167
|
-
for (const [k, v] of Object.entries(link)) {
|
|
168
|
-
el.setAttribute(k, v)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (input.script) {
|
|
175
|
-
for (const script of input.script) {
|
|
176
|
-
const { innerHTML, src, ...rest } = script as Record<string, string>
|
|
177
|
-
// Only add if not already present (by src)
|
|
178
|
-
if (src && !document.querySelector(`script[src="${src}"]`)) {
|
|
179
|
-
const el = document.createElement('script')
|
|
180
|
-
el.src = src
|
|
181
|
-
for (const [k, v] of Object.entries(rest)) {
|
|
182
|
-
el.setAttribute(k, v)
|
|
183
|
-
}
|
|
184
|
-
document.head.appendChild(el)
|
|
185
|
-
} else if (innerHTML && !src) {
|
|
186
|
-
const type = rest.type ?? ''
|
|
187
|
-
if (type === 'application/ld+json') {
|
|
188
|
-
// JSON-LD is a singleton — update existing content rather than
|
|
189
|
-
// appending a duplicate on re-render or SPA navigation.
|
|
190
|
-
let el = document.querySelector('script[type="application/ld+json"]')
|
|
191
|
-
if (!el) {
|
|
192
|
-
el = document.createElement('script')
|
|
193
|
-
document.head.appendChild(el)
|
|
194
|
-
for (const [k, v] of Object.entries(rest)) {
|
|
195
|
-
el.setAttribute(k, v)
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
;(el as HTMLScriptElement).textContent = innerHTML
|
|
199
|
-
} else {
|
|
200
|
-
const el = document.createElement('script')
|
|
201
|
-
el.textContent = innerHTML
|
|
202
|
-
for (const [k, v] of Object.entries(rest)) {
|
|
203
|
-
el.setAttribute(k, v)
|
|
204
|
-
}
|
|
205
|
-
document.head.appendChild(el)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
214
|
-
|
|
215
|
-
function escapeHtml(str: string): string {
|
|
216
|
-
return str
|
|
217
|
-
.replace(/&/g, '&')
|
|
218
|
-
.replace(/</g, '<')
|
|
219
|
-
.replace(/>/g, '>')
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function escapeAttr(value: string): string {
|
|
223
|
-
return value
|
|
224
|
-
.replace(/&/g, '&')
|
|
225
|
-
.replace(/"/g, '"')
|
|
226
|
-
.replace(/</g, '<')
|
|
227
|
-
.replace(/>/g, '>')
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function renderAttrs(attrs: Record<string, string>): string {
|
|
231
|
-
return Object.entries(attrs)
|
|
232
|
-
.map(([k, v]) => (v === '' ? k : `${k}="${escapeAttr(v)}"`))
|
|
233
|
-
.join(' ')
|
|
234
|
-
}
|