kmcom-nuxt-layers 5.0.4 → 5.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +10 -10
  2. package/docs/USAGE.md +100 -60
  3. package/layers/content/nuxt.config.ts +8 -0
  4. package/layers/core/app/composables/useBrowser.ts +2 -2
  5. package/layers/core/app/utils/browserInfo.ts +7 -7
  6. package/layers/data/auth/server/routes/auth/github.get.ts +2 -0
  7. package/layers/data/database/server/utils/drizzle.ts +1 -0
  8. package/layers/data/database/server/utils/sql.ts +2 -0
  9. package/layers/data/forms/server/api/contact.post.ts +1 -0
  10. package/layers/data/forms/server/api/forms/status.get.ts +1 -0
  11. package/layers/data/mailer/server/utils/config.ts +2 -0
  12. package/layers/data/metadata/providers/comicvine/server/plugins/comicvine-register.ts +4 -0
  13. package/layers/data/metadata/providers/comicvine/server/utils/metadata-comicvine/client.ts +1 -0
  14. package/layers/data/metadata/providers/comicvine/server/utils/metadata-comicvine/provider.ts +7 -0
  15. package/layers/data/metadata/providers/google-books/server/plugins/google-books-register.ts +4 -0
  16. package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/client.ts +1 -0
  17. package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/normalise.ts +1 -0
  18. package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/provider.ts +2 -0
  19. package/layers/data/metadata/providers/openlibrary/server/plugins/openlibrary-register.ts +4 -0
  20. package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.ts +1 -0
  21. package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/provider.ts +6 -0
  22. package/layers/data/metadata/providers/themoviedb/server/plugins/tmdb-register.ts +4 -0
  23. package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/client.ts +1 -0
  24. package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/normalise.ts +1 -0
  25. package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/provider.ts +2 -0
  26. package/layers/data/metadata/server/api/metadata/lookup.get.ts +3 -0
  27. package/layers/data/metadata/server/api/metadata/providers.get.ts +3 -0
  28. package/layers/data/metadata/server/api/metadata/search.get.ts +2 -0
  29. package/layers/data/metadata/server/api/metadata/status.get.ts +3 -0
  30. package/layers/data/metadata/server/api/metadata/sync.post.ts +3 -0
  31. package/layers/data/metadata/server/utils/metadata/cache.ts +1 -0
  32. package/layers/data/metadata/server/utils/metadata/search.ts +2 -0
  33. package/layers/delivery/feeds/server/routes/feed/[collection]/atom/all.get.ts +5 -0
  34. package/layers/delivery/feeds/server/routes/feed/[collection]/atom.get.ts +5 -0
  35. package/layers/delivery/feeds/server/routes/feed/[collection]/json/all.get.ts +5 -0
  36. package/layers/delivery/feeds/server/routes/feed/[collection]/json.get.ts +5 -0
  37. package/layers/delivery/feeds/server/routes/feed/[collection]/rss/all.get.ts +5 -0
  38. package/layers/delivery/feeds/server/routes/feed/[collection]/rss.get.ts +5 -0
  39. package/layers/delivery/feeds/server/routes/feed/atom/all.get.ts +5 -0
  40. package/layers/delivery/feeds/server/routes/feed/atom.get.ts +5 -0
  41. package/layers/delivery/feeds/server/routes/feed/demo.get.ts +4 -0
  42. package/layers/delivery/feeds/server/routes/feed/index.get.ts +2 -0
  43. package/layers/delivery/feeds/server/routes/feed/json/all.get.ts +5 -0
  44. package/layers/delivery/feeds/server/routes/feed/json.get.ts +5 -0
  45. package/layers/delivery/feeds/server/routes/feed/rss/all.get.ts +5 -0
  46. package/layers/delivery/feeds/server/routes/feed/rss.get.ts +5 -0
  47. package/layers/delivery/feeds/server/routes/feed/style.xsl.get.ts +2 -0
  48. package/layers/delivery/feeds/server/utils/cache.ts +1 -1
  49. package/layers/delivery/feeds/server/utils/content-adapter.ts +1 -1
  50. package/layers/delivery/feeds/server/utils/feed-service.ts +3 -1
  51. package/layers/design-system/visual/app/types/index.ts +0 -2
  52. package/layers/motion/scroll/app/components/Motion/ScrollScene.vue +2 -1
  53. package/layers/motion/scroll/app/components/Motion/ScrollStep.vue +2 -1
  54. package/layers/motion/scroll/app/composables/useScrollSteps.ts +0 -2
  55. package/layers/motion/scroll/app/composables/useSmoothScroll.ts +2 -6
  56. package/layers/motion/scroll/app/types/scroll.ts +5 -5
  57. package/layers/motion/scroll/app/utils/scroll.ts +2 -8
  58. package/layers/render/shader/app/types/index.ts +1 -1
  59. package/layers/render/shader/app/types/renderer.ts +1 -1
  60. package/layers/structure/routing/server/api/feature-flags.get.ts +2 -0
  61. package/package.json +6 -2
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm install kmcom-nuxt-layers
13
13
  export default defineNuxtConfig({
14
14
  extends: [
15
15
  'kmcom-nuxt-layers/layers/core',
16
- 'kmcom-nuxt-layers/layers/ui',
16
+ 'kmcom-nuxt-layers/layers/typography',
17
17
  // add more layers as needed
18
18
  ]
19
19
  })
