Repository: enzonotario/vitepress-openapi
Pull request #98 (2024-10-23 02:20:43)

chore(openapi): add createOpenApiInstance
# Description

- Unified `useOpenapi` instance to support multiple schemas
- Adds `usePaths` composable to build paths
- Change `useTheme` composable to be able to config it via object like `useTheme({ i18n: { ... } })`

## Related issues/external references


## Types of changes

- New feature


vercel[bot]: [vc]: #0fKTa/bVK8eK5KHZAUI1p6dmKw3ZFImzNG+IL9nLM5w=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ2aXRlcHJlc3Mtb3BlbmFwaSIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9lbnpvLW5vdGFyaW9zLXByb2plY3RzL3ZpdGVwcmVzcy1vcGVuYXBpL0FlVnVnM2lCb2lKQVl4S3hOMjd3b3M4bkRpVFgiLCJwcmV2aWV3VXJsIjoidml0ZXByZXNzLW9wZW5hcGktZ2l0LWZlYXR1cmUtb3BlbmFwaTItZW56by1ub3Rhcmlvcy1wcm9qZWN0cy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IlBFTkRJTkciLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoidml0ZXByZXNzLW9wZW5hcGktZ2l0LWZlYXR1cmUtb3BlbmFwaTItZW56by1ub3Rhcmlvcy1wcm9qZWN0cy52ZXJjZWwuYXBwIn19XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **vitepress-openapi** | 🔄 Building ([Inspect](https://vercel.com/enzo-notarios-projects/vitepress-openapi/AeVug3iBoiJAYxKxN27wos8nDiTX)) | [Visit Preview](https://vercel.live/open-feedback/vitepress-openapi-git-feature-openapi2-enzo-notarios-projects.vercel.app?via=pr-comment-visit-preview-link&passThrough=1) | 💬 [**Add feedback**](https://vercel.live/open-feedback/vitepress-openapi-git-feature-openapi2-enzo-notarios-projects.vercel.app?via=pr-comment-feedback-link) | Oct 23, 2024 2:20am |


coderabbitai[bot]: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->

> [!NOTE]
> Currently processing new changes in this PR. This may take a few minutes, please wait...
> 
> 	<details>
> <summary>📥 Commits</summary>
> 
> Files that changed from the base of the PR and between 4849877e2e9f84f4169926e682090bffbce5e58a and 8d1e1cef4b5f441fb663e1785b3852663bca3ad2.
> 
> </details>
> 
> <details>
> <summary>📒 Files selected for processing (30)</summary>
> 
> * dev/.vitepress/theme/index.ts (1 hunks)
> * dev/operations/[operationId].paths.js (1 hunks)
> * docs/.vitepress/theme/index.ts (1 hunks)
> * docs/example/operations/[operationId].md (0 hunks)
> * docs/example/operations/[operationId].paths.js (1 hunks)
> * docs/guide/getting-started.md (1 hunks)
> * docs/layouts/one-operation.md (1 hunks)
> * package.json (1 hunks)
> * src/components/Common/OAInfo.vue (1 hunks)
> * src/components/Common/OAOperation.vue (5 hunks)
> * src/components/Common/OAServers.vue (1 hunks)
> * src/components/Common/OASpec.vue (3 hunks)
> * src/components/Path/OAPath.vue (2 hunks)
> * src/components/Path/OAPaths.vue (2 hunks)
> * src/components/Path/OAPathsByTags.vue (4 hunks)
> * src/components/Path/OAPathsSummary.vue (0 hunks)
> * src/components/Schema/OASchemaProperty.vue (1 hunks)
> * src/components/ui/collapsible/Collapsible.vue (1 hunks)
> * src/components/ui/select/SelectItem.vue (1 hunks)
> * src/components/ui/tabs/Tabs.vue (1 hunks)
> * src/composables/useOpenapi.ts (1 hunks)
> * src/composables/usePaths.ts (1 hunks)
> * src/composables/useSidebar.ts (1 hunks)
> * src/index.ts (2 hunks)
> * src/lib/OpenApi.ts (4 hunks)
> * src/lib/createOpenApiInstance.ts (1 hunks)
> * src/lib/getOpenApiInstance.ts (1 hunks)
> * src/lib/parseSpec.ts (1 hunks)
> * src/lib/transformSpec.ts (1 hunks)
> * test/composables/openapi.test.ts (7 hunks)
> 
> </details>
> 
> 	```ascii
>  _________________________________________________________________
> < This is Sparta! And I'm here to kick bugs into the pit of doom. >
>  -----------------------------------------------------------------
>   \
>    \   \
>         \ /\
>         ( )
>       .( o ).
> ```

<!-- end of auto-generated comment: review in progress by coderabbit.ai --><!-- internal state start -->

<!--

Q===

-->

<!-- internal state end --><!-- usage_tips_start -->

> [!TIP]
> <details>
> <summary>You can disable the changed files summary in the walkthrough.</summary>
> 
> Disable the `reviews.changed_files_summary` setting in your project's settings in CodeRabbit to disable the changed files summary in the walkthrough.
> 
> </details>

<!-- usage_tips_end --><!-- tips_start -->

---

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://coderabbit.ai/docs) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
pkg-pr-new[bot]: 
[Open in Stackblitz](https://pkg.pr.new/template/9253d169-4162-479d-b6c4-79ee9ce383d9)


```
pnpm add https://pkg.pr.new/vitepress-openapi@98
```


```
pnpm add https://pkg.pr.new/vitepress-openapi@98
```


_commit: <a href="https://github.com/enzonotario/vitepress-openapi/runs/31923376027"><code>8d1e1ce</code></a>_


Patch:
diff --git a/dev/.vitepress/theme/index.ts b/dev/.vitepress/theme/index.ts
index 3e39d50..3767312 100644
--- a/dev/.vitepress/theme/index.ts
+++ b/dev/.vitepress/theme/index.ts
@@ -1,4 +1,4 @@
-import { theme, useOpenapi, useTheme } from 'vitepress-openapi'
+import { theme, useOpenapi } from 'vitepress-openapi'
 import DefaultTheme from 'vitepress/theme'
 import spec from '../../../docs/public/openapi.json' assert {type: 'json'}
 
@@ -6,16 +6,16 @@ export default {
   extends: DefaultTheme,
   enhanceApp({ app }) {
     // Set the OpenAPI specification.
-    const openapi = useOpenapi({ spec })
-    app.provide('openapi', openapi)
-
-    // Configure the theme.
-    const themeConfig = useTheme()
-    themeConfig.setI18nConfig({
-      locale: 'es',
+    const openapi = useOpenapi({
+      spec,
+      config: {
+        i18n: {
+          locale: 'es',
+        },
+      },
     })
 
     // Use the theme.
-    theme.enhanceApp({ app })
+    theme.enhanceApp({ app, openapi })
   },
 }
diff --git a/dev/criptoya-argentina/index.md b/dev/criptoya-argentina/index.md
index 50f11c8..2fff151 100644
--- a/dev/criptoya-argentina/index.md
+++ b/dev/criptoya-argentina/index.md
@@ -14,12 +14,30 @@ const route = useRoute()
 
 const { isDark } = useData()
 
-useTheme().setJsonViewerDeep(1)
-useTheme().setSchemaViewerDeep(1)
+useTheme({
+    jsonViewer: {
+        deep: 1,
+    },
+    schemaViewer: {
+        deep: 1,
+    },
+    request: {
+        defaultView: 'schema',
+    },
+})
 
 onUnmounted(() => {
-    useTheme().setJsonViewerDeep(Infinity)
-    useTheme().setSchemaViewerDeep(Infinity)
+    useTheme({
+        jsonViewer: {
+            deep: Infinity,
+        },
+        schemaViewer: {
+            deep: Infinity,
+        },
+        request: {
+            defaultView: 'json',
+        },
+    })
 })
 </script>
 
diff --git a/dev/multiple-specs.md b/dev/multiple-specs.md
index 9f9a8c6..be19a8f 100644
--- a/dev/multiple-specs.md
+++ b/dev/multiple-specs.md
@@ -7,13 +7,12 @@ title: vitepress-openapi
 <script setup lang="ts">
 import { useRoute, useData } from 'vitepress'
 import { useOpenapi } from 'vitepress-openapi'
-import spec from '../docs/public/openapi.json'
 import specV2 from '../docs/public/openapi-v2.json'
 
 const { isDark } = useData()
 </script>
 
-<OAOperation operationId="getAllArtists" :spec="spec" :isDark="isDark" />
+<OAOperation operationId="getAllArtists" :isDark="isDark" />
 
 ---
 
diff --git a/dev/operations/[operationId].paths.js b/dev/operations/[operationId].paths.js
index c6a303c..ffc6a3f 100644
--- a/dev/operations/[operationId].paths.js
+++ b/dev/operations/[operationId].paths.js
@@ -1,17 +1,17 @@
-import { OpenApi } from 'vitepress-openapi'
+import { usePaths } from 'vitepress-openapi'
 import spec from '../../docs/public/openapi.json' assert { type: 'json' }
 
 export default {
     paths() {
-        const openapi = OpenApi({ spec })
-
-        return openapi.getPathsByVerbs().map(({ operationId, summary }) => {
-            return {
-                params: {
-                    operationId,
-                    pageTitle: `${summary} - vitepress-openapi`,
-                },
-            }
-        })
+        return usePaths({ spec })
+            .getPathsByVerbs()
+            .map(({ operationId, summary }) => {
+                return {
+                    params: {
+                        operationId,
+                        pageTitle: `${summary} - vitepress-openapi`,
+                    },
+                }
+            })
     },
 }
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
index 078db67..b1647f3 100644
--- a/docs/.vitepress/theme/index.ts
+++ b/docs/.vitepress/theme/index.ts
@@ -7,10 +7,11 @@ export default {
   extends: DefaultTheme,
   enhanceApp({ app }) {
     // Set the OpenAPI specification.
-    const openapi = useOpenapi({ spec })
-    app.provide('openapi', openapi)
+    const openapi = useOpenapi({
+      spec,
+    })
 
     // Use the theme.
-    theme.enhanceApp({ app })
+    theme.enhanceApp({ app, openapi })
   },
 }
diff --git a/docs/composables/useTheme.md b/docs/composables/useTheme.md
index 0898947..6da5e32 100644
--- a/docs/composables/useTheme.md
+++ b/docs/composables/useTheme.md
@@ -9,72 +9,69 @@ import { useTheme, locales } from 'vitepress-openapi'
 
 export default {
     async enhanceApp({app, router, siteData}) {
-        const themeConfig = useTheme()
-        
-        // Set the default schema view.
-        themeConfig.setSchemaDefaultView('schema') // schema or contentType
-        
-        // Set the JSON viewer depth.
-        themeConfig.setJsonViewerDeep(Infinity)
-        
-        // Set the schema viewer depth.
-        themeConfig.setSchemaViewerDeep(Infinity)
-        
-        // Set the heading levels.
-        themeConfig.setHeadingLevels({
-            h1: 1,
-            h2: 2,
-            h3: 3,
-            h4: 4,
-            h5: 5,
-            h6: 6,
-        })
-        
-        // Set the response code selector.
-        themeConfig.setResponseCodeSelector('tabs') // tabs or select
-        
-        // Set the maximum number of tabs, after which a Select will be shown.
-        themeConfig.setResponseCodeMaxTabs(5)
-        
-        // Set the mode of the JSON editor.
-        themeConfig.setPlaygroundJsonEditorMode('tree') // text, tree, or table
-        
-        // Set the visibility of the main menu bar.
-        themeConfig.setPlaygroundJsonEditorMainMenuBar(false)
-        
-        // Set the visibility of the navigation bar.
-        themeConfig.setPlaygroundJsonEditorNavigationBar(false)
-        
-        // Get the current operation badges.
-        themeConfig.getOperationBadges() // ['deprecated']
-        
-        // Set the operation badges.
-        themeConfig.setOperationBadges(['deprecated'])
-        
-        // Get the current i18n configuration.
-        themeConfig.getI18nConfig() // { locale: 'en', fallbackLocale: 'en', messages: locales }
-        
-        // Set the i18n configuration.
-        themeConfig.setI18nConfig({
-            locale: 'en', // en or es
-            fallbackLocale: 'en', // en or es
-            messages: {
-                en: {
-                    ...locales.en,
-                    'operation.badgePrefix.operationId': 'Operation ID',
+        useTheme({
+            request: {
+                // Set the default schema view.
+                defaultView: 'schema', // schema or contentType
+            },
+            jsonViewer: {
+                // Set the JSON viewer depth.
+                deep: Infinity,
+            },
+            schemaViewer: {
+                // Set the schema viewer depth.
+                deep: Infinity,
+            },
+            // Set the heading levels.
+            headingLevels: {
+                h1: 1,
+                h2: 2,
+                h3: 3,
+                h4: 4,
+                h5: 5,
+                h6: 6,
+            },
+            response: {
+                // Set the response code selector.
+                codeSelector: 'tabs', // tabs or select
+                // Set the maximum number of tabs, after which a Select will be shown.
+                codeMaxTabs: 5,
+            },
+            playground: {
+                jsonEditor: {
+                    // Set the mode of the JSON editor.
+                    mode: 'tree', // text, tree, or table
+                    // Set the visibility of the main menu bar.
+                    mainMenuBar: false,
+                    // Set the visibility of the navigation bar.
+                    navigationBar: false,
                 },
-                es: {
-                    ...locales.es,
-                    'operation.badgePrefix.operationId': 'ID de operación',
+            },
+            operation: {
+                // Set the operation badges. The order is respected.
+                badges: ['deprecated'],
+            },
+            // Set the i18n configuration.
+            i18n: {
+                locale: 'en', // en or es
+                fallbackLocale: 'en', // en or es
+                messages: {
+                    en: {
+                        ...locales.en,
+                        'operation.badgePrefix.operationId': 'Operation ID',
+                    },
+                    es: {
+                        ...locales.es,
+                        'operation.badgePrefix.operationId': 'ID de operación',
+                    },
                 },
             },
-        })
-        
-        // Set spec configuration.
-        themeConfig.setSpecConfig({
-            groupByTags: true,
-            collapsePaths: false,
-            showPathsSummary: true,
+            // Set spec configuration.
+            spec: {
+                groupByTags: true,
+                collapsePaths: false,
+                showPathsSummary: true,
+            },
         })
     }
 }
diff --git a/docs/customizations/operation-badges.md b/docs/customizations/operation-badges.md
index 96067e2..c04d39d 100644
--- a/docs/customizations/operation-badges.md
+++ b/docs/customizations/operation-badges.md
@@ -10,7 +10,7 @@ Each operation can have different badges that indicate its state, for example if
 - `deprecated`
 - `operationId`
 
-By default, only the `deprecated` badge is shown, as appropriate. You can customize the operation badges using the `useTheme().setOperationBadges()` method. **The order in which you set the badges is the order in which they will be displayed.**
+By default, only the `deprecated` badge is shown, as appropriate. You can customize the operation badges using the `useTheme({ operation: { badges: string[] })` function. **The order in which you set the badges is the order in which they will be displayed.**
 
 For example:
 
@@ -28,8 +28,11 @@ import spec from '../public/openapi.json'
 
 const { isDark } = useData()
 
-const themeConfig = useTheme()
-themeConfig.setOperationBadges(['deprecated', 'operationId'])
+useTheme({
+    operation: {
+        badges: ['deprecated', 'operationId'],
+    },
+})
 </script>
 
 <OAOperation :spec="spec" operationId="getAllArtists" :isDark="isDark" />
@@ -45,17 +48,17 @@ import 'vitepress-openapi/dist/style.css'
 export default {
     extends: DefaultTheme,
     enhanceApp({ app }) {
-        // Optionally, set the i18n configuration.
-        const themeConfig = useTheme()
-        themeConfig.setI18nConfig({
-            messages: {
-                en: {
-                    ...locales.en,
-                    'operation.badgePrefix.operationId': 'Operation ID: ',
-                },
-                es: {
-                    ...locales.es,
-                    'operation.badgePrefix.operationId': 'ID de operación: ',
+        useTheme({
+            i18n: {
+                messages: {
+                    en: {
+                        ...locales.en,
+                        'operation.badgePrefix.operationId': 'Operation ID: ',
+                    },
+                    es: {
+                        ...locales.es,
+                        'operation.badgePrefix.operationId': 'ID de operación: ',
+                    },
                 },
             },
         })
@@ -79,14 +82,20 @@ import spec from '../public/openapi.json'
 
 const { isDark } = useData()
 
-const themeConfig = useTheme()
-
 onMounted(() => {
-    themeConfig.setOperationBadges(['deprecated', 'operationId'])
+    useTheme({
+        operation: {
+            badges: ['deprecated', 'operationId'],
+        },
+    })
 })
 
 onUnmounted(() => {
-    themeConfig.setOperationBadges(['deprecated'])
+    useTheme({
+        operation: {
+            badges: ['deprecated'],
+        },
+    })
 })
 </script>
 
diff --git a/docs/example/operations/[operationId].md b/docs/example/operations/[operationId].md
index 83d6dad..428b256 100644
--- a/docs/example/operations/[operationId].md
+++ b/docs/example/operations/[operationId].md
@@ -6,14 +6,11 @@ title: vitepress-openapi
 
 <script setup lang="ts">
 import { useRoute, useData } from 'vitepress'
-import { useOpenapi } from 'vitepress-openapi'
 
 const route = useRoute()
 
 const { isDark } = useData()
 
-const openapi = useOpenapi()
-
 const operationId = route.data.params.operationId
 </script>
 
diff --git a/docs/example/operations/[operationId].paths.js b/docs/example/operations/[operationId].paths.js
index 98f6021..e2cc40f 100644
--- a/docs/example/operations/[operationId].paths.js
+++ b/docs/example/operations/[operationId].paths.js
@@ -1,27 +1,17 @@
-import { useOpenapi, httpVerbs } from 'vitepress-openapi'
-import spec from '../../public/openapi.json' assert { type: 'json' }
+import { usePaths } from 'vitepress-openapi'
+import spec from '../../public/openapi.json' assert {type: 'json'}
 
 export default {
     paths() {
-        const openapi = useOpenapi({ spec })
-
-        if (!openapi?.json?.paths) {
-            return []
-        }
-
-        return Object.keys(openapi.json.paths)
-            .flatMap((path) => {
-                return httpVerbs
-                    .filter((verb) => openapi.json.paths[path][verb])
-                    .map((verb) => {
-                        const { operationId, summary } = openapi.json.paths[path][verb]
-                        return {
-                            params: {
-                                operationId,
-                                pageTitle: `${summary} - vitepress-openapi`,
-                            },
-                        }
-                    })
+        return usePaths({ spec })
+            .getPathsByVerbs()
+            .map(({ operationId, summary }) => {
+                return {
+                    params: {
+                        operationId,
+                        pageTitle: `${summary} - vitepress-openapi`,
+                    },
+                }
             })
     },
 }
diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md
index 6e820fe..b998d00 100644
--- a/docs/guide/getting-started.md
+++ b/docs/guide/getting-started.md
@@ -54,11 +54,12 @@ export default {
     extends: DefaultTheme,
     async enhanceApp({ app, router, siteData }) {
         // Set the OpenAPI specification.
-        const openapi = useOpenapi({ spec })
-        app.provide('openapi', openapi)
+        const openapi = useOpenapi({
+            spec,
+        })
 
         // Use the theme.
-        theme.enhanceApp({ app })
+        theme.enhanceApp({ app, openapi })
     }
 } satisfies Theme
 ```
diff --git a/docs/layouts/one-operation.md b/docs/layouts/one-operation.md
index 31ff1be..0db3827 100644
--- a/docs/layouts/one-operation.md
+++ b/docs/layouts/one-operation.md
@@ -21,30 +21,20 @@ To create operation pages, create a directory named `operations` in the `docs` d
 Example of `[operationId].paths.js`:
 
 ```ts
-import { useOpenapi, httpVerbs } from 'vitepress-openapi'
+import { usePaths } from 'vitepress-openapi'
 import spec from '../public/openapi.json' assert { type: 'json' }
 
 export default {
     paths() {
-        const openapi = useOpenapi({ spec })
-
-        if (!openapi?.json?.paths) {
-            return []
-        }
-
-        return Object.keys(openapi.json.paths)
-            .flatMap((path) => {
-                return httpVerbs
-                    .filter((verb) => openapi.json.paths[path][verb])
-                    .map((verb) => {
-                        const { operationId, summary } = openapi.json.paths[path][verb]
-                        return {
-                            params: {
-                                operationId,
-                                pageTitle: `${summary} - vitepress-openapi`,
-                            },
-                        }
-                    })
+        return usePaths({ spec })
+            .getPathsByVerbs()
+            .map(({ operationId, summary }) => {
+                return {
+                    params: {
+                        operationId,
+                        pageTitle: `${summary} - vitepress-openapi`,
+                    },
+                }
             })
     },
 }
@@ -91,16 +81,16 @@ const { isDark } = useData()
 
 const openapi = useOpenapi()
 
-const themeConfig = useTheme()
-
 const operationId = route.data.params.operationId
 
 const operation = openapi.getOperation(operationId)
 
 // Set the response code selector to select if there are more than 3 responses
-themeConfig.setResponseCodeSelector(
-    Object.keys(operation.responses).length > 3 ? 'select' : 'tabs'
-)
+useTheme({
+    response: {
+        codeSelector: Object.keys(operation.responses).length > 3 ? 'select' : 'tabs',
+    },
+})
 </script>
 
 <OAOperation :operationId="operationId" :isDark="isDark" />
diff --git a/eslint.config.js b/eslint.config.js
index 9955793..b821f63 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -8,6 +8,8 @@ export default antfu(
     lessOpinionated: true,
     rules: {
       'style/brace-style': 'off',
+      'no-use-before-define': 'off',
+      '@typescript-eslint/no-use-before-define': 'off',
     },
     stylistic: {
       'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
diff --git a/package.json b/package.json
index 47d1853..22b421a 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
   "scripts": {
     "build": "rimraf dist && vite build",
     "build-watch": "vite build --watch",
-    "dev": "vitepress dev dev",
+    "dev": "VITE_DEBUG=1 vitepress dev dev",
     "prepublishOnly": "pnpm run build",
     "lint": "eslint src",
     "lint:fix": "eslint src --fix",
@@ -42,8 +42,8 @@
     "test:run": "vitest --run"
   },
   "peerDependencies": {
-    "vue": "^3.0.0",
-    "vitepress": ">=1.0.0"
+    "vitepress": ">=1.0.0",
+    "vue": "^3.0.0"
   },
   "dependencies": {
     "@vueuse/core": "^10.11.0",
@@ -77,13 +77,13 @@
     "tailwindcss": "^3.4.10",
     "typescript": "^5.5.4",
     "vite": "^5.4.2",
+    "vitepress": "^1.4.1",
     "vitest": "^2.0.5",
     "vue": "^3.4.38",
     "vue-i18n": "^9.13.1",
     "vue-json-pretty": "^2.4.0",
     "vue-tsc": "^2.1.4",
-    "xml-formatter": "^3.6.3",
-    "vitepress": "^1.4.1"
+    "xml-formatter": "^3.6.3"
   },
   "resolutions": {
     "@typescript-eslint/utils": "^8.2.0"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 074250f..df00437 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -252,10 +252,6 @@ packages:
     resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-string-parser@7.24.8':
-    resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/helper-string-parser@7.25.7':
     resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==}
     engines: {node: '>=6.9.0'}
@@ -272,20 +268,11 @@ packages:
     resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/parser@7.24.8':
-    resolution: {integrity: sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==}
-    engines: {node: '>=6.0.0'}
-    hasBin: true
-
   '@babel/parser@7.25.8':
     resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==}
     engines: {node: '>=6.0.0'}
     hasBin: true
 
-  '@babel/types@7.24.9':
-    resolution: {integrity: sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/types@7.25.8':
     resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==}
     engines: {node: '>=6.9.0'}
@@ -3181,9 +3168,7 @@ snapshots:
   '@babel/code-frame@7.24.7':
     dependencies:
       '@babel/highlight': 7.24.7
-      picocolors: 1.0.1
-
-  '@babel/helper-string-parser@7.24.8': {}
+      picocolors: 1.1.0
 
   '@babel/helper-string-parser@7.25.7': {}
 
@@ -3193,25 +3178,15 @@ snapshots:
 
   '@babel/highlight@7.24.7':
     dependencies:
-      '@babel/helper-validator-identifier': 7.24.7
+      '@babel/helper-validator-identifier': 7.25.7
       chalk: 2.4.2
       js-tokens: 4.0.0
-      picocolors: 1.0.1
-
-  '@babel/parser@7.24.8':
-    dependencies:
-      '@babel/types': 7.24.9
+      picocolors: 1.1.0
 
   '@babel/parser@7.25.8':
     dependencies:
       '@babel/types': 7.25.8
 
-  '@babel/types@7.24.9':
-    dependencies:
-      '@babel/helper-string-parser': 7.24.8
-      '@babel/helper-validator-identifier': 7.24.7
-      to-fast-properties: 2.0.0
-
   '@babel/types@7.25.8':
     dependencies:
       '@babel/helper-string-parser': 7.25.7
@@ -3220,13 +3195,13 @@ snapshots:
 
   '@clack/core@0.3.4':
     dependencies:
-      picocolors: 1.0.1
+      picocolors: 1.1.0
       sisteransi: 1.0.5
 
   '@clack/prompts@0.7.0':
     dependencies:
       '@clack/core': 0.3.4
-      picocolors: 1.0.1
+      picocolors: 1.1.0
       sisteransi: 1.0.5
 
   '@codemirror/autocomplete@6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.33.0)(@lezer/common@1.2.1)':
@@ -3475,7 +3450,7 @@ snapshots:
   '@intlify/message-compiler@9.13.1':
     dependencies:
       '@intlify/shared': 9.13.1
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
   '@intlify/shared@9.13.1': {}
 
@@ -3830,7 +3805,7 @@ snapshots:
   '@vitest/snapshot@2.0.5':
     dependencies:
       '@vitest/pretty-format': 2.0.5
-      magic-string: 0.30.10
+      magic-string: 0.30.12
       pathe: 1.1.2
 
   '@vitest/spy@2.0.5':
@@ -3858,11 +3833,11 @@ snapshots:
 
   '@vue/compiler-core@3.4.38':
     dependencies:
-      '@babel/parser': 7.24.8
+      '@babel/parser': 7.25.8
       '@vue/shared': 3.4.38
       entities: 4.5.0
       estree-walker: 2.0.2
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
   '@vue/compiler-core@3.5.12':
     dependencies:
@@ -3870,7 +3845,7 @@ snapshots:
       '@vue/shared': 3.5.12
       entities: 4.5.0
       estree-walker: 2.0.2
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
   '@vue/compiler-dom@3.4.38':
     dependencies:
@@ -3884,15 +3859,15 @@ snapshots:
 
   '@vue/compiler-sfc@3.4.38':
     dependencies:
-      '@babel/parser': 7.24.8
+      '@babel/parser': 7.25.8
       '@vue/compiler-core': 3.4.38
       '@vue/compiler-dom': 3.4.38
       '@vue/compiler-ssr': 3.4.38
       '@vue/shared': 3.4.38
       estree-walker: 2.0.2
-      magic-string: 0.30.10
-      postcss: 8.4.42
-      source-map-js: 1.2.0
+      magic-string: 0.30.12
+      postcss: 8.4.47
+      source-map-js: 1.2.1
 
   '@vue/compiler-sfc@3.5.12':
     dependencies:
@@ -3904,7 +3879,7 @@ snapshots:
       estree-walker: 2.0.2
       magic-string: 0.30.12
       postcss: 8.4.47
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
   '@vue/compiler-ssr@3.4.38':
     dependencies:
@@ -3944,9 +3919,9 @@ snapshots:
   '@vue/language-core@2.1.4(typescript@5.5.4)':
     dependencies:
       '@volar/language-core': 2.4.1
-      '@vue/compiler-dom': 3.4.38
+      '@vue/compiler-dom': 3.5.12
       '@vue/compiler-vue2': 2.7.16
-      '@vue/shared': 3.4.38
+      '@vue/shared': 3.5.12
       computeds: 0.0.1
       minimatch: 9.0.5
       muggle-string: 0.4.1
@@ -4304,7 +4279,7 @@ snapshots:
   css-tree@2.3.1:
     dependencies:
       mdn-data: 2.0.30
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
   cssesc@3.0.0: {}
 
@@ -5491,7 +5466,7 @@ snapshots:
     dependencies:
       chokidar: 4.0.0
       immutable: 4.3.7
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
   scslre@0.3.0:
     dependencies:
@@ -5642,7 +5617,7 @@ snapshots:
       estree-walker: 3.0.3
       is-reference: 3.0.2
       locate-character: 3.0.0
-      magic-string: 0.30.10
+      magic-string: 0.30.12
       periscopic: 3.1.0
 
   synckit@0.6.2:
@@ -5780,7 +5755,7 @@ snapshots:
     dependencies:
       browserslist: 4.23.3
       escalade: 3.1.2
-      picocolors: 1.0.1
+      picocolors: 1.1.0
 
   uri-js@4.4.1:
     dependencies:
diff --git a/src/components/Common/OAInfo.vue b/src/components/Common/OAInfo.vue
index a7f92b5..c319748 100644
--- a/src/components/Common/OAInfo.vue
+++ b/src/components/Common/OAInfo.vue
@@ -3,15 +3,19 @@ import { Badge } from 'vitepress-openapi/components/ui/badge'
 import { inject } from 'vue'
 import { getOpenApiInstance } from 'vitepress-openapi'
 
-const { spec } = defineProps({
+const props = defineProps({
   spec: {
     type: Object,
     required: false,
   },
+  openapi: {
+    type: Object,
+    required: false,
+  },
 })
 
-const openapi = getOpenApiInstance({
-  custom: { spec },
+const openapi = props.openapi ?? getOpenApiInstance({
+  custom: { spec: props.spec },
   injected: inject('openapi', undefined),
 })
 
diff --git a/src/components/Common/OAOperation.vue b/src/components/Common/OAOperation.vue
index 10e0c7d..57597d1 100644
--- a/src/components/Common/OAOperation.vue
+++ b/src/components/Common/OAOperation.vue
@@ -1,6 +1,7 @@
 <script setup>
-import { computed, useSlots } from 'vue'
+import { computed, inject, useSlots } from 'vue'
 import OAHeaderBadges from 'vitepress-openapi/components/Common/OAHeaderBadges.vue'
+import { getOpenApiInstance } from 'vitepress-openapi'
 
 const props = defineProps({
   operationId: {
@@ -26,6 +27,11 @@ const props = defineProps({
   },
 })
 
+const openapi = props.openapi ?? getOpenApiInstance({
+  custom: { spec: props.spec },
+  injected: inject('openapi', undefined),
+})
+
 const slots = useSlots()
 
 const headingPrefix = computed(() => {
@@ -45,7 +51,7 @@ function hasSlot(name) {
   <OAPath
     v-if="props.operationId"
     :id="props.operationId"
-    :spec="props.spec"
+    :openapi="openapi"
   >
     <template
       v-if="hasSlot('header')"
diff --git a/src/components/Common/OAServers.vue b/src/components/Common/OAServers.vue
index f46ddcc..4f20dfd 100644
--- a/src/components/Common/OAServers.vue
+++ b/src/components/Common/OAServers.vue
@@ -1,16 +1,20 @@
 <script setup>
-import { inject } from 'vue'
 import { getOpenApiInstance } from 'vitepress-openapi'
+import { inject } from 'vue'
 
-const { spec } = defineProps({
+const props = defineProps({
   spec: {
     type: Object,
     required: false,
   },
+  openapi: {
+    type: Object,
+    required: false,
+  },
 })
 
-const openapi = getOpenApiInstance({
-  custom: { spec },
+const openapi = props.openapi ?? getOpenApiInstance({
+  custom: { spec: props.spec },
   injected: inject('openapi', undefined),
 })
 
diff --git a/src/components/Common/OASpec.vue b/src/components/Common/OASpec.vue
index 6f66636..e564178 100644
--- a/src/components/Common/OASpec.vue
+++ b/src/components/Common/OASpec.vue
@@ -1,7 +1,8 @@
 <script setup>
-import { OpenApi, useOpenapi, useTheme } from 'vitepress-openapi'
+import { getOpenApiInstance, useTheme } from 'vitepress-openapi'
 import OAInfo from 'vitepress-openapi/components/Common/OAInfo.vue'
 import OAServers from 'vitepress-openapi/components/Common/OAServers.vue'
+import { inject } from 'vue'
 
 const props = defineProps({
   spec: {
@@ -33,11 +34,10 @@ const props = defineProps({
 
 const themeConfig = useTheme()
 
-const spec = props.spec || useOpenapi().json
-
-const openapi = OpenApi({ spec })
-
-const parsedSpec = openapi.getParsedSpec()
+const openapi = getOpenApiInstance({
+  custom: { spec: props.spec },
+  injected: inject('openapi', undefined),
+})
 
 const servers = openapi.getServers()
 
@@ -57,24 +57,22 @@ const paths = openapi.getPaths()
 <template>
   <div class="flex flex-col space-y-10">
     <div v-if="showInfo || showServers">
-      <OAInfo v-if="showInfo" :spec="spec" />
+      <OAInfo v-if="showInfo" :openapi="openapi" />
 
-      <OAServers v-if="showServers" :spec="spec" :servers="servers" />
+      <OAServers v-if="showServers" :openapi="openapi" />
     </div>
 
     <hr v-if="showInfo || showServers">
 
     <OAPathsByTags
       v-if="groupByTags && operationsTags.length"
-      :spec="spec"
-      :parsed-spec="parsedSpec"
+      :openapi="openapi"
       :tags="operationsTags"
       :paths="paths"
     />
     <OAPaths
       v-else
-      :spec="spec"
-      :parsed-spec="parsedSpec"
+      :openapi="openapi"
       :paths="paths"
     />
 
diff --git a/src/components/Path/OAPath.vue b/src/components/Path/OAPath.vue
index 4fc5118..5d230d5 100644
--- a/src/components/Path/OAPath.vue
+++ b/src/components/Path/OAPath.vue
@@ -1,5 +1,5 @@
 <script setup>
-import { defineProps, inject } from 'vue'
+import { defineProps } from 'vue'
 import { useTheme } from 'vitepress-openapi/composables/useTheme'
 import { getOpenApiInstance } from 'vitepress-openapi'
 
@@ -8,25 +8,15 @@ const props = defineProps({
     type: String,
     required: true,
   },
-  spec: {
-    type: Object,
-    required: false,
-  },
-  parsedSpec: {
+  openapi: {
     type: Object,
     required: false,
   },
 })
 
-const theme = useTheme()
+const themeConfig = useTheme()
 
-const openapi = getOpenApiInstance({
-  custom: {
-    spec: props.spec,
-    parsedSpec: props.parsedSpec,
-  },
-  injected: inject('openapi', undefined),
-})
+const openapi = props.openapi ?? getOpenApiInstance()
 
 const operation = openapi.getOperation(props.id)
 
@@ -72,7 +62,7 @@ const operationResponses = operationParsed?.responses
               :method="operationMethod"
               :base-url="baseUrl"
               :path="operationPath"
-              :hide-base-url="!theme.getShowBaseURL()"
+              :hide-base-url="!themeConfig.getShowBaseURL()"
               :deprecated="operation.deprecated"
             />
 
@@ -126,7 +116,7 @@ const operationResponses = operationParsed?.responses
               :method="operationMethod"
               :base-url="baseUrl"
               :path="operationPath"
-              :hide-base-url="!theme.getShowBaseURL()"
+              :hide-base-url="!themeConfig.getShowBaseURL()"
               :deprecated="operation.deprecated"
             />
 
diff --git a/src/components/Path/OAPaths.vue b/src/components/Path/OAPaths.vue
index 6ca7fb3..ac5aa85 100644
--- a/src/components/Path/OAPaths.vue
+++ b/src/components/Path/OAPaths.vue
@@ -1,15 +1,11 @@
 <script setup>
 import { defineProps } from 'vue'
 
-const { spec, paths, isDark } = defineProps({
-  spec: {
+const { openapi, paths, isDark } = defineProps({
+  openapi: {
     type: Object,
     required: true,
   },
-  parsedSpec: {
-    type: Object,
-    required: false,
-  },
   paths: {
     type: Object,
     required: true,
@@ -33,8 +29,7 @@ const { spec, paths, isDark } = defineProps({
     >
       <OAOperation
         :operation-id="path[method].operationId"
-        :spec="spec"
-        :parsed-spec="parsedSpec"
+        :openapi="openapi"
         :is-dark="isDark"
         prefix-headings
         hide-default-footer
diff --git a/src/components/Path/OAPathsByTags.vue b/src/components/Path/OAPathsByTags.vue
index 2a1b4cc..e1be8e7 100644
--- a/src/components/Path/OAPathsByTags.vue
+++ b/src/components/Path/OAPathsByTags.vue
@@ -3,18 +3,14 @@ import { defineProps, ref } from 'vue'
 import { Collapsible, CollapsibleTrigger } from 'vitepress-openapi/components/ui/collapsible'
 import { Button } from 'vitepress-openapi/components/ui/button'
 import { useI18n } from 'vue-i18n'
-import { OpenApi, useOpenapi, useTheme } from 'vitepress-openapi'
+import { getOpenApiInstance, useTheme } from 'vitepress-openapi'
 import OAPathsSummary from 'vitepress-openapi/components/Path/OAPathsSummary.vue'
 
 const props = defineProps({
-  spec: {
+  openapi: {
     type: Object,
     required: true,
   },
-  parsedSpec: {
-    type: Object,
-    required: false,
-  },
   tags: {
     type: Array,
     required: false,
@@ -33,9 +29,7 @@ const { t } = useI18n()
 
 const themeConfig = useTheme()
 
-const spec = props.spec || useOpenapi().json
-
-const openapi = OpenApi({ spec, parsedSpec: props.parsedSpec })
+const openapi = props.openapi ?? getOpenApiInstance()
 
 const tagsInfo = openapi.getTags()
 
@@ -120,7 +114,12 @@ function onPathClick(tagPaths, hash) {
           v-if="themeConfig.getSpecConfig().showPathsSummary.value"
           class="flex-1 my-[16px]"
         >
-          <OAPathsSummary :spec="spec" :paths="tagPaths.paths" :is-dark="isDark" @path-click="onPathClick(tagPaths, $event)" />
+          <OAPathsSummary
+            :openapi="openapi"
+            :paths="tagPaths.paths"
+            :is-dark="isDark"
+            @path-click="onPathClick(tagPaths, $event)"
+          />
         </div>
       </div>
 
@@ -136,8 +135,7 @@ function onPathClick(tagPaths, hash) {
 
       <div class="flex flex-col space-y-10" :class="[{ hidden: !tagPaths.isOpen }]">
         <OAPaths
-          :spec="spec"
-          :parsed-spec="parsedSpec"
+          :openapi="openapi"
           :paths="tagPaths.paths"
         />
       </div>
diff --git a/src/components/Path/OAPathsSummary.vue b/src/components/Path/OAPathsSummary.vue
index 28e681b..74c9dd3 100644
--- a/src/components/Path/OAPathsSummary.vue
+++ b/src/components/Path/OAPathsSummary.vue
@@ -2,10 +2,6 @@
 import { defineProps } from 'vue'
 
 const { paths } = defineProps({
-  spec: {
-    type: Object,
-    required: true,
-  },
   paths: {
     type: Object,
     required: true,
diff --git a/src/components/Playground/OAPlaygroundParameters.vue b/src/components/Playground/OAPlaygroundParameters.vue
index 984e324..8f16ff0 100644
--- a/src/components/Playground/OAPlaygroundParameters.vue
+++ b/src/components/Playground/OAPlaygroundParameters.vue
@@ -79,7 +79,7 @@ function initializeVariables(parameters) {
 }
 
 const selectedSchemeName = computed(() => {
-  return themeConfig.securityConfig.selectedScheme.value
+  return themeConfig.getSecuritySelectedScheme()
 })
 
 const selectedScheme = computed(() => {
diff --git a/src/components/Schema/OASchemaProperty.vue b/src/components/Schema/OASchemaProperty.vue
index 9023088..bcbb726 100644
--- a/src/components/Schema/OASchemaProperty.vue
+++ b/src/components/Schema/OASchemaProperty.vue
@@ -1,10 +1,6 @@
 <script setup>
 import { ref } from 'vue'
-import {
-  Collapsible,
-  CollapsibleContent,
-  CollapsibleTrigger,
-} from 'vitepress-openapi/components/ui/collapsible'
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from 'vitepress-openapi/components/ui/collapsible'
 import OASchemaBody from './OASchemaBody.vue'
 
 const props = defineProps({
diff --git a/src/components/Security/OASecurity.vue b/src/components/Security/OASecurity.vue
index 8cc8a8f..d6f2c22 100644
--- a/src/components/Security/OASecurity.vue
+++ b/src/components/Security/OASecurity.vue
@@ -28,8 +28,8 @@ const themeConfig = useTheme()
 const firstSecurityScheme = Object.keys(securitySchemes).find(Boolean)
 
 const selectedSchemeName = computed({
-  get: () => themeConfig.securityConfig.selectedScheme.value,
-  set: value => themeConfig.securityConfig.selectedScheme.value = value,
+  get: () => themeConfig.getSecuritySelectedScheme(),
+  set: value => themeConfig.setSecuritySelectedScheme(value),
 })
 
 const selectedScheme = computed(() => {
diff --git a/src/components/ui/collapsible/Collapsible.vue b/src/components/ui/collapsible/Collapsible.vue
index c35068e..6cb5a2f 100644
--- a/src/components/ui/collapsible/Collapsible.vue
+++ b/src/components/ui/collapsible/Collapsible.vue
@@ -1,6 +1,6 @@
 <script setup lang="ts">
-import { CollapsibleRoot, useForwardPropsEmits } from 'radix-vue'
 import type { CollapsibleRootEmits, CollapsibleRootProps } from 'radix-vue'
+import { CollapsibleRoot, useForwardPropsEmits } from 'radix-vue'
 
 const props = defineProps<CollapsibleRootProps>()
 const emits = defineEmits<CollapsibleRootEmits>()
diff --git a/src/components/ui/select/SelectItem.vue b/src/components/ui/select/SelectItem.vue
index 9c9ff20..9dd6780 100644
--- a/src/components/ui/select/SelectItem.vue
+++ b/src/components/ui/select/SelectItem.vue
@@ -1,12 +1,6 @@
 <script setup lang="ts">
 import { type HTMLAttributes, computed } from 'vue'
-import {
-  SelectItem,
-  SelectItemIndicator,
-  type SelectItemProps,
-  SelectItemText,
-  useForwardProps,
-} from 'radix-vue'
+import { SelectItem, SelectItemIndicator, type SelectItemProps, SelectItemText, useForwardProps } from 'radix-vue'
 import { Check } from 'lucide-vue-next'
 import { cn } from 'vitepress-openapi/lib/utils'
 
diff --git a/src/components/ui/tabs/Tabs.vue b/src/components/ui/tabs/Tabs.vue
index 2fa0971..8feda3f 100644
--- a/src/components/ui/tabs/Tabs.vue
+++ b/src/components/ui/tabs/Tabs.vue
@@ -1,6 +1,6 @@
 <script setup lang="ts">
-import { TabsRoot, useForwardPropsEmits } from 'radix-vue'
 import type { TabsRootEmits, TabsRootProps } from 'radix-vue'
+import { TabsRoot, useForwardPropsEmits } from 'radix-vue'
 
 const props = defineProps<TabsRootProps>()
 const emits = defineEmits<TabsRootEmits>()
diff --git a/src/composables/useOpenapi.ts b/src/composables/useOpenapi.ts
index 2b8c7b3..44e80aa 100644
--- a/src/composables/useOpenapi.ts
+++ b/src/composables/useOpenapi.ts
@@ -1,24 +1,68 @@
-import { OpenApi } from '../lib/OpenApi'
+import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
+import { createOpenApiInstance } from '../lib/createOpenApiInstance'
+import type { UseThemeConfig } from './useTheme'
+import { useTheme } from './useTheme'
 
-let openapi = null
+export type OpenAPI = OpenAPIV3.Document | OpenAPIV3_1.Document
 
-export function useOpenapi({ spec } = { spec: null }) {
+export interface OpenAPIData {
+  id: string
+  spec: OpenAPI
+  openapi: any
+  config: any
+}
+
+export type Schemas = Map<string, OpenAPIData>
+
+export const DEFAULT_SCHEMA = 'main'
+
+const schemas: Schemas = new Map()
+
+let mainSchema: OpenAPIData | null = null
+
+export function useOpenapi({
+  spec,
+  config,
+}: {
+  spec: OpenAPI | null
+  config: UseThemeConfig | null
+} = {
+  spec: null,
+  config: null,
+}) {
   if (spec !== null) {
-    setupOpenApi({ spec })
+    setupOpenApi({ spec, config })
   }
 
+  /**
+   * @deprecated Use `useOpenapi({ spec })` instead.
+   */
   function setSpec(value: any) {
+    console.warn('Deprecated usage of `setSpec`. Use `useOpenapi({ spec })` instead.')
     setupOpenApi({ spec: value })
   }
 
-  function setupOpenApi({ spec }) {
-    openapi = OpenApi({ spec })
+  function setupOpenApi({ spec, config }) {
+    addSchema({ id: DEFAULT_SCHEMA, spec, config })
+    mainSchema = schemas.get(DEFAULT_SCHEMA)
+  }
+
+  function addSchema({ id, spec, config }) {
+    const openapi = createOpenApiInstance({ spec })
+    if (config) {
+      useTheme(config)
+    }
+    schemas.set(id, {
+      ...openapi,
+      id,
+      config,
+    })
   }
 
   return {
-    ...(openapi || {}),
-    spec: openapi?.parsedSpec,
-    json: openapi?.spec,
+    ...mainSchema,
+    json: mainSchema?.spec,
     setSpec,
+    schemas,
   }
 }
diff --git a/src/composables/usePaths.ts b/src/composables/usePaths.ts
new file mode 100644
index 0000000..d8826c6
--- /dev/null
+++ b/src/composables/usePaths.ts
@@ -0,0 +1,11 @@
+import { OpenApi } from 'vitepress-openapi'
+
+export function usePaths({
+  spec,
+}) {
+  const openapi = OpenApi({ spec })
+
+  return {
+    getPathsByVerbs: openapi.getPathsByVerbs,
+  }
+}
diff --git a/src/composables/useSidebar.ts b/src/composables/useSidebar.ts
index 93e4f7f..af9082f 100644
--- a/src/composables/useSidebar.ts
+++ b/src/composables/useSidebar.ts
@@ -63,17 +63,19 @@ export function useSidebar({
     linkPrefix = linkPrefix || options.linkPrefix
     addedOperations = addedOperations || new Set()
 
-    if (!openapi.getPaths()) {
+    const paths = openapi.getPaths()
+
+    if (!paths) {
       return []
     }
 
     const includeTags = Array.isArray(tag) ? tag : [tag]
 
-    const sidebarGroupElements = Object.keys(openapi.getPaths())
+    const sidebarGroupElements = Object.keys(paths)
       .flatMap((path) => {
         return httpVerbs
           .map((method) => {
-            const operation = openapi.getPaths()[path][method]
+            const operation = paths[path][method]
             if (operation && !addedOperations.has(operation.operationId) && (includeTags.length === 0 || includeTags.every(tag => operation.tags?.includes(tag)))) {
               addedOperations.add(operation.operationId)
               return generateSidebarItem(method, path, linkPrefix)
diff --git a/src/composables/useTheme.ts b/src/composables/useTheme.ts
index 54e8aa2..9606690 100644
--- a/src/composables/useTheme.ts
+++ b/src/composables/useTheme.ts
@@ -1,98 +1,223 @@
 import { ref } from 'vue'
 import vitesseLight from 'shiki/themes/vitesse-light.mjs'
 import vitesseDark from 'shiki/themes/vitesse-dark.mjs'
+import { deepUnref } from '../lib/deepUnref'
 import { locales } from '../locales'
 
-interface HeadingLevels {
-  h1: number
-  h2: number
-  h3: number
-  h4: number
-  h5: number
-  h6: number
-}
-
-type PlaygroundJsonEditorMode = 'text' | 'tree' | 'table'
-
-const themeConfig = {
+export interface ThemeConfig {
   highlighterTheme: {
-    light: vitesseLight,
-    dark: vitesseDark,
-  },
+    light: any
+    dark: any
+  }
 }
 
-const schemaConfig = {
-  defaultView: ref<'schema' | 'contentType'>('contentType'),
-  showBaseURL: ref<boolean>(false),
+export interface RequestConfig {
+  defaultView: Ref<'schema' | 'contentType'>
+  showBaseURL: Ref<boolean>
 }
 
-const jsonViewerConfig = {
-  deep: ref<number>(Infinity),
+export interface JsonViewerConfig {
+  deep: Ref<number>
 }
 
-const schemaViewerConfig = {
-  deep: ref<number>(Infinity),
+export interface SchemaViewerConfig {
+  deep: Ref<number>
 }
 
-const headingLevels: HeadingLevels = {
-  h1: 1,
-  h2: 2,
-  h3: 3,
-  h4: 4,
-  h5: 5,
-  h6: 6,
+export interface HeadingLevels {
+  h1: number
+  h2: number
+  h3: number
+  h4: number
+  h5: number
+  h6: number
 }
 
-const responseConfig = {
-  responseCodeSelector: ref<'tabs', 'select'>('tabs'),
-  maxTabs: ref<number>(5),
+export interface ResponseConfig {
+  responseCodeSelector: Ref<'tabs' | 'select'>
+  maxTabs: Ref<number>
 }
 
-const playgroundConfig = {
-  /**
-   * See:
-   * - https://github.com/cloydlau/json-editor-vue?tab=readme-ov-file#props
-   * - https://github.com/josdejong/svelte-jsoneditor/#properties
-   */
+type PlaygroundJsonEditorMode = 'text' | 'tree' | 'table'
+
+export interface PlaygroundConfig {
   jsonEditor: {
-    mode: ref<PlaygroundJsonEditorMode>('tree'),
-    mainMenuBar: ref<boolean>(false),
-    navigationBar: ref<boolean>(false),
-  },
+    mode: Ref<PlaygroundJsonEditorMode>
+    mainMenuBar: Ref<boolean>
+    navigationBar: Ref<boolean>
+  }
 }
 
-const securityConfig = {
-  defaultScheme: ref<string | null>(null),
-  selectedScheme: ref<string | null>(null),
+export interface SecurityConfig {
+  defaultScheme: Ref<string | null>
+  selectedScheme: Ref<string | null>
 }
 
 type OperationBadges = 'deprecated' | 'operationId'
 
-const operationConfig = {
-  badges: ref<OperationBadges[]>(['deprecated']),
+export interface OperationConfig {
+  badges: Ref<OperationBadges[]>
 }
 
-interface I18nConfig {
+export interface I18nConfig {
   locale: Ref<'es' | 'en' | string>
   fallbackLocale: Ref<'es' | 'en' | string>
   messages: Record<'es' | 'en', Record<string, Record<string, string>>>
 }
 
-const i18nConfig: I18nConfig = {
-  locale: ref<'es' | 'en'>('en'),
-  fallbackLocale: ref<'es' | 'en'>('en'),
-  messages: locales,
+export interface SpecConfig {
+  groupByTags: Ref<boolean>
+  collapsePaths: Ref<boolean>
+  showPathsSummary: Ref<boolean>
+}
+
+export interface UseThemeConfig {
+  theme?: Partial<ThemeConfig>
+  request?: Partial<RequestConfig>
+  jsonViewer?: Partial<JsonViewerConfig>
+  schemaViewer?: Partial<SchemaViewerConfig>
+  headingLevels?: Partial<HeadingLevels>
+  response?: Partial<ResponseConfig>
+  playground?: Partial<PlaygroundConfig>
+  security?: Partial<SecurityConfig>
+  operation?: Partial<OperationConfig>
+  i18n?: Partial<I18nConfig>
+  spec?: Partial<SpecConfig>
 }
 
-const specConfig = {
-  groupByTags: ref(true),
-  collapsePaths: ref(false),
-  showPathsSummary: ref(true),
+const themeConfig: UseThemeConfig = {
+  theme: {
+    highlighterTheme: {
+      light: vitesseLight,
+      dark: vitesseDark,
+    },
+  },
+  request: {
+    defaultView: ref<'schema' | 'contentType'>('contentType'),
+    showBaseURL: ref<boolean>(false),
+  },
+  jsonViewer: {
+    deep: ref<number>(Number.POSITIVE_INFINITY),
+  },
+  schemaViewer: {
+    deep: ref<number>(Number.POSITIVE_INFINITY),
+  },
+  headingLevels: {
+    h1: 1,
+    h2: 2,
+    h3: 3,
+    h4: 4,
+    h5: 5,
+    h6: 6,
+  },
+  response: {
+    responseCodeSelector: ref<'tabs' | 'select'>('tabs'),
+    maxTabs: ref<number>(5),
+  },
+  playground: {
+    jsonEditor: {
+      mode: ref<PlaygroundJsonEditorMode>('tree'),
+      mainMenuBar: ref<boolean>(false),
+      navigationBar: ref<boolean>(false),
+    },
+  },
+  security: {
+    defaultScheme: ref<string | null>(null),
+    selectedScheme: ref<string | null>(null),
+  },
+  operation: {
+    badges: ref<OperationBadges[]>(['deprecated']),
+  },
+  i18n: {
+    locale: ref<'es' | 'en'>('en'),
+    fallbackLocale: ref<'es' | 'en'>('en'),
+    messages: locales,
+  },
+  spec: {
+    groupByTags: ref(true),
+    collapsePaths: ref(false),
+    showPathsSummary: ref(true),
+  },
 }
 
-export function useTheme() {
+const defaultThemeConfig = { ...deepUnref(themeConfig) }
+
+export function useTheme(config: Partial<UseThemeConfig> = {}) {
+  if (Object.keys(config).length) {
+    if (config?.theme?.highlighterTheme) {
+      themeConfig.theme.highlighterTheme = {
+        ...themeConfig.theme.highlighterTheme,
+        ...config.theme.highlighterTheme,
+      }
+    }
+
+    if (config?.request?.defaultView !== undefined) {
+      setSchemaDefaultView(config.request.defaultView)
+    }
+
+    if (config?.request?.showBaseURL !== undefined) {
+      setShowBaseURL(config.request.showBaseURL)
+    }
+
+    if (config?.jsonViewer?.deep !== undefined) {
+      setJsonViewerDeep(config.jsonViewer.deep)
+    }
+
+    if (config?.schemaViewer?.deep !== undefined) {
+      setSchemaViewerDeep(config.schemaViewer.deep)
+    }
+
+    if (config?.headingLevels !== undefined) {
+      setHeadingLevels(config.headingLevels)
+    }
+
+    if (config?.response?.responseCodeSelector !== undefined) {
+      setResponseCodeSelector(config.response.responseCodeSelector)
+    }
+
+    if (config?.response?.maxTabs !== undefined) {
+      setResponseCodeMaxTabs(config.response.maxTabs)
+    }
+
+    if (config?.playground?.jsonEditor?.mode !== undefined) {
+      setPlaygroundJsonEditorMode(config.playground.jsonEditor.mode)
+    }
+
+    if (config?.playground?.jsonEditor?.mainMenuBar !== undefined) {
+      setPlaygroundJsonEditorMainMenuBar(config.playground.jsonEditor.mainMenuBar)
+    }
+
+    if (config?.playground?.jsonEditor?.navigationBar !== undefined) {
+      setPlaygroundJsonEditorNavigationBar(config.playground.jsonEditor.navigationBar)
+    }
+
+    if (config?.security?.defaultScheme !== undefined) {
+      setSecurityDefaultScheme(config.security.defaultScheme)
+    }
+
+    if (config?.security?.selectedScheme !== undefined) {
+      setSecuritySelectedScheme(config.security.selectedScheme)
+    }
+
+    if (config?.operation?.badges !== undefined) {
+      setOperationBadges(config.operation.badges)
+    }
+
+    if (config?.i18n !== undefined) {
+      setI18nConfig(config.i18n)
+    }
+
+    if (config?.spec !== undefined) {
+      setSpecConfig(config.spec)
+    }
+  }
+
+  function reset() {
+    useTheme({ ...defaultThemeConfig })
+  }
+
   function getLocale(): 'es' | 'en' | string {
-    return getI18nConfig().locale.value
+    return themeConfig.i18n.locale.value
   }
 
   /**
@@ -101,51 +226,51 @@ export function useTheme() {
    */
   function setLocale(value: 'es' | 'en' | string) {
     console.warn('`setLocale` is deprecated. Use `setI18nConfig({ locale: value })` instead.')
-    setI18nConfig({ locale: value })
+    themeConfig.i18n.locale.value = value
   }
 
   function getHighlighterTheme() {
-    return themeConfig.highlighterTheme
+    return themeConfig.theme.highlighterTheme
   }
 
   function getSchemaDefaultView(): 'schema' | 'contentType' {
-    return schemaConfig.defaultView.value
+    return themeConfig.request.defaultView.value
   }
 
   function setSchemaDefaultView(value: 'schema' | 'contentType') {
-    schemaConfig.defaultView.value = value
+    themeConfig.request.defaultView.value = value
   }
 
   function getShowBaseURL(): boolean {
-    return schemaConfig.showBaseURL.value
+    return themeConfig.request.showBaseURL.value
   }
 
   function setShowBaseURL(value: boolean) {
-    schemaConfig.showBaseURL.value = value
+    themeConfig.request.showBaseURL.value = value
   }
 
   function getJsonViewerDeep(): number {
-    return jsonViewerConfig.deep.value
+    return themeConfig.jsonViewer.deep.value
   }
 
   function setJsonViewerDeep(value: number) {
-    jsonViewerConfig.deep.value = value
+    themeConfig.jsonViewer.deep.value = value
   }
 
   function getSchemaViewerDeep(): number {
-    return schemaViewerConfig.deep.value
+    return themeConfig.schemaViewer.deep.value
   }
 
   function setSchemaViewerDeep(value: number) {
-    schemaViewerConfig.deep.value = value
+    themeConfig.schemaViewer.deep.value = value
   }
 
   function getHeadingLevels() {
-    return headingLevels
+    return themeConfig.headingLevels
   }
 
   function getHeadingLevel(level: keyof HeadingLevels): `h${1 | 2 | 3 | 4 | 5 | 6}` {
-    const headingLevel = headingLevels[level]
+    const headingLevel = themeConfig.headingLevels[level]
     if (headingLevel < 1 || headingLevel > 6) {
       throw new Error(`Heading level for ${level} must be between 1 and 6.`)
     }
@@ -159,96 +284,112 @@ export function useTheme() {
         throw new Error(`Heading level for ${key} must be between 1 and 6.`)
       }
     }
-    Object.assign(headingLevels, levels)
+    Object.assign(themeConfig.headingLevels, levels)
   }
 
   function getResponseCodeSelector(): 'tabs' | 'select' {
-    return responseConfig.responseCodeSelector.value
+    return themeConfig.response.responseCodeSelector.value
   }
 
   function setResponseCodeSelector(value: 'tabs' | 'select') {
-    responseConfig.responseCodeSelector.value = value
+    themeConfig.response.responseCodeSelector.value = value
   }
 
   function getResponseCodeMaxTabs(): number {
-    return responseConfig.maxTabs.value
+    return themeConfig.response.maxTabs.value
   }
 
   function setResponseCodeMaxTabs(value: number) {
-    responseConfig.maxTabs.value = value
+    themeConfig.response.maxTabs.value = value
   }
 
   function getPlaygroundJsonEditorMode(): PlaygroundJsonEditorMode {
-    return playgroundConfig.jsonEditor.mode.value
+    return themeConfig.playground.jsonEditor.mode.value
   }
 
   function setPlaygroundJsonEditorMode(value: PlaygroundJsonEditorMode) {
-    playgroundConfig.jsonEditor.mode.value = value
+    themeConfig.playground.jsonEditor.mode.value = value
   }
 
   function getPlaygroundJsonEditorMainMenuBar(): boolean {
-    return playgroundConfig.jsonEditor.mainMenuBar.value
+    return themeConfig.playground.jsonEditor.mainMenuBar.value
   }
 
   function setPlaygroundJsonEditorMainMenuBar(value: boolean) {
-    playgroundConfig.jsonEditor.mainMenuBar.value = value
+    themeConfig.playground.jsonEditor.mainMenuBar.value = value
   }
 
   function getPlaygroundJsonEditorNavigationBar(): boolean {
-    return playgroundConfig.jsonEditor.navigationBar.value
+    return themeConfig.playground.jsonEditor.navigationBar.value
   }
 
   function setPlaygroundJsonEditorNavigationBar(value: boolean) {
-    playgroundConfig.jsonEditor.navigationBar.value = value
+    themeConfig.playground.jsonEditor.navigationBar.value = value
+  }
+
+  function getSecurityDefaultScheme(): string | null {
+    return themeConfig.security.defaultScheme.value
+  }
+
+  function setSecurityDefaultScheme(value: string | null) {
+    themeConfig.security.defaultScheme.value = value
+  }
+
+  function getSecuritySelectedScheme(): string | null {
+    return themeConfig.security.selectedScheme.value
+  }
+
+  function setSecuritySelectedScheme(value: string | null) {
+    themeConfig.security.selectedScheme.value = value
   }
 
   function getOperationBadges(): OperationBadges[] {
-    return [...operationConfig.badges.value]
+    return [...themeConfig.operation.badges.value]
   }
 
   function setOperationBadges(value: OperationBadges[]) {
-    operationConfig.badges.value = value
+    themeConfig.operation.badges.value = value
   }
 
   function getI18nConfig(): I18nConfig {
-    return i18nConfig
+    return themeConfig.i18n
   }
 
   function setI18nConfig(config: Partial<I18nConfig>) {
     if (config.locale) {
-      i18nConfig.locale.value = config.locale
+      themeConfig.i18n.locale.value = config.locale
     }
 
     if (config.fallbackLocale) {
-      i18nConfig.fallbackLocale.value = config.fallbackLocale
+      themeConfig.i18n.fallbackLocale.value = config.fallbackLocale
     }
 
     if (config.messages) {
-      i18nConfig.messages = config.messages
+      themeConfig.i18n.messages = config.messages
     }
   }
 
   function getSpecConfig() {
-    return specConfig
+    return themeConfig.spec
   }
 
   function setSpecConfig(config: Partial<typeof specConfig>) {
     if (config.groupByTags !== undefined) {
-      specConfig.groupByTags.value = config.groupByTags
+      themeConfig.spec.groupByTags.value = config.groupByTags
     }
 
     if (config.collapsePaths !== undefined) {
-      specConfig.collapsePaths.value = config.collapsePaths
+      themeConfig.spec.collapsePaths.value = config.collapsePaths
     }
 
     if (config.showPathsSummary !== undefined) {
-      specConfig.showPathsSummary.value = config.showPathsSummary
+      themeConfig.spec.showPathsSummary.value = config.showPathsSummary
     }
   }
 
   return {
-    schemaConfig,
-    securityConfig,
+    schemaConfig: themeConfig.request,
+    reset,
     getLocale,
     setLocale,
     getHighlighterTheme,
@@ -273,6 +414,10 @@ export function useTheme() {
     setPlaygroundJsonEditorMainMenuBar,
     getPlaygroundJsonEditorNavigationBar,
     setPlaygroundJsonEditorNavigationBar,
+    getSecurityDefaultScheme,
+    setSecurityDefaultScheme,
+    getSecuritySelectedScheme,
+    setSecuritySelectedScheme,
     getOperationBadges,
     setOperationBadges,
     getI18nConfig,
diff --git a/src/index.ts b/src/index.ts
index 64c5b8f..e98fbb0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -15,8 +15,10 @@ export { useOpenapi } from './composables/useOpenapi'
 export { useTheme } from './composables/useTheme'
 export { useShiki } from './composables/useShiki'
 export { usePlayground } from './composables/usePlayground'
+export { usePaths } from './composables/usePaths'
 export { OpenApi } from './lib/OpenApi'
 export { getOpenApiInstance } from './lib/getOpenApiInstance'
+export { createOpenApiInstance } from './lib/createOpenApiInstance'
 export { locales } from './locales'
 
 interface VPTheme {
@@ -26,7 +28,11 @@ interface VPTheme {
 }
 
 export const theme = {
-  enhanceApp({ app }) {
+  enhanceApp({ app, openapi }) {
+    if (openapi) {
+      app.provide('openapi', openapi)
+    }
+
     const themeConfig = useTheme()
 
     const i18n = VueI18n.createI18n({
diff --git a/src/lib/OpenApi.ts b/src/lib/OpenApi.ts
index 9f7bc3e..e56644f 100644
--- a/src/lib/OpenApi.ts
+++ b/src/lib/OpenApi.ts
@@ -1,38 +1,21 @@
 import { httpVerbs } from 'vitepress-openapi'
-import { dereferenceSync } from '@trojs/openapi-dereference'
-import { merge } from 'allof-merge'
-import { generateMissingOperationIds } from './generateMissingOperationIds'
-import { generateMissingSummary } from './generateMissingSummary'
+import { parseSpec } from './parseSpec'
 
 const DEFAULT_SERVER_URL = 'http://localhost'
 
 export function OpenApi({
   spec,
   parsedSpec,
+  transformedSpec,
 }: {
   spec: any
+  transformedSpec?: any
   parsedSpec?: any
 } = {
   spec: null,
+  transformedSpec: null,
   parsedSpec: null,
 }) {
-  transformSpec()
-
-  function transformSpec() {
-    if (!spec) {
-      return
-    }
-
-    if (!spec.openapi || !spec.openapi.startsWith('3.')) {
-      throw new Error('Only OpenAPI 3.x is supported')
-    }
-
-    if (spec?.paths) {
-      spec = generateMissingOperationIds(spec)
-      spec = generateMissingSummary(spec)
-    }
-  }
-
   function findOperation(paths: any, operationId: string) {
     for (const path of Object.values(paths)) {
       for (const verb of httpVerbs) {
@@ -46,13 +29,7 @@ export function OpenApi({
 
   function getParsedSpec() {
     if (!parsedSpec) {
-      try {
-        const mergedSpec = merge(spec)
-        parsedSpec = dereferenceSync(mergedSpec)
-      } catch (error) {
-        console.warn('Failed to parse OpenAPI spec:', error)
-        parsedSpec = { ...spec }
-      }
+      parsedSpec = parseSpec(transformedSpec ?? spec)
     }
 
     return parsedSpec
@@ -184,18 +161,21 @@ export function OpenApi({
   }
 
   function getPathsByVerbs() {
-    return Object.keys(getPaths())
+    const paths = getPaths()
+
+    return Object.keys(paths)
       .flatMap((path) => {
         return httpVerbs
-          .filter(verb => getPaths()[path][verb])
+          .filter(verb => paths[path][verb])
           .map((verb) => {
-            const { operationId, summary } = getPaths()[path][verb]
+            const { operationId, summary, tags } = paths[path][verb]
 
             return {
               path,
               verb,
               operationId,
               summary,
+              tags,
             }
           })
       })
@@ -264,6 +244,7 @@ export function OpenApi({
 
   return {
     spec,
+    transformedSpec,
     parsedSpec,
     getBaseUrl,
     getOperation,
diff --git a/src/lib/createOpenApiInstance.ts b/src/lib/createOpenApiInstance.ts
new file mode 100644
index 0000000..8c82505
--- /dev/null
+++ b/src/lib/createOpenApiInstance.ts
@@ -0,0 +1,14 @@
+import type { OpenAPIData } from '../composables/useOpenapi'
+import { parseSpec } from './parseSpec'
+import { transformSpec } from './transformSpec'
+import { OpenApi } from './OpenApi'
+
+export function createOpenApiInstance({
+  spec,
+}: {
+  spec: OpenAPIData
+}): OpenApi {
+  const transformedSpec = transformSpec(spec)
+  const parsedSpec = parseSpec(transformedSpec)
+  return OpenApi({ spec, transformedSpec, parsedSpec })
+}
diff --git a/src/lib/deepUnref.ts b/src/lib/deepUnref.ts
new file mode 100644
index 0000000..690a18a
--- /dev/null
+++ b/src/lib/deepUnref.ts
@@ -0,0 +1,87 @@
+/**
+ * Inspired by https://github.com/DanHulton/vue-deepunref and converted to TypeScript
+ */
+
+import type { Ref } from 'vue'
+import { isRef, unref } from 'vue'
+
+type DeepUnrefArray<T> = Array<DeepUnref<T>>
+
+type DeepUnrefObject<T> = {
+  [K in keyof T]: DeepUnref<T[K]>;
+}
+
+export type DeepUnref<T> = T extends Ref<infer V>
+  ? DeepUnref<V>
+  : T extends Array<any>
+    ? DeepUnrefArray<T[number]>
+    : T extends object
+      ? DeepUnrefObject<T>
+      : T
+
+const isObject = (val: unknown): val is Record<string, unknown> =>
+  val !== null && typeof val === 'object'
+
+const isArray = Array.isArray
+
+/**
+ * Deeply unref a value, recursing into objects and arrays.
+ *
+ * @param val - The value to deeply unref
+ * @returns The deeply unreffed value
+ */
+export function deepUnref<T>(val: T): DeepUnref<T> {
+  const checkedVal = isRef(val) ? unref(val) : val
+
+  if (!isObject(checkedVal)) {
+    return checkedVal as DeepUnref<T>
+  }
+
+  if (isArray(checkedVal)) {
+    return unrefArray(checkedVal) as DeepUnref<T>
+  }
+
+  return unrefObject(checkedVal) as DeepUnref<T>
+}
+
+/**
+ * Unref a value, recursing into it if it's an object.
+ *
+ * @param val - The value to unref
+ * @returns The unreffed value
+ */
+function smartUnref<T>(val: T): DeepUnref<T> {
+  // Non-ref object? Go deeper!
+  if (val !== null && !isRef(val) && typeof val === 'object') {
+    return deepUnref(val)
+  }
+
+  return unref(val as any) as DeepUnref<T>
+}
+
+/**
+ * Unref an array, recursively.
+ *
+ * @param arr - The array to unref
+ * @returns The unreffed array
+ */
+function unrefArray<T>(arr: T[]): DeepUnrefArray<T> {
+  return arr.map(item => smartUnref(item)) as DeepUnrefArray<T>
+}
+
+/**
+ * Unref an object, recursively.
+ *
+ * @param obj - The object to unref
+ * @returns The unreffed object
+ */
+function unrefObject<T extends Record<string, unknown>>(obj: T): DeepUnrefObject<T> {
+  const unreffed = {} as DeepUnrefObject<T>
+
+  // Object? un-ref it!
+  Object.keys(obj).forEach((key) => {
+    unreffed[key as keyof T] = smartUnref(obj[key])
+  })
+
+  return unreffed
+}
diff --git a/src/lib/getOpenApiInstance.ts b/src/lib/getOpenApiInstance.ts
index c4b4a01..5676869 100644
--- a/src/lib/getOpenApiInstance.ts
+++ b/src/lib/getOpenApiInstance.ts
@@ -1,16 +1,32 @@
-import { useOpenapi } from '../composables/useOpenapi'
-import { OpenApi } from './OpenApi'
+import type { Schemas } from '../composables/useOpenapi'
+import { DEFAULT_SCHEMA, useOpenapi } from '../composables/useOpenapi'
+import type { OpenApi } from './OpenApi'
+import { createOpenApiInstance } from './createOpenApiInstance'
 
 export function getOpenApiInstance({
+  id,
   custom,
   injected,
-} = {}) {
+}: {
+  id?: string
+  custom?: { spec: any, parsedSpec: any }
+  injected?: Schemas | any
+} = {}): OpenApi | null {
+  if (id === undefined) {
+    id = DEFAULT_SCHEMA
+  }
+
   if (custom?.spec) {
-    return OpenApi({ spec: custom?.spec, parsedSpec: custom?.parsedSpec })
+    return createOpenApiInstance({ spec: custom.spec })
   }
 
-  if (injected !== undefined) {
-    return injected
+  if (injected && injected.schemas) {
+    try {
+      return injected.schemas.get(id)
+    } catch {
+      console.warn('Deprecated usage of injected.')
+      return injected
+    }
   }
 
   const globalSpec = useOpenapi()
diff --git a/src/lib/parseSpec.ts b/src/lib/parseSpec.ts
new file mode 100644
index 0000000..85dd0a5
--- /dev/null
+++ b/src/lib/parseSpec.ts
@@ -0,0 +1,15 @@
+import { merge } from 'allof-merge'
+import { dereferenceSync } from '@trojs/openapi-dereference'
+
+export function parseSpec(spec) {
+  if (import.meta.env.VITE_DEBUG) {
+    console.warn('Parsing OpenAPI spec:', spec)
+  }
+  try {
+    const mergedSpec = merge(spec)
+    return dereferenceSync(mergedSpec)
+  } catch (error) {
+    console.warn('Failed to parse OpenAPI spec:', error)
+    return { ...spec }
+  }
+}
diff --git a/src/lib/transformSpec.ts b/src/lib/transformSpec.ts
new file mode 100644
index 0000000..bcea768
--- /dev/null
+++ b/src/lib/transformSpec.ts
@@ -0,0 +1,23 @@
+import { generateMissingOperationIds } from './generateMissingOperationIds'
+import { generateMissingSummary } from './generateMissingSummary'
+
+export function transformSpec(spec) {
+  if (import.meta.env.VITE_DEBUG) {
+    console.warn('Transforming OpenAPI spec:', spec)
+  }
+
+  if (!spec) {
+    return
+  }
+
+  if (!spec.openapi || !spec.openapi.startsWith('3.')) {
+    throw new Error('Only OpenAPI 3.x is supported')
+  }
+
+  if (spec?.paths) {
+    spec = generateMissingOperationIds(spec)
+    spec = generateMissingSummary(spec)
+  }
+
+  return Object.assign({}, spec)
+}


Test patch:
diff --git a/test/composables/openapi.test.ts b/test/composables/openapi.test.ts
index 2f2452d..1a48bab 100644
--- a/test/composables/openapi.test.ts
+++ b/test/composables/openapi.test.ts
@@ -1,9 +1,9 @@
 import { describe, expect, it } from 'vitest'
-import { OpenApi } from 'vitepress-openapi'
+import { createOpenApiInstance } from 'vitepress-openapi'
 import { spec } from '../testsConstants'
 
 describe('openapi with spec', () => {
-  const openapi = OpenApi({ spec })
+  const openapi = createOpenApiInstance({ spec })
 
   it('returns the correct operation for getOperation', () => {
     const result = openapi.getOperation('getUsers')
@@ -72,7 +72,7 @@ describe('openapi with spec', () => {
   })
 
   it('getOperationsTags returns all unique tags', () => {
-    const api = OpenApi({
+    const api = createOpenApiInstance({
       spec: {
         ...spec,
         paths: {
@@ -96,13 +96,13 @@ describe('openapi with spec', () => {
   })
 
   it('getOperationsTags returns empty array if no paths', () => {
-    const api = OpenApi({ spec: { openapi: '3.0.0' } })
+    const api = createOpenApiInstance({ spec: { openapi: '3.0.0' } })
     const tags = api.getOperationsTags()
     expect(tags).toEqual([])
   })
 
   it('getPathsByTags returns paths with specified tags', () => {
-    const api = OpenApi({
+    const api = createOpenApiInstance({
       spec: {
         ...spec,
         paths: {
@@ -134,13 +134,13 @@ describe('openapi with spec', () => {
   })
 
   it('getPathsByTags returns empty object if no matching tags', () => {
-    const api = OpenApi({ spec })
+    const api = createOpenApiInstance({ spec })
     const paths = api.getPathsByTags('nonexistent')
     expect(paths).toMatchObject({})
   })
 
   it('getPathsWithoutTags returns paths without tags', () => {
-    const api = OpenApi({
+    const api = createOpenApiInstance({
       spec: {
         ...spec,
         paths: {
@@ -172,13 +172,13 @@ describe('openapi with spec', () => {
   })
 
   it('getPathsWithoutTags returns empty array if no paths without tags', () => {
-    const api = OpenApi({ spec: { openapi: '3.0.0', paths: {} } })
+    const api = createOpenApiInstance({ spec: { openapi: '3.0.0', paths: {} } })
     const paths = api.getPathsWithoutTags()
     expect(paths).toMatchObject({})
   })
 
   it('getTags returns tags object', () => {
-    const api = OpenApi({ spec })
+    const api = createOpenApiInstance({ spec })
     const tags = api.getTags()
     expect(tags).toEqual([{
       name: 'users',
@@ -187,7 +187,7 @@ describe('openapi with spec', () => {
   })
 
   it('getTags returns empty array if no tags in spec', () => {
-    const api = OpenApi({ spec: { openapi: '3.0.0', paths: {} } })
+    const api = createOpenApiInstance({ spec: { openapi: '3.0.0', paths: {} } })
     const tags = api.getTags()
     expect(tags).toEqual([])
   })
@@ -220,7 +220,7 @@ describe('spec with different servers for specific path', () => {
     },
   }
 
-  const openapi = OpenApi({ spec })
+  const openapi = createOpenApiInstance({ spec })
 
   it('returns global server for getBaseUrl', () => {
     const result = openapi.getBaseUrl()
diff --git a/test/composables/useTheme.test.ts b/test/composables/useTheme.test.ts
index b9e66cb..108665b 100644
--- a/test/composables/useTheme.test.ts
+++ b/test/composables/useTheme.test.ts
@@ -1,23 +1,159 @@
 import { describe, expect, it } from 'vitest'
-import { unref } from 'vue'
-import { useTheme } from './src/composables/useTheme'
+import { useTheme } from '../../src/composables/useTheme'
+
+describe('composition API', () => {
+  it('can config', () => {
+    const theme = useTheme({
+      highlighterTheme: {
+        light: {},
+        dark: {},
+      },
+      request: {
+        defaultView: 'schema',
+        showBaseURL: true,
+      },
+      jsonViewer: {
+        deep: 5,
+      },
+      schemaViewer: {
+        deep: 3,
+      },
+      playground: {
+        jsonEditor: {
+          mode: 'text',
+          mainMenuBar: true,
+          navigationBar: true,
+        },
+      },
+      headingLevels: {
+        h1: 2,
+        h2: 3,
+      },
+      operation: {
+        badges: ['operationId'],
+      },
+      response: {
+        maxTabs: 10,
+        responseCodeSelector: 'select',
+      },
+      security: {
+        defaultScheme: 'bearer',
+        selectedScheme: 'bearer',
+      },
+      theme: {
+        highlighterTheme: {
+          light: {},
+          dark: {},
+        },
+      },
+      i18n: {
+        locale: 'es',
+      },
+      spec: {
+        groupByTags: false,
+        collapsePaths: true,
+        showPathsSummary: false,
+      },
+    })
+
+    expect(theme.getHighlighterTheme()).toEqual({
+      light: {},
+      dark: {},
+    })
+    expect(theme.getSchemaDefaultView()).toBe('schema')
+    expect(theme.getShowBaseURL()).toBe(true)
+    expect(theme.getJsonViewerDeep()).toBe(5)
+    expect(theme.getSchemaViewerDeep()).toBe(3)
+    expect(theme.getPlaygroundJsonEditorMode()).toBe('text')
+    expect(theme.getPlaygroundJsonEditorMainMenuBar()).toBe(true)
+    expect(theme.getPlaygroundJsonEditorNavigationBar()).toBe(true)
+    expect(theme.getHeadingLevels()).toEqual({
+      h1: 2,
+      h2: 3,
+      h3: 3,
+      h4: 4,
+      h5: 5,
+      h6: 6,
+    })
+    expect(theme.getOperationBadges()).toEqual(['operationId'])
+    expect(theme.getResponseCodeMaxTabs()).toBe(10)
+    expect(theme.getResponseCodeSelector()).toBe('select')
+    expect(theme.getSecurityDefaultScheme()).toBe('bearer')
+    expect(theme.getSecuritySelectedScheme()).toBe('bearer')
+    expect(theme.getI18nConfig().locale.value).toBe('es')
+    expect(theme.getSpecConfig()).toEqual({
+      groupByTags: expect.any(Object),
+      collapsePaths: expect.any(Object),
+      showPathsSummary: expect.any(Object),
+    })
+  })
+
+  it('can reset', () => {
+    const theme = useTheme({
+      jsonViewer: {
+        deep: 7,
+      },
+      schemaViewer: {
+        deep: 5,
+      },
+      headingLevels: {
+        h1: 3,
+        h2: 4,
+      },
+      operation: {
+        badges: ['operationId'],
+      },
+      i18n: {
+        locale: 'es',
+      },
+    })
+
+    expect(theme.getJsonViewerDeep()).toBe(7)
+    expect(theme.getSchemaViewerDeep()).toBe(5)
+    expect(theme.getHeadingLevels()).toEqual({
+      h1: 3,
+      h2: 4,
+      h3: 3,
+      h4: 4,
+      h5: 5,
+      h6: 6,
+    })
+    expect(theme.getOperationBadges()).toEqual(['operationId'])
+    expect(theme.getI18nConfig().locale.value).toBe('es')
+
+    theme.reset()
+
+    expect(theme.getJsonViewerDeep()).toBe(Number.POSITIVE_INFINITY)
+    expect(theme.getSchemaViewerDeep()).toBe(Number.POSITIVE_INFINITY)
+    expect(theme.getHeadingLevels()).toEqual({
+      h1: 1,
+      h2: 2,
+      h3: 3,
+      h4: 4,
+      h5: 5,
+      h6: 6,
+    })
+    expect(theme.getOperationBadges()).toEqual(['deprecated'])
+    expect(theme.getI18nConfig().locale.value).toBe('en')
+  })
+})
 
 describe('useTheme', () => {
-  const theme = useTheme()
+  const themeConfig = useTheme()
 
   it('returns the default locale', () => {
-    const result = theme.getLocale()
+    const result = themeConfig.getLocale()
     expect(result).toBe('en')
   })
 
   it('sets and gets the locale', () => {
-    theme.setLocale('es')
-    const result = theme.getLocale()
+    themeConfig.setLocale('es')
+    const result = themeConfig.getLocale()
     expect(result).toBe('es')
   })
 
-  it('returns the highlighter theme', () => {
-    const result = theme.getHighlighterTheme()
+  it('returns the highlighter themeConfig', () => {
+    const result = themeConfig.getHighlighterTheme()
     expect(result).toEqual({
       light: expect.any(Object),
       dark: expect.any(Object),
@@ -25,51 +161,51 @@ describe('useTheme', () => {
   })
 
   it('returns the default schema view', () => {
-    const result = theme.getSchemaDefaultView()
+    const result = themeConfig.getSchemaDefaultView()
     expect(result).toBe('contentType')
   })
 
   it('sets and gets the schema default view', () => {
-    theme.setSchemaDefaultView('schema')
-    const result = theme.getSchemaDefaultView()
+    themeConfig.setSchemaDefaultView('schema')
+    const result = themeConfig.getSchemaDefaultView()
     expect(result).toBe('schema')
   })
 
   it('returns the default showBaseURL value', () => {
-    const result = theme.getShowBaseURL()
+    const result = themeConfig.getShowBaseURL()
     expect(result).toBe(false)
   })
 
   it('sets and gets the showBaseURL value', () => {
-    theme.setShowBaseURL(true)
-    const result = theme.getShowBaseURL()
+    themeConfig.setShowBaseURL(true)
+    const result = themeConfig.getShowBaseURL()
     expect(result).toBe(true)
   })
 
   it('returns the default jsonViewer deep value', () => {
-    const result = theme.getJsonViewerDeep()
-    expect(result).toBe(Infinity)
+    const result = themeConfig.getJsonViewerDeep()
+    expect(result).toBe(Number.POSITIVE_INFINITY)
   })
 
   it('sets and gets the jsonViewer deep value', () => {
-    theme.setJsonViewerDeep(5)
-    const result = theme.getJsonViewerDeep()
+    themeConfig.setJsonViewerDeep(5)
+    const result = themeConfig.getJsonViewerDeep()
     expect(result).toBe(5)
   })
 
   it('returns the default schemaViewer deep value', () => {
-    const result = theme.getSchemaViewerDeep()
-    expect(result).toBe(Infinity)
+    const result = themeConfig.getSchemaViewerDeep()
+    expect(result).toBe(Number.POSITIVE_INFINITY)
   })
 
   it('sets and gets the schemaViewer deep value', () => {
-    theme.setSchemaViewerDeep(3)
-    const result = theme.getSchemaViewerDeep()
+    themeConfig.setSchemaViewerDeep(3)
+    const result = themeConfig.getSchemaViewerDeep()
     expect(result).toBe(3)
   })
 
   it('returns the heading levels', () => {
-    const result = theme.getHeadingLevels()
+    const result = themeConfig.getHeadingLevels()
     expect(result).toEqual({
       h1: 1,
       h2: 2,
@@ -81,13 +217,13 @@ describe('useTheme', () => {
   })
 
   it('returns the correct heading level', () => {
-    const result = theme.getHeadingLevel('h3')
+    const result = themeConfig.getHeadingLevel('h3')
     expect(result).toBe('h3')
   })
 
   it('sets and gets the heading levels', () => {
-    theme.setHeadingLevels({ h1: 2, h2: 3 })
-    const result = theme.getHeadingLevels()
+    themeConfig.setHeadingLevels({ h1: 2, h2: 3 })
+    const result = themeConfig.getHeadingLevels()
     expect(result).toEqual({
       h1: 2,
       h2: 3,
@@ -99,73 +235,73 @@ describe('useTheme', () => {
   })
 
   it('returns the default response code selector', () => {
-    const result = theme.getResponseCodeSelector()
+    const result = themeConfig.getResponseCodeSelector()
     expect(result).toBe('tabs')
   })
 
   it('sets and gets the response code selector', () => {
-    theme.setResponseCodeSelector('select')
-    const result = theme.getResponseCodeSelector()
+    themeConfig.setResponseCodeSelector('select')
+    const result = themeConfig.getResponseCodeSelector()
     expect(result).toBe('select')
   })
 
   it('returns the default response code max tabs', () => {
-    const result = theme.getResponseCodeMaxTabs()
+    const result = themeConfig.getResponseCodeMaxTabs()
     expect(result).toBe(5)
   })
 
   it('sets and gets the response code max tabs', () => {
-    theme.setResponseCodeMaxTabs(10)
-    const result = theme.getResponseCodeMaxTabs()
+    themeConfig.setResponseCodeMaxTabs(10)
+    const result = themeConfig.getResponseCodeMaxTabs()
     expect(result).toBe(10)
   })
 
   it('returns the default playground json editor mode', () => {
-    const result = theme.getPlaygroundJsonEditorMode()
+    const result = themeConfig.getPlaygroundJsonEditorMode()
     expect(result).toBe('tree')
   })
 
   it('sets and gets the playground json editor mode', () => {
-    theme.setPlaygroundJsonEditorMode('text')
-    const result = theme.getPlaygroundJsonEditorMode()
+    themeConfig.setPlaygroundJsonEditorMode('text')
+    const result = themeConfig.getPlaygroundJsonEditorMode()
     expect(result).toBe('text')
   })
 
   it('returns the default playground json editor main menu bar value', () => {
-    const result = theme.getPlaygroundJsonEditorMainMenuBar()
+    const result = themeConfig.getPlaygroundJsonEditorMainMenuBar()
     expect(result).toBe(false)
   })
 
   it('sets and gets the playground json editor main menu bar value', () => {
-    theme.setPlaygroundJsonEditorMainMenuBar(true)
-    const result = theme.getPlaygroundJsonEditorMainMenuBar()
+    themeConfig.setPlaygroundJsonEditorMainMenuBar(true)
+    const result = themeConfig.getPlaygroundJsonEditorMainMenuBar()
     expect(result).toBe(true)
   })
 
   it('returns the default playground json editor navigation bar value', () => {
-    const result = theme.getPlaygroundJsonEditorNavigationBar()
+    const result = themeConfig.getPlaygroundJsonEditorNavigationBar()
     expect(result).toBe(false)
   })
 
   it('sets and gets the playground json editor navigation bar value', () => {
-    theme.setPlaygroundJsonEditorNavigationBar(true)
-    const result = theme.getPlaygroundJsonEditorNavigationBar()
+    themeConfig.setPlaygroundJsonEditorNavigationBar(true)
+    const result = themeConfig.getPlaygroundJsonEditorNavigationBar()
     expect(result).toBe(true)
   })
 
   it('returns the operation badges', () => {
-    const result = theme.getOperationBadges()
+    const result = themeConfig.getOperationBadges()
     expect(result).toEqual(['deprecated'])
   })
 
   it('sets and gets the operation badges', () => {
-    theme.setOperationBadges(['operationId'])
-    const result = theme.getOperationBadges()
+    themeConfig.setOperationBadges(['operationId'])
+    const result = themeConfig.getOperationBadges()
     expect(result).toEqual(['operationId'])
   })
 
   it('returns the i18n config', () => {
-    const result = theme.getI18nConfig()
+    const result = themeConfig.getI18nConfig()
     expect(result).toEqual({
       locale: expect.any(Object),
       fallbackLocale: expect.any(Object),
@@ -174,13 +310,13 @@ describe('useTheme', () => {
   })
 
   it('sets and gets the i18n config', () => {
-    theme.setI18nConfig({ locale: 'es' })
-    const result = theme.getI18nConfig()
+    themeConfig.setI18nConfig({ locale: 'es' })
+    const result = themeConfig.getI18nConfig()
     expect(result.locale.value).toBe('es')
   })
 
   it('returns the spec config', () => {
-    const result = theme.getSpecConfig()
+    const result = themeConfig.getSpecConfig()
     expect(result).toEqual({
       groupByTags: expect.any(Object),
       collapsePaths: expect.any(Object),
@@ -189,8 +325,8 @@ describe('useTheme', () => {
   })
 
   it('sets spec config', () => {
-    theme.setSpecConfig({ groupByTags: false, collapsePaths: true, showPathsSummary: false })
-    const result = theme.getSpecConfig()
+    themeConfig.setSpecConfig({ groupByTags: false, collapsePaths: true, showPathsSummary: false })
+    const result = themeConfig.getSpecConfig()
     result.groupByTags.value = false
     result.collapsePaths.value = true
     result.showPathsSummary.value = false