@@ -23,16 +23,16 @@ export default defineNuxtConfig({
23
23
 
24
24
  | Layer | Description |
25
25
  |-------|-------------|
26
- | **core** | Foundation — Nuxt UI, VueUse, Pinia, loading screen, error handling, scroll guard |
27
- | **ui** | Design system — typography, color, media, branding components |
28
- | **layout** | Page structure — grids, containers, spacing |
29
- | **motion** | Animations — GSAP, Locomotive Scroll |
30
- | **shader** | 3D / WebGPU — Three.js, TresJS |
31
- | **forms** | Validation — Zod-based form system |
32
- | **database** | Storage — Drizzle ORM + Neon Postgres |
33
- | **auth** | Authentication — nuxt-auth-utils + GitHub OAuth |
34
- | **theme** | Theming — color mode, accent colors |
26
+ | **core** | Foundation — Nuxt UI, VueUse, Pinia, loading screen, error handling, Element primitives |
27
+ | **theming** / **typography** / **visual** | Design system — accents, type, gradients, modals |
28
+ | **layout** / **navigation** / **routing** | Structure — grids, stacks, nav components, feature flags |
35
29
  | **content** | CMS — @nuxt/content, Nuxt Studio |
30
+ | **database** / **auth** / **metadata** / **mailer** / **forms** | Data — Drizzle + Neon, GitHub OAuth, metadata providers, Resend, Zod forms |
31
+ | **scroll** / **animations** / **transitions** / **page-transitions** / **motion** | Motion — GSAP, Locomotive Scroll |
32
+ | **canvas** / **shader** | Render — TresJS, WebGPU/TSL |
33
+ | **seo** / **scripts** / **feeds** | Delivery — @nuxtjs/seo, @nuxt/scripts, RSS/Atom/JSON feeds |
34
+
35
+ Upgrading from v2: `ui` → `typography` + `navigation` + `visual`, `theme` → `theming`, `starter` removed.
36
36
 
37
37
  All layers depend on `core`. Always list `core` first in `extends`.
38
38
 
package/docs/USAGE.md CHANGED
@@ -10,106 +10,146 @@ npm install kmcom-nuxt-layers
10
10
 
11
11
  ## Basic Usage
12
12
 
13
- Add layers to the `extends` array in your `nuxt.config.ts`. Each layer is referenced by its filesystem path within the package:
13
+ Add layers to the `extends` array in your `nuxt.config.ts`. Each layer is an entry in the package `exports` map:
14
14
 
15
15
  ```ts
16
16
  // nuxt.config.ts
17
17
  export default defineNuxtConfig({
18
- extends: ['kmcom-nuxt-layers/layers/core', 'kmcom-nuxt-layers/layers/ui'],
18
+ extends: ['kmcom-nuxt-layers/layers/core', 'kmcom-nuxt-layers/layers/typography'],
19
19
  })
20
20
  ```
21
21
 
22
- Nuxt resolves each layer via the `package.json` → `"main": "./nuxt.config.ts"` entry. No import maps or special configuration needed.
22
+ Every non-core layer declares its own `extends`, so you only list what you want. A misspelled or
23
+ removed layer name is **not** an error: Nuxt only warns `Cannot extend config from ...` and the
24
+ layer is silently skipped. Check the dev log for that warning after changing `extends`.
23
25
 
24
- ## Available Layers
26
+ ## Requirements
27
+
28
+ - `nuxt >= 4.6.1` (older 4.x resolves a mismatched `@nuxt/schema` and breaks Nitro config types).
29
+ - `typescript.strict: true`. The layers' sources are pulled into your `vue-tsc` graph and are
30
+ written for strict mode.
31
+ - If `@nuxtjs/seo` is enabled, pin `unhead` to a single version (pnpm `overrides`) so Nitro's
32
+ file trace includes `unhead/dist/legacy.mjs`.
33
+
34
+ ### Expected warning: `NUXT_B5023`
35
+
36
+ On `nuxt dev`, `build` and `prepare` you will see one warning per layer:
37
+
38
+ ```
39
+ WARN [NUXT_B5023] The config file .../layers/core/nuxt.config.ts was loaded through jiti
40
+ because the runtime could not load it: Stripping types is currently unsupported for files
41
+ under node_modules
42
+ ```
25
43
 
26
- | Layer | Extends Path | Depends On | Required Dependencies |
27
- | ----------- | ---------------------------------- | ---------- | ------------------------------------------------------------------------------------- |
28
- | **core** | `kmcom-nuxt-layers/layers/core` | — | `nuxt`, `@nuxt/ui`, `pinia`, `@vueuse/nuxt`, `@nuxtjs/device` |
29
- | **ui** | `kmcom-nuxt-layers/layers/ui` | core | — |
30
- | **layout** | `kmcom-nuxt-layers/layers/layout` | core | — |
31
- | **motion** | `kmcom-nuxt-layers/layers/motion` | core | `gsap`, `locomotive-scroll` |
32
- | **shader** | `kmcom-nuxt-layers/layers/shader` | core | `three`, `@tresjs/nuxt`, `@tresjs/core`, `@tresjs/cientos`, `@tresjs/post-processing` |
33
- | **forms** | `kmcom-nuxt-layers/layers/forms` | core | `zod` |
34
- | **theme** | `kmcom-nuxt-layers/layers/theme` | core | — |
35
- | **content** | `kmcom-nuxt-layers/layers/content` | core | `@nuxt/content`, `better-sqlite3` |
44
+ This is expected and harmless. The layers ship `.ts` configs, Node refuses to strip types under
45
+ `node_modules`, and Nuxt falls back to jiti. Nothing is broken; startup is slightly slower.
36
46
 
37
- ## Layer Dependency Rules
47
+ ## Available Layers
38
48
 
39
- 1. **Always include `core` first** — all other layers depend on it
40
- 2. **Order matters** — list `core` before any layer that depends on it
41
- 3. **Only include what you need** — each layer is independent (apart from the core dependency)
49
+ | Layer | Extends Path | Depends On |
50
+ | -------------------- | ------------------------------------------- | ------------------------------------ |
51
+ | **core** | `kmcom-nuxt-layers/layers/core` | — |
52
+ | **theming** | `kmcom-nuxt-layers/layers/theming` | core |
53
+ | **typography** | `kmcom-nuxt-layers/layers/typography` | core |
54
+ | **visual** | `kmcom-nuxt-layers/layers/visual` | core |
55
+ | **layout** | `kmcom-nuxt-layers/layers/layout` | core |
56
+ | **navigation** | `kmcom-nuxt-layers/layers/navigation` | core, scroll, layout, typography |
57
+ | **routing** | `kmcom-nuxt-layers/layers/routing` | core |
58
+ | **content** | `kmcom-nuxt-layers/layers/content` | core |
59
+ | **database** | `kmcom-nuxt-layers/layers/database` | core |
60
+ | **auth** | `kmcom-nuxt-layers/layers/auth` | core |
61
+ | **metadata** | `kmcom-nuxt-layers/layers/metadata` | core |
62
+ | **mailer** | `kmcom-nuxt-layers/layers/mailer` | core |
63
+ | **forms** | `kmcom-nuxt-layers/layers/forms` | mailer |
64
+ | **scroll** | `kmcom-nuxt-layers/layers/scroll` | core |
65
+ | **animations** | `kmcom-nuxt-layers/layers/animations` | scroll |
66
+ | **transitions** | `kmcom-nuxt-layers/layers/transitions` | core |
67
+ | **page-transitions** | `kmcom-nuxt-layers/layers/page-transitions` | core |
68
+ | **motion** | `kmcom-nuxt-layers/layers/motion` | scroll, animations, transitions, ... |
69
+ | **canvas** | `kmcom-nuxt-layers/layers/canvas` | core |
70
+ | **shader** | `kmcom-nuxt-layers/layers/shader` | canvas |
71
+ | **seo** | `kmcom-nuxt-layers/layers/seo` | core |
72
+ | **scripts** | `kmcom-nuxt-layers/layers/scripts` | core |
73
+ | **feeds** | `kmcom-nuxt-layers/layers/feeds` | core, content |
74
+
75
+ Metadata providers: `kmcom-nuxt-layers/layers/metadata/providers/{comicvine,openlibrary,google-books,themoviedb}`.
76
+
77
+ ### Upgrading from v2
78
+
79
+ | v2 | v5 |
80
+ | --------- | ----------------------------------------- |
81
+ | `ui` | `typography` + `navigation` + `visual` |
82
+ | `theme` | `theming` |
83
+ | `starter` | removed (now `apps/starter`, not a layer) |
42
84
 
43
85
  ## Layer-Specific Dependencies
44
86
 
45
87
  Install additional dependencies only for the layers you use:
46
88
 
47
89
  ```bash
48
- # Motion layer
90
+ # motion / animations / scroll
49
91
  npm install gsap locomotive-scroll
50
92
 
51
- # Shader layer
93
+ # canvas / shader
52
94
  npm install three @tresjs/nuxt @tresjs/core @tresjs/cientos @tresjs/post-processing
53
95
 
54
- # Forms layer
96
+ # forms
55
97
  npm install zod
56
98
 
57
- # Content layer
99
+ # content
58
100
  npm install @nuxt/content better-sqlite3
101
+
102
+ # scripts (baseline-status badge)
103
+ npm install @nuxt/scripts baseline-status
59
104
  ```
60
105
 
61
- ## Configuration Examples
106
+ ## Content layer contract
62
107
 
63
- ### Core + UI (design system)
108
+ The `Blog`, `Portfolio` and `Gallery` components and the `useBlogPosts` / `useGalleryItems`
109
+ composables are typed against collections named `blog`, `portfolio` and `gallery`. Define them in
110
+ your app's `content.config.ts` (empty collections are fine):
64
111
 
65
112
  ```ts
66
- export default defineNuxtConfig({
67
- extends: ['kmcom-nuxt-layers/layers/core', 'kmcom-nuxt-layers/layers/ui'],
113
+ export default defineContentConfig({
114
+ collections: {
115
+ content: defineCollection({ type: 'page', source: '**/*.md' }),
116
+ blog: defineCollection({ type: 'page', source: 'blog/**/*.md' }),
117
+ portfolio: defineCollection({ type: 'page', source: 'portfolio/**/*.md' }),
118
+ gallery: defineCollection({ type: 'page', source: 'gallery/**/*.md' }),
119
+ },
68
120
  })
69
121
  ```
70
122
 
71
- ### Core + UI + Layout (full page structure)
123
+ Do not pass `schema:` to `defineCollection`; the layer's `collections.d.ts` augments the item types.
124
+
125
+ ## Configuration Examples
126
+
127
+ ### Design system
72
128
 
73
129
  ```ts
74
130
  export default defineNuxtConfig({
75
131
  extends: [
76
132
  'kmcom-nuxt-layers/layers/core',
77
- 'kmcom-nuxt-layers/layers/ui',
78
- 'kmcom-nuxt-layers/layers/layout',
133
+ 'kmcom-nuxt-layers/layers/theming',
134
+ 'kmcom-nuxt-layers/layers/typography',
135
+ 'kmcom-nuxt-layers/layers/visual',
79
136
  ],
80
137
  })
81
138
  ```
82
139
 
83
- ### Core + Motion (animations)
84
-
85
- ```bash
86
- npm install gsap locomotive-scroll
87
- ```
140
+ ### Page structure + navigation
88
141
 
89
142
  ```ts
90
143
  export default defineNuxtConfig({
91
- extends: ['kmcom-nuxt-layers/layers/core', 'kmcom-nuxt-layers/layers/motion'],
144
+ extends: ['kmcom-nuxt-layers/layers/core', 'kmcom-nuxt-layers/layers/navigation'],
92
145
  })
93
146
  ```
94
147
 
95
- ### Full Stack
96
-
97
- ```bash
98
- npm install gsap locomotive-scroll three @tresjs/nuxt @tresjs/core @tresjs/cientos @tresjs/post-processing zod @nuxt/content better-sqlite3
99
- ```
148
+ ### Motion
100
149
 
101
150
  ```ts
102
151
  export default defineNuxtConfig({
103
- extends: [
104
- 'kmcom-nuxt-layers/layers/core',
105
- 'kmcom-nuxt-layers/layers/ui',
106
- 'kmcom-nuxt-layers/layers/layout',
107
- 'kmcom-nuxt-layers/layers/motion',
108
- 'kmcom-nuxt-layers/layers/shader',
109
- 'kmcom-nuxt-layers/layers/forms',
110
- 'kmcom-nuxt-layers/layers/theme',
111
- 'kmcom-nuxt-layers/layers/content',
112
- ],
152
+ extends: ['kmcom-nuxt-layers/layers/core', 'kmcom-nuxt-layers/layers/motion'],
113
153
  })
114
154
  ```
115
155
 
@@ -138,7 +178,7 @@ export default defineAppConfig({
138
178
  },
139
179
  },
140
180
 
141
- // Theme layer overrides
181
+ // Theming layer overrides
142
182
  themeLayer: {
143
183
  defaultAccent: 'indigo', // Change default accent color
144
184
  accents: ['blue', 'indigo', 'violet', 'purple'], // Limit palette
@@ -148,19 +188,19 @@ export default defineAppConfig({
148
188
 
149
189
  ### Available Config Namespaces
150
190
 
151
- | Namespace | Layer | Key Options |
152
- | ----------------------- | ------ | -------------------------------------------------------------------------- |
153
- | `coreLayer.loading` | core | `enabled`, `minDuration`, `maxDuration`, `background`, `textColor` |
154
- | `coreLayer.scrollGuard` | core | `enabled`, `strict`, `excludeSelectors`, `debug` |
155
- | `coreLayer.errors` | core | `logToConsole`, `logToExternal`, `externalUrl` |
156
- | `coreLayer.notFound` | core | `icon`, `title`, `description`, `showHomeButton`, `actions`, `suggestions` |
157
- | `motion` | motion | `gsapScrollTrigger`, `lenis.*` (duration, smoothWheel, smoothTouch, etc.) |
158
- | `themeLayer` | theme | `accents`, `defaultAccent` |
191
+ | Namespace | Layer | Key Options |
192
+ | ----------------------- | ------- | -------------------------------------------------------------------------- |
193
+ | `coreLayer.loading` | core | `enabled`, `minDuration`, `maxDuration`, `background`, `textColor` |
194
+ | `coreLayer.scrollGuard` | core | `enabled`, `strict`, `excludeSelectors`, `debug` |
195
+ | `coreLayer.errors` | core | `logToConsole`, `logToExternal`, `externalUrl` |
196
+ | `coreLayer.notFound` | core | `icon`, `title`, `description`, `showHomeButton`, `actions`, `suggestions` |
197
+ | `motion` | motion | `gsapScrollTrigger`, `lenis.*` (duration, smoothWheel, smoothTouch, etc.) |
198
+ | `themeLayer` | theming | `accents`, `defaultAccent` |
159
199
 
160
200
  ## Optional Modules
161
201
 
162
202
  Some modules used in the layers are optional or dev-only:
163
203
 
164
204
  - **`@nuxt/eslint`** — Loaded only in development (`NODE_ENV !== 'production'`). Install if you want layer-provided ESLint config during dev.
165
- - **`@vite-pwa/nuxt`** — Loaded only in production by the core layer. Install if you want PWA support.
205
+ - **`@vite-pwa/nuxt`** — Currently disabled in the core layer (commented out). Add it to your own `modules` if you want PWA support; the layer does not type the `pwa` config key for you.
166
206
  - **`nuxt-studio`** — Optional peer dependency of the content layer. Install for Nuxt Studio CMS integration.
@@ -33,6 +33,14 @@ export default defineNuxtConfig({
33
33
 
34
34
  css: ['#layers/content/app/assets/css/content.css'],
35
35
 
36
+ // Nuxt only references layer .d.ts files that live in generated .nuxt types, so register
37
+ // the @nuxt/content frontmatter augmentation explicitly for consumers.
38
+ hooks: {
39
+ 'prepare:types': ({ references }) => {
40
+ references.push({ path: `${import.meta.dirname}/app/types/collections.d.ts` })
41
+ },
42
+ },
43
+
36
44
  compatibilityDate: '2026-01-24',
37
45
 
38
46
  typescript: {
@@ -2,7 +2,7 @@ import {
2
2
  getBrowserVersionParts,
3
3
  isBrowserAtLeast,
4
4
  parseBrowserInfo,
5
- type BrowserInfo,
5
+ type ParsedBrowserInfo,
6
6
  } from '#layers/core/app/utils/browserInfo'
7
7
 
8
8
  /**
@@ -16,7 +16,7 @@ import {
16
16
  * @returns Browser information and helper flags
17
17
  */
18
18
  export function useBrowser() {
19
- const info = ref<BrowserInfo>(parseBrowserInfo())
19
+ const info = ref<ParsedBrowserInfo>(parseBrowserInfo())
20
20
 
21
21
  // Helper flags
22
22
  const isChrome = computed(() => info.value.name === 'chrome')
@@ -1,4 +1,4 @@
1
- export type BrowserInfo = {
1
+ export type ParsedBrowserInfo = {
2
2
  name: string
3
3
  version: string
4
4
  engine: string
@@ -7,14 +7,14 @@ export type BrowserInfo = {
7
7
 
8
8
  type BrowserRule = {
9
9
  match: (ua: string) => boolean
10
- name: BrowserInfo['name']
11
- engine: BrowserInfo['engine']
10
+ name: ParsedBrowserInfo['name']
11
+ engine: ParsedBrowserInfo['engine']
12
12
  version: (ua: string) => string
13
13
  }
14
14
 
15
15
  type OsRule = {
16
16
  match: (ua: string) => boolean
17
- os: BrowserInfo['os']
17
+ os: ParsedBrowserInfo['os']
18
18
  }
19
19
 
20
20
  const OS_RULES: OsRule[] = [
@@ -58,11 +58,11 @@ const BROWSER_RULES: BrowserRule[] = [
58
58
  },
59
59
  ]
60
60
 
61
- function detectOs(ua: string): BrowserInfo['os'] {
61
+ function detectOs(ua: string): ParsedBrowserInfo['os'] {
62
62
  return OS_RULES.find((rule) => rule.match(ua))?.os ?? 'unknown'
63
63
  }
64
64
 
65
- function detectBrowser(ua: string): Pick<BrowserInfo, 'name' | 'version' | 'engine'> {
65
+ function detectBrowser(ua: string): Pick<ParsedBrowserInfo, 'name' | 'version' | 'engine'> {
66
66
  const rule = BROWSER_RULES.find((candidate) => candidate.match(ua))
67
67
  if (!rule) return { name: 'unknown', version: '0', engine: 'unknown' }
68
68
  return {
@@ -72,7 +72,7 @@ function detectBrowser(ua: string): Pick<BrowserInfo, 'name' | 'version' | 'engi
72
72
  }
73
73
  }
74
74
 
75
- export function parseBrowserInfo(userAgent?: string | null): BrowserInfo {
75
+ export function parseBrowserInfo(userAgent?: string | null): ParsedBrowserInfo {
76
76
  if (!import.meta.client) {
77
77
  return {
78
78
  name: 'unknown',
@@ -1,3 +1,5 @@
1
+ import { sendRedirect } from 'h3'
2
+
1
3
  export default defineOAuthGitHubEventHandler({
2
4
  async onSuccess(event, { user }) {
3
5
  await setUserSession(event, {
@@ -1,3 +1,4 @@
1
+ import { useSql } from './sql'
1
2
  import { drizzle } from 'drizzle-orm/neon-http'
2
3
 
3
4
  export function useDrizzle<TSchema extends Record<string, unknown>>(schema: TSchema) {
@@ -1,3 +1,5 @@
1
+ import { createError } from 'h3'
2
+ import { useRuntimeConfig } from 'nitropack/runtime'
1
3
  import { neon } from '@neondatabase/serverless'
2
4
 
3
5
  export function useSql() {
@@ -1,3 +1,4 @@
1
+ import { createError, defineEventHandler, readBody } from 'h3'
1
2
  import { sendContactEmail } from '#layers/mailer/server/utils/email'
2
3
  import { mailerLayerHooks } from '#layers/mailer/server/utils/hooks'
3
4
 
@@ -1,3 +1,4 @@
1
+ import { defineEventHandler } from 'h3'
1
2
  import { useMailerConfig } from '#layers/mailer/server/utils/config'
2
3
 
3
4
  export default defineEventHandler(() => {
@@ -1,3 +1,5 @@
1
+ import { useRuntimeConfig } from 'nitropack/runtime'
2
+
1
3
  type MailerLayerConfig = {
2
4
  resendApiKey: string
3
5
  emailFrom: string
@@ -1,3 +1,7 @@
1
+ import { registerProvider } from '#layers/metadata/server/utils/metadata/provider-registry'
2
+ import { comicVineProvider } from '../utils/metadata-comicvine/provider'
3
+ import { defineNitroPlugin } from 'nitropack/runtime'
4
+
1
5
  export default defineNitroPlugin(() => {
2
6
  registerProvider(comicVineProvider)
3
7
  })
@@ -1,3 +1,4 @@
1
+ import { useRuntimeConfig } from 'nitropack/runtime'
1
2
  import type { ComicVineApiResponse, ComicVineIssue, ComicVineVolume } from './types'
2
3
 
3
4
  const FIELD_LIST_ISSUE =
@@ -1,3 +1,10 @@
1
+ import { normaliseComicVineIssue, normaliseComicVineVolume } from './normalise'
2
+ import {
3
+ fetchComicVineIssue,
4
+ fetchComicVineVolume,
5
+ searchComicVineIssues,
6
+ searchComicVineVolumes,
7
+ } from './client'
1
8
  import type { MetadataProvider } from '#layers/metadata/shared/types'
2
9
 
3
10
  import type { ComicVineIssue, ComicVineVolume } from './types'
@@ -1,3 +1,7 @@
1
+ import { registerProvider } from '#layers/metadata/server/utils/metadata/provider-registry'
2
+ import { googleBooksProvider } from '../utils/metadata-google-books/provider'
3
+ import { defineNitroPlugin } from 'nitropack/runtime'
4
+
1
5
  export default defineNitroPlugin(() => {
2
6
  registerProvider(googleBooksProvider)
3
7
  })
@@ -1,3 +1,4 @@
1
+ import { useRuntimeConfig } from 'nitropack/runtime'
1
2
  import type { MetadataMediaType } from '#layers/metadata/shared/types'
2
3
 
3
4
  import type { GoogleBooksSearchResponse, GoogleBooksVolume } from './types'
@@ -1,3 +1,4 @@
1
+ import { googleBooksCoverUrl, googleBooksMediaType } from './client'
1
2
  import type { MetadataCreator, MetadataRecord } from '#layers/metadata/shared/types'
2
3
 
3
4
  import type { GoogleBooksVolume, GoogleBooksVolumeInfo } from './types'
@@ -1,3 +1,5 @@
1
+ import { normaliseGoogleBooksVolume } from './normalise'
2
+ import { fetchGoogleBooksVolume, searchGoogleBooks } from './client'
1
3
  import type { MetadataProvider } from '#layers/metadata/shared/types'
2
4
 
3
5
  export const googleBooksProvider: MetadataProvider = {
@@ -1,3 +1,7 @@
1
+ import { registerProvider } from '#layers/metadata/server/utils/metadata/provider-registry'
2
+ import { openLibraryProvider } from '../utils/metadata-openlibrary/provider'
3
+ import { defineNitroPlugin } from 'nitropack/runtime'
4
+
1
5
  export default defineNitroPlugin(() => {
2
6
  registerProvider(openLibraryProvider)
3
7
  })
@@ -1,3 +1,4 @@
1
+ import { openLibraryCoverUrl } from './client'
1
2
  import type { MetadataCreator, MetadataRecord } from '#layers/metadata/shared/types'
2
3
 
3
4
  import type { OpenLibraryEdition, OpenLibrarySearchDoc, OpenLibraryWork } from './types'
@@ -1,3 +1,9 @@
1
+ import {
2
+ normaliseOpenLibraryEdition,
3
+ normaliseOpenLibrarySearchDoc,
4
+ normaliseOpenLibraryWork,
5
+ } from './normalise'
6
+ import { lookupOpenLibraryEdition, lookupOpenLibraryWork, searchOpenLibrary } from './client'
1
7
  import type { MetadataProvider } from '#layers/metadata/shared/types'
2
8
 
3
9
  export const openLibraryProvider: MetadataProvider = {
@@ -1,3 +1,7 @@
1
+ import { registerProvider } from '#layers/metadata/server/utils/metadata/provider-registry'
2
+ import { tmdbProvider } from '../utils/metadata-tmdb/provider'
3
+ import { defineNitroPlugin } from 'nitropack/runtime'
4
+
1
5
  export default defineNitroPlugin(() => {
2
6
  registerProvider(tmdbProvider)
3
7
  })
@@ -1,3 +1,4 @@
1
+ import { useRuntimeConfig } from 'nitropack/runtime'
1
2
  import type { TMDBFindResponse, TMDBMovie, TMDBSearchResponse, TMDBTVShow } from './types'
2
3
 
3
4
  function getConfig() {
@@ -1,3 +1,4 @@
1
+ import { tmdbImageUrl } from './client'
1
2
  import type { MetadataCreator, MetadataRecord } from '#layers/metadata/shared/types'
2
3
 
3
4
  import type { TMDBMovie, TMDBSearchMultiResult, TMDBTVShow } from './types'
@@ -1,3 +1,5 @@
1
+ import { normaliseTmdbMovie, normaliseTmdbSearchResult, normaliseTmdbTV } from './normalise'
2
+ import { fetchTmdbMovie, fetchTmdbTV, findByImdbId, searchTmdb } from './client'
1
3
  import type { MetadataProvider } from '#layers/metadata/shared/types'
2
4
 
3
5
  export const tmdbProvider: MetadataProvider = {
@@ -1,3 +1,6 @@
1
+ import { getCacheRecord, makeCacheKey, setCacheRecord } from '../../utils/metadata/cache'
2
+ import { getProvider } from '../../utils/metadata/provider-registry'
3
+ import { createError, defineEventHandler, getQuery } from 'h3'
1
4
  import type { MetadataProviderId } from '#layers/metadata/shared/types'
2
5
 
3
6
  // fallow-ignore-next-line complexity
@@ -1,3 +1,6 @@
1
+ import { getProviders } from '../../utils/metadata/provider-registry'
2
+ import { defineEventHandler } from 'h3'
3
+
1
4
  export default defineEventHandler(() => {
2
5
  return getProviders().map((p) => ({
3
6
  id: p.id,
@@ -1,3 +1,5 @@
1
+ import { searchMetadata } from '../../utils/metadata/search'
2
+ import { createError, defineEventHandler, getQuery } from 'h3'
1
3
  import type { MetadataMediaType, MetadataProviderId } from '#layers/metadata/shared/types'
2
4
 
3
5
  // fallow-ignore-next-line complexity
@@ -1,3 +1,6 @@
1
+ import { getProviders } from '../../utils/metadata/provider-registry'
2
+ import { defineEventHandler } from 'h3'
3
+
1
4
  export default defineEventHandler(async () => {
2
5
  const providers = getProviders()
3
6
 
@@ -1,3 +1,6 @@
1
+ import { getCacheRecord, makeCacheKey, setCacheRecord } from '../../utils/metadata/cache'
2
+ import { getProvider } from '../../utils/metadata/provider-registry'
3
+ import { createError, defineEventHandler, readBody } from 'h3'
1
4
  import type { MetadataProviderId, MetadataSyncInput } from '#layers/metadata/shared/types'
2
5
 
3
6
  // fallow-ignore-next-line complexity
@@ -1,3 +1,4 @@
1
+ import { useStorage } from 'nitropack/runtime'
1
2
  import type { MetadataCacheRecord } from '#layers/metadata/shared/types'
2
3
 
3
4
  // Cache keys: metadata:{provider}:{resourceType}:{providerId}
@@ -1,3 +1,5 @@
1
+ import { getSearchCache, hashQuery, makeCacheKey, setSearchCache } from './cache'
2
+ import { getProviders } from './provider-registry'
1
3
  import type { MetadataSearchInput, MetadataSearchResult } from '#layers/metadata/shared/types'
2
4
 
3
5
  // fallow-ignore-next-line complexity
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../../../utils/cache'
3
+ import { toAtom } from '../../../../utils/formats/atom'
4
+ import { defineEventHandler, getRouterParam, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const collection = getRouterParam(event, 'collection')
3
8
  const { items, config } = await buildFeed(event, collection, { unlimited: true })
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../../utils/cache'
3
+ import { toAtom } from '../../../utils/formats/atom'
4
+ import { defineEventHandler, getRouterParam, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const collection = getRouterParam(event, 'collection')
3
8
  const { items, config } = await buildFeed(event, collection)
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../../../utils/cache'
3
+ import { toJSONFeed } from '../../../../utils/formats/json'
4
+ import { defineEventHandler, getRouterParam, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const collection = getRouterParam(event, 'collection')
3
8
  const { items, config } = await buildFeed(event, collection, { unlimited: true })
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../../utils/cache'
3
+ import { toJSONFeed } from '../../../utils/formats/json'
4
+ import { defineEventHandler, getRouterParam, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const collection = getRouterParam(event, 'collection')
3
8
  const { items, config } = await buildFeed(event, collection)
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../../../utils/cache'
3
+ import { toRSS } from '../../../../utils/formats/rss'
4
+ import { defineEventHandler, getRouterParam, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const collection = getRouterParam(event, 'collection')
3
8
  const { items, config } = await buildFeed(event, collection, { unlimited: true })
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../../utils/cache'
3
+ import { toRSS } from '../../../utils/formats/rss'
4
+ import { defineEventHandler, getRouterParam, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const collection = getRouterParam(event, 'collection')
3
8
  const { items, config } = await buildFeed(event, collection)
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../../utils/cache'
3
+ import { toAtom } from '../../../utils/formats/atom'
4
+ import { defineEventHandler, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const { items, config } = await buildFeed(event, undefined, { unlimited: true })
3
8
  const content = toAtom(items, config)
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../utils/cache'
3
+ import { toAtom } from '../../utils/formats/atom'
4
+ import { defineEventHandler, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const { items, config } = await buildFeed(event)
3
8
  const content = toAtom(items, config)
@@ -1,3 +1,7 @@
1
+ import { buildFeed } from '../../utils/feed-service'
2
+ import { defineEventHandler, getRequestURL, setHeader } from 'h3'
3
+ import { useAppConfig } from 'nitropack/runtime'
4
+
1
5
  const RSS_ICON = `<svg viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
2
6
  <path d="M2.5 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5V3Z"/>
3
7
  <path d="M4 8a6 6 0 0 1 6 6H8.5A4.5 4.5 0 0 0 4 9.5V8Z"/>
@@ -1,3 +1,5 @@
1
+ import { defineEventHandler, sendRedirect } from 'h3'
2
+
1
3
  export default defineEventHandler((event) => {
2
4
  return sendRedirect(event, '/feed/rss', 302)
3
5
  })
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../../utils/cache'
3
+ import { toJSONFeed } from '../../../utils/formats/json'
4
+ import { defineEventHandler, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const { items, config } = await buildFeed(event, undefined, { unlimited: true })
3
8
  const content = toJSONFeed(items, config)
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../utils/cache'
3
+ import { toJSONFeed } from '../../utils/formats/json'
4
+ import { defineEventHandler, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const { items, config } = await buildFeed(event)
3
8
  const body = toJSONFeed(items, config)
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../../utils/cache'
3
+ import { toRSS } from '../../../utils/formats/rss'
4
+ import { defineEventHandler, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const { items, config } = await buildFeed(event, undefined, { unlimited: true })
3
8
  const content = toRSS(items, config)
@@ -1,3 +1,8 @@
1
+ import { buildFeed } from '../../utils/feed-service'
2
+ import { setFeedCacheHeaders } from '../../utils/cache'
3
+ import { toRSS } from '../../utils/formats/rss'
4
+ import { defineEventHandler, setHeader } from 'h3'
5
+
1
6
  export default defineEventHandler(async (event) => {
2
7
  const { items, config } = await buildFeed(event)
3
8
  const content = toRSS(items, config)
@@ -1,3 +1,5 @@
1
+ import { defineEventHandler, setHeader } from 'h3'
2
+
1
3
  export default defineEventHandler((event) => {
2
4
  setHeader(event, 'Content-Type', 'text/xsl; charset=utf-8')
3
5
  setHeader(event, 'Cache-Control', 'public, max-age=3600')
@@ -1,5 +1,5 @@
1
+ import { setHeader, type H3Event } from 'h3'
1
2
  import { createHash } from 'node:crypto'
2
- import type { H3Event } from 'h3'
3
3
 
4
4
  export function setFeedCacheHeaders(event: H3Event, content: string): void {
5
5
  const etag = `"${createHash('md5').update(content).digest('hex')}"`
@@ -1,5 +1,5 @@
1
+ import { createError, type H3Event } from 'h3'
1
2
  import { queryCollection } from '@nuxt/content/nitro'
2
- import type { H3Event } from 'h3'
3
3
 
4
4
  import { resolveFeedAuthor, resolveFeedDate } from './feed-author'
5
5
  import type { FeedItem } from './types'
@@ -1,4 +1,6 @@
1
- import type { H3Event } from 'h3'
1
+ import { getContentFeedItems } from './content-adapter'
2
+ import { getRequestURL, type H3Event } from 'h3'
3
+ import { useAppConfig } from 'nitropack/runtime'
2
4
 
3
5
  import {
4
6
  createFeedConfig,
@@ -2,5 +2,3 @@ export type * from './accent'
2
2
  export type * from './breakpoints'
3
3
  export type * from './gradient'
4
4
  export type * from './media'
5
- export type { TintLevel, TintColorSlot, TintConfig } from './tint'
6
- export { TINT_LEVEL_OPACITY } from './tint'
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import type { ScrollTrigger as GSAPScrollTrigger } from 'gsap/ScrollTrigger'
3
3
 
4
- import { SCROLL_SCENE_KEY, type ScrollSceneContext } from '../../composables/useScrollSteps'
4
+ import type { ScrollSceneContext } from '../../types/scroll'
5
+ import { SCROLL_SCENE_KEY } from '../../composables/useScrollSteps'
5
6
 
6
7
  const {
7
8
  name,
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { SCROLL_SCENE_KEY, type ScrollSceneContext } from '../../composables/useScrollSteps'
2
+ import type { ScrollSceneContext } from '../../types/scroll'
3
+ import { SCROLL_SCENE_KEY } from '../../composables/useScrollSteps'
3
4
 
4
5
  const { index } = defineProps<{
5
6
  /**
@@ -2,8 +2,6 @@ import type { ScrollSceneContext } from '../types/scroll'
2
2
 
3
3
  export const SCROLL_SCENE_KEY = Symbol('motionScrollScene')
4
4
 
5
- export type { ScrollSceneContext }
6
-
7
5
  /**
8
6
  * Step/waypoint state tracking — must be used inside a MotionScrollScene.
9
7
  *
@@ -1,12 +1,8 @@
1
1
  import type { Ref } from 'vue'
2
2
  import type LocomotiveScroll from 'locomotive-scroll'
3
3
 
4
- import {
5
- runLocomotiveScroll,
6
- runNativeScroll,
7
- type ScrollTarget,
8
- type ScrollToOptions,
9
- } from '../utils/scroll'
4
+ import { runLocomotiveScroll, runNativeScroll, type ScrollTarget } from '../utils/scroll'
5
+ import type { ScrollToOptions } from '../types/scroll'
10
6
 
11
7
  /**
12
8
  * Unified scroll composable using Locomotive Scroll v5 smooth scrolling
@@ -1,11 +1,11 @@
1
1
  import type { ComputedRef, Ref } from 'vue'
2
2
 
3
3
  export type ScrollToOptions = {
4
- offset?: number
5
- duration?: number
6
- immediate?: boolean
7
- lock?: boolean
8
- onComplete?: () => void
4
+ offset?: number | undefined
5
+ duration?: number | undefined
6
+ immediate?: boolean | undefined
7
+ lock?: boolean | undefined
8
+ onComplete?: (() => void) | undefined
9
9
  }
10
10
 
11
11
  export type SectionProgressOptions = {
@@ -1,14 +1,8 @@
1
1
  import type LocomotiveScroll from 'locomotive-scroll'
2
2
 
3
- export type ScrollTarget = string | number | HTMLElement
3
+ import type { ScrollToOptions } from '../types/scroll'
4
4
 
5
- export type ScrollToOptions = {
6
- offset?: number | undefined
7
- duration?: number | undefined
8
- immediate?: boolean | undefined
9
- lock?: boolean | undefined
10
- onComplete?: (() => void) | undefined
11
- }
5
+ export type ScrollTarget = string | number | HTMLElement
12
6
 
13
7
  function resolveScrollOffset(options?: ScrollToOptions) {
14
8
  return options?.offset ?? 0
@@ -1,4 +1,4 @@
1
1
  export type * from './tsl'
2
2
  export type * from './uniforms'
3
3
  export type * from './materials'
4
- export * from '#layers/canvas/types/renderer'
4
+ export * from '../../../canvas/app/types/renderer'
@@ -1 +1 @@
1
- export * from '#layers/canvas/types/renderer'
1
+ export * from '../../../canvas/app/types/renderer'
@@ -1,3 +1,5 @@
1
+ import { defineEventHandler } from 'h3'
2
+ import { useAppConfig } from 'nitropack/runtime'
1
3
  import type { FeatureValue } from '../../app/types/routing'
2
4
 
3
5
  export default defineEventHandler(() => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kmcom-nuxt-layers",
3
3
  "private": false,
4
- "version": "5.0.4",
4
+ "version": "5.0.6",
5
5
  "description": "Composable Nuxt 4 layers for building scalable Vue applications",
6
6
  "exports": {
7
7
  "./layers/core": "./layers/core/nuxt.config.ts",
@@ -84,10 +84,11 @@
84
84
  "@tresjs/post-processing": "^3.7.2",
85
85
  "@vite-pwa/nuxt": "^1.1.1",
86
86
  "@vueuse/nuxt": "^14.3.0 || ^15.0.0",
87
+ "baseline-status": "^1.1.1",
87
88
  "better-sqlite3": "^12.10.0 || ^13.0.0",
88
89
  "gsap": "^3.15.0",
89
90
  "locomotive-scroll": "^5.0.1",
90
- "nuxt": "^4.4.7",
91
+ "nuxt": "^4.6.1",
91
92
  "pinia": "^3.0.4 || ^4.1.0",
92
93
  "tailwindcss": "^4.3.0",
93
94
  "three": ">=0.184.0",
@@ -145,6 +146,9 @@
145
146
  },
146
147
  "v-gsap-nuxt": {
147
148
  "optional": true
149
+ },
150
+ "baseline-status": {
151
+ "optional": true
148
152
  }
149
153
  },
150
154
  "devDependencies": {