54 Commits

Author SHA1 Message Date
2ff2445969 chore(deps): update studio non-major dependencies 2024-01-09 21:04:45 +00:00
8bb7665af5 chore: update dependencies (#38) 2024-01-09 12:08:49 -07:00
644ccc237d chore: test bundler module resolution (#36) 2023-12-22 11:09:18 -07:00
84a2c821e9 chore(release): v1.0.3 2023-10-26 13:07:04 -06:00
386c7dac05 chore(release): v1.0.4 2023-10-26 13:05:09 -06:00
1bec7a703d refactor: update to DevTools v1 2023-10-26 13:02:26 -06:00
0b575fa322 docs: update link 2023-10-19 11:31:52 -06:00
4c5a0345a8 docs: fix install workflow 2023-10-19 11:26:37 -06:00
ea7c7b02d6 docs: fix playground path 2023-10-19 11:24:33 -06:00
9296ae7d78 docs: update ui 2023-10-19 11:18:11 -06:00
549b2f30be chore: remove comment 2023-10-11 20:13:08 -06:00
4bc07425b4 fix: add schema type 2023-10-11 20:12:17 -06:00
b0fb2edc24 chore(deps): update studio dependencies (#27)
Co-authored-by: nuxt-studio[bot] <117276984+nuxt-studio[bot]@users.noreply.github.com>
2023-10-04 23:39:28 +03:00
9b63877eaa chore(release): v1.0.2 2023-10-04 05:13:46 +03:00
ba845931da chore(release): v1.0.2 2023-10-04 05:11:40 +03:00
3d7503e227 chore: lint 2023-10-03 19:56:52 -06:00
c570597d06 fix: resolve build stuck issue with nitro pre-render enabled (#26) 2023-10-04 04:56:15 +03:00
e7c7beb8fd docs: default connection
close #24
2023-10-03 03:46:23 +03:00
037920620e chore(release): v1.0.1 2023-09-22 01:50:58 +03:00
0f73464afe chore: update dependencies 2023-09-21 16:41:21 -06:00
5bf554fbdf chore(release): v1.0.1 2023-09-20 20:19:25 +03:00
b566196c96 chore(release): v1.0.0 2023-09-20 19:54:06 +03:00
4da24986ee chore(release): v1.0.0 2023-09-20 19:53:01 +03:00
ced2c77427 chore(release): v1.1.0 2023-09-20 19:48:51 +03:00
431d3784fe feat: version 1.0.0 (#21) 2023-09-20 19:47:07 +03:00
033380e051 chore(release): v0.0.9 2023-09-20 05:21:24 +03:00
0db6bddbc4 docs: update docus 2023-07-17 16:18:50 +03:00
0dfad07399 chore(release): v0.0.9 2023-07-09 14:39:55 +03:00
e6b8b7d0fa chore: fix tsconfig for build time 2023-07-09 14:39:02 +03:00
0cdba0d764 chore: lint 2023-07-09 14:27:10 +03:00
9c0e0baf95 docs: add types link 2023-07-09 14:21:33 +03:00
925d382f2f chore: update dependencies 2023-07-09 14:16:21 +03:00
3ff97569f5 docs(utils): fix model options type 2023-07-09 14:10:34 +03:00
52a5ae9180 Merge branch 'main' of https://github.com/arashsheyda/nuxt-mongoose 2023-07-09 14:08:23 +03:00
4647fbda16 Revert "chore: fix defineNitroPlugin patch (#9)"
This reverts commit 470f272183.
2023-07-09 14:08:05 +03:00
647d26db85 docs(utils): add hooks and improve documentation 2023-07-09 13:53:11 +03:00
40b8ca91ee chore: example env 2023-07-09 13:28:28 +03:00
d92a58b2ad feat: mongoose schema hooks 2023-07-09 13:25:38 +03:00
5a43ebe06f fix: update connection message in logger (#10) 2023-07-02 12:36:48 +03:00
56259adaf7 feat: add possibility to type mongoose model (#8)
Co-authored-by: Arash <38922203+arashsheyda@users.noreply.github.com>
2023-07-02 11:55:49 +03:00
470f272183 chore: fix defineNitroPlugin patch (#9) 2023-06-28 23:15:30 +03:00
e2d37c2efe chore(release): v0.0.8 2023-06-18 13:19:46 +03:00
487cca530b chore: update cover 2023-06-18 13:09:00 +03:00
9e867fc0b3 docs: favicon 2023-06-18 13:04:06 +03:00
23ec1989f7 docs: style 2023-06-18 13:00:34 +03:00
1c3608743e fix: resource generator modelsDir output 2023-06-18 12:32:00 +03:00
33bb3cc550 fix: run studio action only for docs 2023-06-18 12:26:59 +03:00
fe1807fcd5 Merge branch 'main' of https://github.com/arashsheyda/nuxt-mongoose 2023-06-18 12:21:49 +03:00
aaef56cb29 refactor: reuse vite's websocket 2023-06-18 12:20:57 +03:00
32846c1e44 Merge pull request #6 from joypal23jkp/patch-1
Updated the configuration setup docs.
2023-06-12 10:53:36 +03:00
771e071005 Updated the configuration setup docs.
Figured out that need to just add MONGODB_URI to .env if only need to provide URI.
2023-06-12 02:12:47 +06:00
2ba8bdadfc docs(devtools): demo video 2023-06-08 22:39:03 +03:00
6c057d4cd7 docs: remove devtools 2023-06-05 21:35:37 +03:00
00259fc865 chore(deployment): add workflow file 2023-06-05 21:31:22 +03:00
69 changed files with 14546 additions and 5999 deletions

View File

@ -1,2 +1,5 @@
dist dist
node_modules node_modules
docs
.github
.vscode

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: [arashsheyda]

82
.github/workflows/studio.yml vendored Normal file
View File

@ -0,0 +1,82 @@
name: studio-nuxt-build
run-name: studio nuxt build
on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Add write workflow permissions
permissions:
contents: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Build job
build-and-deploy:
if: startsWith(github.event.head_commit.message, 'docs:')
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: docs
strategy:
matrix:
os: [ubuntu-latest]
node: [18]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Identify package manager
id: pkgman
run: |
cache=`[ -f "docs/pnpm-lock.yaml" ] && echo "pnpm" || ([ -f "docs/package-lock.json" ] && echo "npm" || ([ -f "docs/yarn.lock" ] && echo "yarn" || echo ""))`
package_manager=`[ ! -z "$cache" ] && echo "$cache" || echo "pnpm"`
echo "cache=$cache" >> $GITHUB_OUTPUT
echo "package_manager=$package_manager" >> $GITHUB_OUTPUT
- uses: pnpm/action-setup@v2.2.4
if: ${{ steps.pkgman.outputs.package_manager == 'pnpm' }}
name: Install pnpm
id: pnpm-install
with:
version: 7
- uses: actions/setup-node@v3
with:
version: ${{ matrix.node }}
cache: ${{ steps.pkgman.outputs.cache }}
- name: Install dependencies
run: ${{ steps.pkgman.outputs.package_manager }} install --no-frozen-lockfile
- name: Install @nuxthq/studio
run: ${{ steps.pkgman.outputs.package_manager }} add -D @nuxthq/studio
- name: Create .nuxtrc
run: echo 'modules[]=@nuxthq/studio' > .nuxtrc
- name: Generate
run: ${{ steps.pkgman.outputs.package_manager }} nuxi generate
env:
NUXT_PUBLIC_STUDIO_API_URL: https://api.nuxt.studio
NUXT_PUBLIC_STUDIO_TOKENS: 6222c5e49391ad99e20e3c1b1a475c656436e8b0877c32021147d8727205c440
- name: Add .nojekyll file
run: touch .output/public/.nojekyll
# Deployment job
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: docs/.output/public

2
.nuxtrc Normal file
View File

@ -0,0 +1,2 @@
# enable TypeScript bundler module resolution - https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler
experimental.typescriptBundlerResolution=true

View File

@ -1,3 +0,0 @@
{
"editor.tabSize": 2,
}

View File

@ -1,6 +1,155 @@
# Changelog # Changelog
## v1.0.3
[compare changes](https://github.com/arashsheyda/nuxt-mongoose/compare/v1.0.2...v1.0.3)
### 🩹 Fixes
- Add schema type ([4bc0742](https://github.com/arashsheyda/nuxt-mongoose/commit/4bc0742))
### 💅 Refactors
- Update to DevTools v1 ([1bec7a7](https://github.com/arashsheyda/nuxt-mongoose/commit/1bec7a7))
### 📖 Documentation
- Update ui ([9296ae7](https://github.com/arashsheyda/nuxt-mongoose/commit/9296ae7))
- Fix playground path ([ea7c7b0](https://github.com/arashsheyda/nuxt-mongoose/commit/ea7c7b0))
- Fix install workflow ([4c5a034](https://github.com/arashsheyda/nuxt-mongoose/commit/4c5a034))
- Update link ([0b575fa](https://github.com/arashsheyda/nuxt-mongoose/commit/0b575fa))
### 🏡 Chore
- Remove comment ([549b2f3](https://github.com/arashsheyda/nuxt-mongoose/commit/549b2f3))
### ❤️ Contributors
- Arash
## v1.0.2
[compare changes](https://github.com/arashsheyda/nuxt-mongoose/compare/v1.0.1...v1.0.2)
### 🩹 Fixes
- Resolve build stuck issue with nitro pre-render enabled ([#26](https://github.com/arashsheyda/nuxt-mongoose/pull/26))
### 📖 Documentation
- Default connection ([e7c7beb](https://github.com/arashsheyda/nuxt-mongoose/commit/e7c7beb))
### 🏡 Chore
- **release:** V1.0.2 ([ba8459](https://github.com/arashsheyda/nuxt-mongoose/commit/ba8459))
- Lint ([3d7503e](https://github.com/arashsheyda/nuxt-mongoose/commit/3d7503e))
### ❤️ Contributors
- Arash Sheyda <sheidaeearash1999@gmail.com>
- Arash
## v1.0.1
### 🏡 Chore
- **release:** V1.0.0 ([b566196](https://github.com/arashsheyda/nuxt-mongoose/commit/b566196))
- **release:** V1.0.1 ([5bf554f](https://github.com/arashsheyda/nuxt-mongoose/commit/5bf554f))
- Update dependencies ([0f73464](https://github.com/arashsheyda/nuxt-mongoose/commit/0f73464))
### ❤️ Contributors
- Arash Sheyda <sheidaeearash1999@gmail.com>
## v1.0.0
[compare changes](https://github.com/arashsheyda/nuxt-mongoose/compare/v0.0.9...v1.0.0)
### 🚀 Enhancements
- Version 1.0.0 ([#21](https://github.com/arashsheyda/nuxt-mongoose/pull/21))
### 📖 Documentation
- Update docus ([0db6bdd](https://github.com/arashsheyda/nuxt-mongoose/commit/0db6bdd))
### ❤️ Contributors
- Arashsheyda <sheidaeearash1999@gmail.com>
## v0.0.9
[compare changes](https://github.com/arashsheyda/nuxt-mongoose/compare/v0.0.8...v0.0.9)
### 🚀 Enhancements
- Add possibility to type mongoose model ([#8](https://github.com/arashsheyda/nuxt-mongoose/pull/8))
- Mongoose schema hooks ([d92a58b](https://github.com/arashsheyda/nuxt-mongoose/commit/d92a58b))
### 🩹 Fixes
- Update connection message in logger ([#10](https://github.com/arashsheyda/nuxt-mongoose/pull/10))
### 📖 Documentation
- **utils:** Add hooks and improve documentation ([647d26d](https://github.com/arashsheyda/nuxt-mongoose/commit/647d26d))
- **utils:** Fix model options type ([3ff9756](https://github.com/arashsheyda/nuxt-mongoose/commit/3ff9756))
- Add types link ([9c0e0ba](https://github.com/arashsheyda/nuxt-mongoose/commit/9c0e0ba))
### 🏡 Chore
- Fix defineNitroPlugin patch ([#9](https://github.com/arashsheyda/nuxt-mongoose/pull/9))
- Example env ([40b8ca9](https://github.com/arashsheyda/nuxt-mongoose/commit/40b8ca9))
- Fix defineNitroPlugin patch " ([#9](https://github.com/arashsheyda/nuxt-mongoose/pull/9))
- Update dependencies ([925d382](https://github.com/arashsheyda/nuxt-mongoose/commit/925d382))
- Lint ([0cdba0d](https://github.com/arashsheyda/nuxt-mongoose/commit/0cdba0d))
- Fix tsconfig for build time ([e6b8b7d](https://github.com/arashsheyda/nuxt-mongoose/commit/e6b8b7d))
### ❤️ Contributors
- Arash Sheyda <sheidaeearash1999@gmail.com>
- Amir-al-mohamad111
- Oumar Barry ([@oumarbarry](http://github.com/oumarbarry))
## v0.0.8
[compare changes](https://github.com/arashsheyda/nuxt-mongoose/compare/v0.0.7...v0.0.8)
### 🚀 Enhancements
- Documentation ([6ca94dc](https://github.com/arashsheyda/nuxt-mongoose/commit/6ca94dc))
### 🩹 Fixes
- Module options type ([182b760](https://github.com/arashsheyda/nuxt-mongoose/commit/182b760))
- Run studio action only for docs ([33bb3cc](https://github.com/arashsheyda/nuxt-mongoose/commit/33bb3cc))
- Resource generator modelsDir output ([1c36087](https://github.com/arashsheyda/nuxt-mongoose/commit/1c36087))
### 💅 Refactors
- Reuse vite's websocket ([aaef56c](https://github.com/arashsheyda/nuxt-mongoose/commit/aaef56c))
### 📖 Documentation
- Remove devtools ([6c057d4](https://github.com/arashsheyda/nuxt-mongoose/commit/6c057d4))
- **devtools:** Demo video ([2ba8bda](https://github.com/arashsheyda/nuxt-mongoose/commit/2ba8bda))
- Style ([23ec198](https://github.com/arashsheyda/nuxt-mongoose/commit/23ec198))
- Favicon ([9e867fc](https://github.com/arashsheyda/nuxt-mongoose/commit/9e867fc))
### 🏡 Chore
- Playground example ([3356ffd](https://github.com/arashsheyda/nuxt-mongoose/commit/3356ffd))
- **deployment:** Add workflow file ([00259fc](https://github.com/arashsheyda/nuxt-mongoose/commit/00259fc))
- Update cover ([487cca5](https://github.com/arashsheyda/nuxt-mongoose/commit/487cca5))
### ❤️ Contributors
- Arash
- Arash Sheyda <sheidaeearash1999@gmail.com>
- Arashsheyda <sheidaeearash1999@gmail.com>
## v0.0.7 ## v0.0.7
[compare changes](https://github.com/arashsheyda/nuxt-mongoose/compare/v0.0.6...v0.0.7) [compare changes](https://github.com/arashsheyda/nuxt-mongoose/compare/v0.0.6...v0.0.7)

View File

@ -1,4 +1,4 @@
![nuxt-mongoose](https://raw.githubusercontent.com/arashsheyda/nuxt-mongoose/c967d0bb162dddc27fa510ba3b49e4f9b6b2db61/docs/public/nuxt-mongoose.svg) ![nuxt-mongoose](https://nuxt-mongoose.nuxt.space/cover.jpg)
<div align="center"> <div align="center">
<h1>Nuxt Mongoose</h1> <h1>Nuxt Mongoose</h1>

View File

@ -1,7 +1,3 @@
<script lang="ts" setup>
import './styles/global.css'
</script>
<template> <template>
<Html> <Html>
<Body h-screen> <Body h-screen>

View File

@ -1,33 +1,53 @@
<script lang="ts" setup> <script lang="ts" setup>
defineProps({ import { computed } from 'vue'
connection: {
const props = defineProps({
code: {
type: Number, type: Number,
default: 0, default: 0,
}, },
}) })
const connections = [
{
color: 'text-red-5',
border: 'border-red-5',
status: 'Not Connected',
description: 'Please Check Your Connection!',
},
{
color: 'text-green-5',
border: 'border-green-5',
status: 'Connected',
description: 'Everything is Working Perfectly!',
},
{
color: 'text-yellow-5',
border: 'border-yellow-5',
status: 'Connecting',
description: 'Just a Moment, We"re Getting There!',
},
{
color: 'text-orange-5',
border: 'border-orange-5',
status: 'Disconnecting',
description: 'Preparing to Safely Disconnect!',
},
]
const connection = computed(() => connections[props.code])
</script> </script>
<template> <template>
<NPanelGrids> <NPanelGrids>
<div flex="~ gap-2" animate-pulse items-center text-yellow> <div flex="~ gap-2" animate-pulse items-center text-lg font-bold :class="connection.color">
<NIcon icon="carbon-flow-connection" /> ({{ code }}):
Please check your mongodb connection {{ connection.status }},
</div> {{ connection.description }}
<div flex="~ gap-2" items-center text-light>
Your current connection is: {{ connection }}
</div> </div>
<div absolute bottom-10 left-10 right-10 flex justify-around> <div absolute bottom-10 left-10 right-10 flex justify-around>
<NCard p2 text-red-5> <NCard v-for="item, index of connections" :key="index" p2 :class="[item.color, item.status === connection.status ? item.border : '']">
0: Not connected ({{ index }}): {{ item.status }}
</NCard>
<NCard p2 text-green-5>
1: Connected
</NCard>
<NCard p2 text-yellow-5>
2: Connecting
</NCard>
<NCard p2 text-orange-5>
3: Disconnecting
</NCard> </NCard>
</div> </div>
</NPanelGrids> </NPanelGrids>

View File

@ -1,4 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'nuxt/app'
import { computed, reactive, ref } from 'vue'
import { rpc } from '../composables/rpc'
interface ColumnInterface { interface ColumnInterface {
name: string name: string
type: string type: string
@ -115,10 +119,8 @@ const convertedBread = computed(() => {
const formattedFields = computed(() => { const formattedFields = computed(() => {
return fields.value.map((field) => { return fields.value.map((field) => {
for (const [key, value] of Object.entries(field)) { for (const [key, value] of Object.entries(field)) {
if (!value) { if (!value)
// @ts-expect-error - no need for type checking
delete field[key] delete field[key]
}
} }
return field return field
@ -126,7 +128,7 @@ const formattedFields = computed(() => {
}) })
async function generate() { async function generate() {
await rpc.generateResource( await rpc.value?.generateResource(
{ {
name: collection.value, name: collection.value,
fields: schema.value ? formattedFields.value : undefined, fields: schema.value ? formattedFields.value : undefined,
@ -142,6 +144,7 @@ async function generate() {
const toggleSchema = computed({ const toggleSchema = computed({
get() { get() {
if (hasBread.value) if (hasBread.value)
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
return schema.value = true return schema.value = true
return schema.value return schema.value
}, },
@ -246,8 +249,8 @@ const toggleSchema = computed({
<NTextInput v-else v-model="column.default" n="orange" /> <NTextInput v-else v-model="column.default" n="orange" />
</div> </div>
<div flex justify-center gap2> <div flex justify-center gap2>
<NIconButton icon="carbon-add" n="cyan" @click="addField(index)" /> <NButton icon="carbon-add" n="cyan" @click="addField(index)" />
<NIconButton icon="carbon-delete" n="red" @click="removeField(index)" /> <NButton icon="carbon-trash-can" n="red" @click="removeField(index)" />
</div> </div>
</div> </div>
</div> </div>
@ -257,5 +260,3 @@ const toggleSchema = computed({
</NButton> </NButton>
</div> </div>
</template> </template>
<style></style>

View File

@ -1,4 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue'
import { computedAsync } from '@vueuse/core'
import { rpc } from '../composables/rpc'
import { useCopy } from '../composables/editor'
const props = defineProps({ const props = defineProps({
collection: { collection: {
type: String, type: String,
@ -10,19 +15,19 @@ const props = defineProps({
const pagination = reactive({ limit: 20, page: 1 }) const pagination = reactive({ limit: 20, page: 1 })
const countDocuments = computedAsync(async () => { const countDocuments = computedAsync(async () => {
return await rpc.countDocuments(props.collection) return await rpc.value?.countDocuments(props.collection)
}) })
const documents = computedAsync(async () => { const documents = computedAsync(async () => {
return await rpc.listDocuments(props.collection, pagination) return await rpc.value?.listDocuments(props.collection, pagination)
}) })
watch(pagination, async () => { watch(pagination, async () => {
documents.value = await rpc.listDocuments(props.collection, pagination) documents.value = await rpc.value?.listDocuments(props.collection, pagination)
}) })
const schema = computedAsync<any>(async () => { const schema = computedAsync<any>(async () => {
return await rpc.resourceSchema(props.collection) return await rpc.value?.resourceSchema(props.collection)
}) })
const fields = computed(() => { const fields = computed(() => {
@ -79,14 +84,17 @@ function editDocument(document: any) {
} }
async function saveDocument(document: any, create = true) { async function saveDocument(document: any, create = true) {
const method = create ? rpc.createDocument : rpc.updateDocument const method = create ? rpc.value?.createDocument : rpc.value?.updateDocument
if (!method)
return
const newDocument = await method(props.collection, document) const newDocument = await method(props.collection, document)
// TODO: show toast
if (newDocument?.error) if (newDocument?.error)
return alert(newDocument.error.message) return
if (create) { if (create) {
if (!documents.value.length) { if (!documents.value.length) {
documents.value = await rpc.listDocuments(props.collection, pagination) documents.value = await rpc.value?.listDocuments(props.collection, pagination)
return discardEditing() return discardEditing()
} }
documents.value.push({ _id: newDocument.insertedId, ...document }) documents.value.push({ _id: newDocument.insertedId, ...document })
@ -104,9 +112,10 @@ function discardEditing() {
} }
async function deleteDocument(document: any) { async function deleteDocument(document: any) {
const newDocument = await rpc.deleteDocument(props.collection, document._id) const newDocument = await rpc.value?.deleteDocument(props.collection, document._id)
// TODO: show toast
if (newDocument.deletedCount === 0) if (newDocument.deletedCount === 0)
return alert('Failed to delete document') return
documents.value = documents.value.filter((doc: any) => doc._id !== document._id) documents.value = documents.value.filter((doc: any) => doc._id !== document._id)
} }
@ -116,7 +125,7 @@ const copy = useCopy()
<template> <template>
<div ref="dbContainer" :class="{ 'h-full': !documents?.length }"> <div ref="dbContainer" :class="{ 'h-full': !documents?.length }">
<Navbar v-model:search="search" sticky top-0 px4 py2 backdrop-blur z-10> <NNavbar v-model:search="search" sticky top-0 px4 py2 backdrop-blur z-10>
<template #actions> <template #actions>
<NButton icon="carbon:add" n="green" @click="addDocument"> <NButton icon="carbon:add" n="green" @click="addDocument">
Add Document Add Document
@ -146,7 +155,7 @@ const copy = useCopy()
</NSelect> </NSelect>
</div> </div>
</div> </div>
</Navbar> </NNavbar>
<table v-if="documents?.length || selectedDocument" w-full mb10 :class="{ 'editing-mode': editing }"> <table v-if="documents?.length || selectedDocument" w-full mb10 :class="{ 'editing-mode': editing }">
<thead> <thead>
<tr> <tr>
@ -168,17 +177,17 @@ const copy = useCopy()
{{ document[field] }} {{ document[field] }}
</span> </span>
</td> </td>
<td class="actions"> <td>
<div flex justify-center gap2 class="group"> <div flex justify-center gap2 class="group">
<template v-if="editing && selectedDocument._id === document._id"> <template v-if="editing && selectedDocument._id === document._id">
<NIconButton icon="carbon-save" @click="saveDocument(selectedDocument, false)" /> <NButton title="Save" icon="carbon-save" n="blue" @click="saveDocument(selectedDocument, false)" />
<NIconButton icon="carbon-close" @click="discardEditing" /> <NButton title="Cancel" icon="carbon-close" n="red" @click="discardEditing" />
</template> </template>
<template v-else> <template v-else>
<NIconButton icon="carbon-edit" @click="editDocument(document)" /> <NButton title="Edit" icon="carbon-edit" n="blue" @click="editDocument(document)" />
<NIconButton icon="carbon-delete" @click="deleteDocument(document)" /> <NButton title="Delete" icon="carbon-trash-can" n="red" @click="deleteDocument(document)" />
<NIconButton icon="carbon-document-multiple-02" @click="saveDocument(document)" /> <NButton title="Duplicate" icon="carbon-document-multiple-02" n="cyan" @click="saveDocument(document)" />
<NIconButton absolute right-4 opacity-0 group-hover="opacity-100" transition-all icon="carbon-copy" @click="copy(JSON.stringify(document))" /> <NButton title="Copy" n="xs purple" absolute right-4 opacity-0 group-hover="opacity-100" transition-all icon="carbon-copy" @click="copy(JSON.stringify(document))" />
</template> </template>
</div> </div>
</td> </td>
@ -188,14 +197,14 @@ const copy = useCopy()
<input v-if="field !== '_id'" v-model="selectedDocument[field]" :placeholder="field"> <input v-if="field !== '_id'" v-model="selectedDocument[field]" :placeholder="field">
<input v-else placeholder="ObjectId(_id)" disabled> <input v-else placeholder="ObjectId(_id)" disabled>
</td> </td>
<td flex justify-center gap2 class="actions"> <td flex="~ justify-center gap2">
<NIconButton icon="carbon-save" @click="saveDocument(selectedDocument)" /> <NButton title="Save" icon="carbon-save" n="green" @click="saveDocument(selectedDocument)" />
<NIconButton icon="carbon-close" @click="discardEditing" /> <NButton title="Cancel" icon="carbon-close" n="red" @click="discardEditing" />
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div v-else flex justify-center items-center h-full text-2xl> <div v-else flex="~ justify-center items-center" h-full text-2xl>
<NIcon icon="carbon-document" mr1 /> <NIcon icon="carbon-document" mr1 />
No documents found No documents found
</div> </div>
@ -203,11 +212,6 @@ const copy = useCopy()
</template> </template>
<style lang="scss"> <style lang="scss">
// TODO:
.actions .n-icon {
margin: 0;
}
table { table {
table-layout: fixed; table-layout: fixed;
tr { tr {

View File

@ -1,54 +0,0 @@
<script setup lang="ts">
const props = defineProps<{
modelValue?: boolean
navbar?: HTMLElement
autoClose?: boolean
}>()
const emit = defineEmits<{
(e: 'close'): void
}>()
const el = ref<HTMLElement>()
const { height: top } = useElementSize(() => props.navbar, undefined, { box: 'border-box' })
onClickOutside(el, () => {
if (props.modelValue && props.autoClose)
emit('close')
}, {
ignore: ['#open-drawer-right'],
})
</script>
<script lang="ts">
export default {
inheritAttrs: false,
}
</script>
<template>
<Transition
enter-active-class="duration-200 ease-in"
enter-from-class="transform translate-x-1/1"
enter-to-class="opacity-100"
leave-active-class="duration-200 ease-out"
leave-from-class="opacity-100"
leave-to-class="transform translate-x-1/1"
>
<div
v-if="modelValue"
ref="el"
border="l base"
flex="~ col gap-1"
fixed bottom-0 right-0 z-10 z-20 of-auto text-sm backdrop-blur-lg
:style="{ top: `${top}px` }"
v-bind="$attrs"
>
<NIconButton absolute right-2 top-2 z-20 text-xl icon="carbon-close" @click="$emit('close')" />
<div relative h-full w-full of-auto>
<slot />
</div>
</div>
</Transition>
</template>

View File

@ -1,43 +0,0 @@
<script setup lang="ts">
defineProps({
search: {
type: String,
default: undefined,
},
noPadding: {
type: Boolean,
default: true,
},
placeholder: {
type: String,
default: 'Search...',
},
})
const emit = defineEmits<{
(event: 'update:search', value: string): void
}>()
function update(event: any) {
emit('update:search', event.target.value)
}
</script>
<template>
<div flex="~ col gap2" border="b base" flex-1 navbar-glass :class="[{ p4: !noPadding }]">
<div flex="~ gap4">
<slot name="search">
<NTextInput
:placeholder="placeholder"
icon="carbon-search"
n="primary" flex-auto
:class="{ 'px-5 py-2': !noPadding }"
:value="search"
@input="update"
/>
</slot>
<slot name="actions" />
</div>
<slot />
</div>
</template>

View File

@ -1,36 +0,0 @@
<script lang="ts" setup>
import { Pane, Splitpanes } from 'splitpanes'
import 'splitpanes/dist/splitpanes.css'
const props = defineProps({
minLeft: {
type: Number,
default: 10,
},
minRight: {
type: Number,
default: 10,
},
maxLeft: {
type: Number,
default: 90,
},
maxRight: {
type: Number,
default: 90,
},
})
const size = ref(props.maxLeft)
</script>
<template>
<Splitpanes h-full of-hidden @resize="size = $event[0].size">
<Pane border="r base" h-full class="of-auto!" :size="size" :min-size="minLeft" :max-size="maxLeft">
<slot name="left" />
</Pane>
<Pane relative h-full class="of-auto!" :min-size="minRight">
<slot name="right" />
</Pane>
</Splitpanes>
</template>

View File

@ -1,10 +1,14 @@
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
// import { } from '@nuxt/devtools-ui-kit'
export function useCopy() { export function useCopy() {
const clipboard = useClipboard() const clipboard = useClipboard()
return (text: string) => { return (text: string) => {
clipboard.copy(text) clipboard.copy(text)
// TODO: show toast // devtoolsUiShowNotification({
// message: 'Copied to clipboard',
// icon: 'carbon-copy',
// })
} }
} }

View File

@ -1,69 +1,18 @@
import { createBirpc } from 'birpc' import { onDevtoolsClientConnected } from '@nuxt/devtools-kit/iframe-client'
import { parse, stringify } from 'flatted' import type { BirpcReturn } from 'birpc'
import { ref } from 'vue'
import type { NuxtDevtoolsClient } from '@nuxt/devtools-kit/dist/types'
import type { ClientFunctions, ServerFunctions } from '../../src/types' import type { ClientFunctions, ServerFunctions } from '../../src/types'
import { PATH_ENTRY } from '../../src/constants' import { RPC_NAMESPACE } from '../../src/constants'
const RECONNECT_INTERVAL = 2000 export const devtools = ref<NuxtDevtoolsClient>()
export const devtoolsRpc = ref<NuxtDevtoolsClient['rpc']>()
export const rpc = ref<BirpcReturn<ServerFunctions, ClientFunctions>>()
export const wsConnecting = ref(true) onDevtoolsClientConnected(async (client) => {
export const wsError = ref<any>() devtoolsRpc.value = client.devtools.rpc
devtools.value = client.devtools
let connectPromise = connectWS() rpc.value = client.devtools.extendClientRpc<ServerFunctions, ClientFunctions>(RPC_NAMESPACE, {
let onMessage: Function = () => {} })
export const clientFunctions = {
// will be added in app.vue
} as ClientFunctions
export const extendedRpcMap = new Map<string, any>()
export const rpc = createBirpc<ServerFunctions>(clientFunctions, {
post: async (d) => {
(await connectPromise).send(d)
},
on: (fn) => { onMessage = fn },
serialize: stringify,
deserialize: parse,
resolver(name, fn) {
if (fn)
return fn
if (!name.includes(':'))
return
const [namespace, fnName] = name.split(':')
return extendedRpcMap.get(namespace)?.[fnName]
},
onError(error, name) {
console.error(`[nuxt-devtools] RPC error on executing "${name}":`, error)
},
}) })
async function connectWS() {
const wsUrl = new URL(`ws://host${PATH_ENTRY}`)
wsUrl.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
wsUrl.host = 'localhost:3000'
const ws = new WebSocket(wsUrl.toString())
ws.addEventListener('message', e => onMessage(String(e.data)))
ws.addEventListener('error', (e) => {
console.error(e)
wsError.value = e
})
ws.addEventListener('close', () => {
// eslint-disable-next-line no-console
console.log('[nuxt-devtools] WebSocket closed, reconnecting...')
wsConnecting.value = true
setTimeout(async () => {
connectPromise = connectWS()
}, RECONNECT_INTERVAL)
})
wsConnecting.value = true
if (ws.readyState !== WebSocket.OPEN)
await new Promise(resolve => ws.addEventListener('open', resolve))
// eslint-disable-next-line no-console
console.log('[nuxt-devtools] WebSocket connected.')
wsConnecting.value = false
wsError.value = null
return ws
}

View File

@ -0,0 +1 @@
export const selectedCollection = useState('mongo:collection', () => '')

View File

@ -1,10 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
const readyState = computedAsync(async () => await rpc.readyState()) import { computedAsync } from '@vueuse/core'
import { rpc } from '../composables/rpc'
const readyState = computedAsync(async () => await rpc.value?.readyState())
</script> </script>
<template> <template>
<div h-full of-auto> <div h-full of-auto>
<slot v-if="readyState === 1" /> <slot v-if="readyState === 1" />
<Connection v-else :connection="readyState" /> <Connection v-else :code="readyState" />
</div> </div>
</template> </template>

View File

@ -1,5 +1,5 @@
import { resolve } from 'pathe' import { resolve } from 'pathe'
import { PATH_CLIENT } from '../src/constants' import { CLIENT_PATH } from '../src/constants'
export default defineNuxtConfig({ export default defineNuxtConfig({
ssr: false, ssr: false,
@ -22,6 +22,6 @@ export default defineNuxtConfig({
}, },
}, },
app: { app: {
baseURL: PATH_CLIENT, baseURL: CLIENT_PATH,
}, },
}) })

View File

@ -1,13 +1,17 @@
<script lang="ts" setup> <script lang="ts" setup>
const route = useRoute() import { computed, ref } from 'vue'
import { computedAsync } from '@vueuse/core'
import { useRouter } from 'nuxt/app'
import { rpc } from '../composables/rpc'
import { selectedCollection } from '../composables/state'
const router = useRouter() const router = useRouter()
const selectedCollection = ref()
const drawer = ref(false) const drawer = ref(false)
const search = ref('') const search = ref('')
const collections = computedAsync(async () => { const collections = computedAsync(async () => {
return await rpc.listCollections() return await rpc.value?.listCollections()
}) })
const filtered = computed(() => { const filtered = computed(() => {
@ -16,46 +20,48 @@ const filtered = computed(() => {
return collections.value.filter((c: any) => c.name.toLowerCase().includes(search.value.toLowerCase())) return collections.value.filter((c: any) => c.name.toLowerCase().includes(search.value.toLowerCase()))
}) })
onMounted(() => {
if (route.query.table)
selectedCollection.value = route.query.table
})
async function dropCollection(table: any) { async function dropCollection(table: any) {
await rpc.dropCollection(table.name) await rpc.value?.dropCollection(table.name)
collections.value = await rpc.listCollections() collections.value = await rpc.value?.listCollections()
if (selectedCollection.value === table.name) { if (selectedCollection.value === table.name) {
selectedCollection.value = undefined selectedCollection.value = ''
router.push({ name: 'index' }) router.push({ name: 'index' })
} }
} }
async function refresh() { async function refresh() {
collections.value = await rpc.listCollections() collections.value = await rpc.value?.listCollections()
drawer.value = false drawer.value = false
} }
</script> </script>
<template> <template>
<PanelLeftRight :min-left="13" :max-left="20"> <NSplitPane :min-left="13" :max-left="20">
<template #left> <template #left>
<div px4> <div px4>
<Navbar v-model:search="search" :placeholder="`${collections?.length ?? '-'} collection in total`" mt2> <NNavbar v-model:search="search" :placeholder="`${collections?.length ?? '-'} collection in total`" mt2 no-padding>
<div flex items-center gap2> <div flex items-center gap2>
<NIconButton w-full mb1.5 icon="carbon-reset" title="Refresh" @click="refresh" /> <NButton n="blue" w-full mb1.5 icon="carbon-reset" title="Refresh" @click="refresh" />
<NIconButton w-full mb1.5 icon="carbon-data-base" title="Default" text-green-5 /> <!-- <NButton w-full mb1.5 icon="carbon-data-base" title="Default" n="green" /> -->
<NIconButton id="open-drawer-right" w-full mb1.5 icon="carbon-add" title="Create Collection" @click="drawer = true" /> <NButton n="green" w-full mb1.5 icon="carbon-add" title="Create Collection" @click="drawer = true" />
</div> </div>
</Navbar> </NNavbar>
<div grid gird-cols-1 my2 mx1> <div grid gird-cols-1 my2 mx1>
<NuxtLink v-for="table in filtered" :key="table.name" :to="{ name: 'index', query: { table: table.name } }" flex justify-between p2 my1 hover-bg-green hover-bg-opacity-5 hover-text-green rounded-lg :class="{ 'bg-green bg-opacity-5 text-green': selectedCollection === table.name }" @click="selectedCollection = table.name"> <NuxtLink
v-for="table in filtered"
:key="table.name"
:to="{ name: 'index', query: { table: table.name } }"
flex items-center justify-between p2 my1 hover-bg-green hover-bg-opacity-5 hover-text-green rounded-lg
:class="{ 'bg-green bg-opacity-5 text-green': selectedCollection === table.name }"
@click="selectedCollection = table.name"
>
<span> <span>
<NIcon icon="carbon-db2-database" /> <NIcon icon="carbon-db2-database" />
{{ table.name }} {{ table.name }}
</span> </span>
<div flex gap2> <div flex="~ items-center gap-2">
<NIconButton block n="red" icon="carbon-delete" @click="dropCollection(table)" /> <NButton :border="false" n="red" icon="carbon-trash-can" @click="dropCollection(table)" />
<!-- <NIconButton icon="carbon-overflow-menu-horizontal" /> --> <!-- <NButton :border="false" icon="carbon-overflow-menu-horizontal" /> -->
</div> </div>
</NuxtLink> </NuxtLink>
</div> </div>
@ -72,8 +78,8 @@ async function refresh() {
</div> </div>
</div> </div>
</template> </template>
</PanelLeftRight> </NSplitPane>
<DrawerRight v-model="drawer" style="width: calc(80.5%);" auto-close @close="drawer = false"> <NDrawer v-model="drawer" style="width: calc(80.5%);" auto-close @close="drawer = false" z-20>
<CreateResource @refresh="refresh" /> <CreateResource @refresh="refresh" />
</DrawerRight> </NDrawer>
</template> </template>

View File

@ -1,21 +0,0 @@
// TODO install @types/splitpanes once updated
declare module 'splitpanes' {
import { Component } from 'vue'
export interface SplitpaneProps {
horizontal: boolean
pushOtherPanes: boolean
dblClickSplitter: boolean
firstSplitter: boolean
}
export interface PaneProps {
size: number | string
minSize: number | string
maxSize: number | string
}
export type Pane = Component<PaneProps>
export const Pane: Pane
export const Splitpanes: Component<SplitpaneProps>
}

View File

@ -1,35 +0,0 @@
/* Splitpanes */
.splitpanes__splitter {
position: relative;
}
.splitpanes__splitter:before {
position: absolute;
left: 0;
top: 0;
transition: .2s ease;
content: '';
transition: opacity 0.4s;
z-index: 1;
}
.splitpanes__splitter:hover:before {
background: #8881;
opacity: 1;
}
.splitpanes--vertical>.splitpanes__splitter {
min-width: 0 !important;
width: 0 !important;
}
.splitpanes--horizontal>.splitpanes__splitter {
min-height: 0 !important;
height: 0 !important;
}
.splitpanes--vertical>.splitpanes__splitter:before {
left: -5px;
right: -4px;
height: 100%;
}
.splitpanes--horizontal>.splitpanes__splitter:before {
top: -5px;
bottom: -4px;
width: 100%;
}

View File

@ -2,8 +2,9 @@ export default defineAppConfig({
docus: { docus: {
title: 'Nuxt Mongoose', title: 'Nuxt Mongoose',
description: 'A Nuxt module for simplifying the use of Mongoose in your project.', description: 'A Nuxt module for simplifying the use of Mongoose in your project.',
image: 'https://user-images.githubusercontent.com/904724/185365452-87b7ca7b-6030-4813-a2db-5e65c785bf88.png', image: '/cover.jpg',
socials: { socials: {
twitter: 'arash_sheyda',
github: 'arashsheyda/nuxt-mongoose', github: 'arashsheyda/nuxt-mongoose',
}, },
github: { github: {
@ -14,22 +15,16 @@ export default defineAppConfig({
edit: true, edit: true,
}, },
aside: { aside: {
level: 0, level: 1,
collapsed: false, collapsed: false,
exclude: [], exclude: [],
}, },
main: {
padded: true,
fluid: true,
},
header: { header: {
logo: true, logo: true,
showLinkIcon: true, showLinkIcon: true,
exclude: [], exclude: [],
fluid: true,
}, },
footer: { footer: {
fluid: true,
iconLinks: [ iconLinks: [
{ {
href: 'https://nuxt.com', href: 'https://nuxt.com',

View File

@ -0,0 +1,295 @@
<template>
<svg
width="403"
height="226"
viewBox="0 0 403 226"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_1180_138)">
<g opacity="0.54">
<path
d="M0 12C0 5.37259 5.37258 0 12 0H391C397.627 0 403 5.37258 403 12V214C403 220.627 397.627 226 391 226H12C5.37259 226 0 220.627 0 214V12Z"
fill="url(#paint0_linear_1180_138)"
fill-opacity="0.3"
/>
<path
d="M12 0.5H391C397.351 0.5 402.5 5.64873 402.5 12V214C402.5 220.351 397.351 225.5 391 225.5H12C5.64873 225.5 0.5 220.351 0.5 214V12C0.5 5.64873 5.64873 0.5 12 0.5Z"
stroke="url(#paint1_linear_1180_138)"
stroke-opacity="0.4"
/>
</g>
<path
opacity="0.54"
d="M49.7811 28H57.5876C57.8356 28 58.0792 27.9354 58.2939 27.8125C58.5086 27.6896 58.6869 27.5128 58.8108 27.3C58.9347 27.0871 58.9999 26.8457 58.9998 26.5999C58.9997 26.3542 58.9343 26.1128 58.8102 25.9001L53.5676 16.9001C53.4437 16.6873 53.2654 16.5105 53.0507 16.3877C52.836 16.2648 52.5925 16.2001 52.3446 16.2001C52.0967 16.2001 51.8532 16.2648 51.6385 16.3877C51.4238 16.5105 51.2455 16.6873 51.1216 16.9001L49.7811 19.2028L47.1602 14.6998C47.0361 14.487 46.8578 14.3104 46.6431 14.1875C46.4283 14.0647 46.1847 14 45.9368 14C45.6889 14 45.4453 14.0647 45.2305 14.1875C45.0158 14.3104 44.8375 14.487 44.7134 14.6998L38.1896 25.9001C38.0655 26.1128 38.0001 26.3542 38 26.5999C37.9999 26.8457 38.0651 27.0871 38.189 27.3C38.3129 27.5128 38.4912 27.6896 38.7059 27.8125C38.9207 27.9354 39.1643 28 39.4122 28H44.3125C46.254 28 47.6859 27.1547 48.6711 25.5057L51.063 21.4001L52.3442 19.2028L56.1893 25.8028H51.063L49.7811 28ZM44.2326 25.8005L40.8128 25.7998L45.9391 17.0004L48.4969 21.4001L46.7843 24.3407C46.13 25.4107 45.3867 25.8005 44.2326 25.8005Z"
fill="white"
fill-opacity="0.1"
/>
<line
opacity="0.54"
x1="43.5"
y1="75.5"
x2="84.5"
y2="75.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="43.5"
y1="147.5"
x2="63.5"
y2="147.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="172.5"
y1="147.5"
x2="192.899"
y2="147.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="301.5"
y1="147.5"
x2="321.5"
y2="147.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="143.5"
y1="20.5"
x2="167.5"
y2="20.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="184.5"
y1="20.5"
x2="208.5"
y2="20.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="225.5"
y1="20.5"
x2="249.5"
y2="20.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="43.5"
y1="93.5"
x2="178.5"
y2="93.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="43.5"
y1="162.5"
x2="115.544"
y2="162.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="172.5"
y1="162.5"
x2="244.5"
y2="162.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="301.5"
y1="162.5"
x2="373.5"
y2="162.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="43.5"
y1="111.5"
x2="178.5"
y2="111.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="43.5"
y1="177.5"
x2="115.5"
y2="177.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="172.5"
y1="177.5"
x2="244.5"
y2="177.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<line
opacity="0.54"
x1="301.5"
y1="177.5"
x2="373.5"
y2="177.5"
stroke="white"
stroke-opacity="0.1"
stroke-width="5"
stroke-linecap="round"
/>
<rect
opacity="0.54"
x="335.465"
y="17.619"
width="41.1089"
height="11.7454"
rx="2.93635"
fill="white"
fill-opacity="0.1"
/>
<rect
opacity="0.54"
x="212"
y="70"
width="164"
height="44"
rx="5"
fill="url(#paint2_linear_1180_138)"
fill-opacity="0.1"
/>
<!-- Mongo Logo -->
<g transform="translate(170, 80) scale(2)">
<rect width="32" height="32" rx="7.5" style="fill:#023430" />
<path d="M21.4,13.4C20.1,7.6,17.3,6.1,16.6,5a10.1,10.1,0,0,1-.7-1.5,1.6,1.6,0,0,1-.6,1.2,14.1,14.1,0,0,0-4.9,10.5c-.3,6.1,4.5,9.9,5.1,10.3a1.4,1.4,0,0,0,1.4-.2,12.1,12.1,0,0,0,4.5-11.9" style="fill:#10aa50" />
<path d="M16.1,22.2a17.8,17.8,0,0,1-.5,3.3s.2,1.5.3,3h.5a30.6,30.6,0,0,1,.5-3.2C16.3,25,16.1,23.6,16.1,22.2Z" style="fill:#b8c4c2" />
<path d="M16.9,25.3c-.6-.3-.8-1.7-.8-3.1s.2-4.3.1-6.5,0-10.7-.3-12.1L16.6,5c.7,1.1,3.5,2.6,4.8,8.4A12,12,0,0,1,16.9,25.3Z" style="fill:#12924f" />
</g>
</g>
<defs>
<filter
id="filter0_d_1180_138"
x="162.895"
y="74.8947"
width="76.2105"
height="76.2105"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="4" />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.44 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_1180_138"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_1180_138"
result="shape"
/>
</filter>
<linearGradient
id="paint0_linear_1180_138"
x1="201.5"
y1="0"
x2="201.5"
y2="102.822"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_1180_138"
x1="201.5"
y1="-1.34109e-07"
x2="201.527"
y2="153.598"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient
id="paint2_linear_1180_138"
x1="294"
y1="70"
x2="294"
y2="114"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<clipPath id="clip0_1180_138">
<rect width="403" height="226" fill="white" />
</clipPath>
</defs>
</svg>
</template>

View File

@ -1,12 +1,18 @@
<template> <template>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 235.5 33.2"> <svg id="NuxtMongooseLogo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 235.5 33.2">
<rect width="32" height="32" rx="7.5" style="fill:#023430" /> <rect width="32" height="32" rx="7.5" style="fill:#023430" />
<path d="M21.4,13.4C20.1,7.6,17.3,6.1,16.6,5a10.1,10.1,0,0,1-.7-1.5,1.6,1.6,0,0,1-.6,1.2,14.1,14.1,0,0,0-4.9,10.5c-.3,6.1,4.5,9.9,5.1,10.3a1.4,1.4,0,0,0,1.4-.2,12.1,12.1,0,0,0,4.5-11.9" style="fill:#10aa50" /> <path d="M21.4,13.4C20.1,7.6,17.3,6.1,16.6,5a10.1,10.1,0,0,1-.7-1.5,1.6,1.6,0,0,1-.6,1.2,14.1,14.1,0,0,0-4.9,10.5c-.3,6.1,4.5,9.9,5.1,10.3a1.4,1.4,0,0,0,1.4-.2,12.1,12.1,0,0,0,4.5-11.9" style="fill:#10aa50" />
<path d="M16.1,22.2a17.8,17.8,0,0,1-.5,3.3s.2,1.5.3,3h.5a30.6,30.6,0,0,1,.5-3.2C16.3,25,16.1,23.6,16.1,22.2Z" style="fill:#b8c4c2" /> <path d="M16.1,22.2a17.8,17.8,0,0,1-.5,3.3s.2,1.5.3,3h.5a30.6,30.6,0,0,1,.5-3.2C16.3,25,16.1,23.6,16.1,22.2Z" style="fill:#b8c4c2" />
<path d="M16.9,25.3c-.6-.3-.8-1.7-.8-3.1s.2-4.3.1-6.5,0-10.7-.3-12.1L16.6,5c.7,1.1,3.5,2.6,4.8,8.4A12,12,0,0,1,16.9,25.3Z" style="fill:#12924f" /> <path d="M16.9,25.3c-.6-.3-.8-1.7-.8-3.1s.2-4.3.1-6.5,0-10.7-.3-12.1L16.6,5c.7,1.1,3.5,2.6,4.8,8.4A12,12,0,0,1,16.9,25.3Z" style="fill:#12924f" />
<text transform="translate(32.8 25.6)" style="font-size:25.818214416503906px;font-family:OpenSans-Bold, Open Sans;font-weight:700"> <text transform="translate(32.8 25.6)" style="font-size:25.818214416503906px;font-family:var(--font-sans);font-weight:700">
<tspan fill="currentColor" x="4" y="0">Nuxt</tspan> <tspan fill="currentColor" x="4" y="0">Nuxt</tspan>
<tspan x="70.8" y="0" style="fill:#10aa50">Mongoose</tspan> <tspan x="67.8" y="0" style="fill:#10aa50">Mongoose</tspan>
</text> </text>
</svg> </svg>
</template> </template>
<style>
.NuxtMongooseLogo {
font-family: serif;
}
</style>

View File

@ -12,25 +12,67 @@ main:
--- ---
cta: cta:
- Get started - Get started
- /getting-started/setup - /docs/getting-started/setup
secondary: secondary:
- Open on GitHub → - Open on GitHub →
- https://github.com/arashsheyda/nuxt-mongoose - https://github.com/arashsheyda/nuxt-mongoose
--- ---
#title #title
Nuxt Mongoose Nuxt [Mongoose]{style="color: var(--color-primary-500)"}
#description #description
A Nuxt module for simplifying the use of [Mongoose](https://mongoosejs.com/) in your project. A Nuxt module for simplifying the use of [Mongoose](https://mongoosejs.com/) in your project.
#support #support
::terminal :illustration
---
content:
- npm i nuxt-mongoose -D
---
::
:: ::
<!-- TODO: features --> ::card-grid
#title
<h1 class="center">Elevate Your <br/> <span class=highlight> Developer Experience</span></h1>
#root
:ellipsis{left=15rem width=40rem top=10rem blur=140px}
#default
::card{icon=tabler:code}
#title
Nuxt DevTools Support
#description
Dive into your database with confidence. Benefit from Nuxt Devtools support, allowing you to inspect and debug your DB operations seamlessly.
::
::card{icon=tabler:cloud-bolt}
#title
Auto Connection
#description
Forget about manual connection hassles. This module seamlessly handles the connection to your Mongoose database, making setup a breeze.
::
::card{icon=tabler:plug}
#title
Extendable & hackable
#description
This module grants you access to the full spectrum of Mongoose functionalities. Leverage the power of Mongoose in your project.
::
::
<style>
.highlight {
color: var(--color-primary-500);
}
.center {
text-align: center;
}
.card svg {
height: 3rem!important;
width: 3rem!important;
}
.support {
display: flex;
justify-content: center;
}
</style>

View File

@ -40,7 +40,7 @@ export default defineNuxtConfig({
## Options ## Options
You can configure the module by adding a `mongoose` section to your `nuxt.config` file. You can configure the module by adding a `mongoose` section to your `nuxt.config` file.
read more about [Mongoose options](/getting-started/configuration). read more about [Mongoose options](/docs/getting-started/configuration).
```ts [nuxt.config] ```ts [nuxt.config]
export default defineNuxtConfig({ export default defineNuxtConfig({
@ -49,3 +49,8 @@ export default defineNuxtConfig({
}, },
}) })
``` ```
If you want to configure only the `uri` just add `MONGODB_URI` in your `.env` file.
```env
MONGODB_URI=YOUR_MONGO_URI
```

View File

@ -12,13 +12,17 @@ This function creates a new Mongoose model with schema. Example usage:
export const User = defineMongooseModel({ export const User = defineMongooseModel({
name: 'User', name: 'User',
schema: { schema: {
name: { email: {
type: String, type: 'string',
required: true, required: true,
unique: true,
}, },
}, },
options: { options: {
},
hooks(schema) {
}, },
}) })
``` ```
@ -27,18 +31,36 @@ This function creates a new Mongoose model with schema. Example usage:
import { defineMongooseModel } from '#nuxt/mongoose' import { defineMongooseModel } from '#nuxt/mongoose'
export const User = defineMongooseModel('User', { export const User = defineMongooseModel('User', {
name: { email: {
type: String, type: 'string',
required: true, required: true,
unique: true,
}, },
}, { }, {
}, (schema) => {
}) })
``` ```
:: ::
| **Key** | **Type** | **Require** | **Description** |
| ---------------------------- | ----------- | ----------- | ----- |
| `name` | `string` | true | Name of Model |
| `schema` | [`SchemaDefinition`](https://mongoosejs.com/docs/schematypes.html) | true | Schema Definition of Model |
| `options` | [`SchemaOptions`](https://mongoosejs.com/docs/guide.html#options) | false | Schema Options for Model |
| `hooks` | [`(schema: Schema<T>) => void`](https://mongoosejs.com/docs/middleware.html) | false | Schema Hooks Function to customize Model |
::alert
you can access the default connection with importing it from mongoose:
::
```
import { connection } from 'mongoose'
```
## `defineMongooseConnection` ## `defineMongooseConnection`
This function creates a new Mongoose connection. This function creates a new Mongoose connection.
- `nuxt-mongoose` provides a default connection for you, it auto-register a plugin in nitro, so you don't need to use this function unless you want to create a new connection. more info [here](https://github.com/arashsheyda/nuxt-mongoose/blob/main/src/runtime/server/plugins/mongoose.db.ts). - `nuxt-mongoose` provides a default connection for you, it auto-register a plugin in nitro, so you don't need to use this function unless you want to create a new connection. more info [here](https://github.com/arashsheyda/nuxt-mongoose/blob/main/src/runtime/server/plugins/mongoose.db.ts).

View File

@ -1,3 +1,8 @@
# Devtools (beta) # Devtools (beta)
`nuxt-mongoose` comes with a [Nuxt Devtools](https://github.com/nuxt/devtools) module that allows you to manage your collections and generate api-endpoints & schemas... `nuxt-mongoose` comes with a [Nuxt Devtools](https://github.com/nuxt/devtools) module that allows you to manage your collections and generate api-endpoints & schemas...
Here is a demo video:
:video-player{src="https://www.youtube.com/watch?v=hK0npSfr_Vs"}

View File

@ -0,0 +1,5 @@
# Examples
Here are a few examples:
- Nuxt Mongoose Minimal: [:icon{name="tabler:brand-github"} Github](https://github.com/arashsheyda/nuxt-mongoose-minimal) [:icon{name="tabler:world"} Website](https://nuxt-mongoose-minimal.vercel.app/)

View File

@ -0,0 +1 @@
icon: tabler:file-description

View File

@ -0,0 +1,5 @@
# Playground
:ellipsis{right=0px width=75% blur=150px}
:sandbox{src="https://stackblitz.com/github/arashsheyda/nuxt-mongoose-minimal"}

View File

@ -0,0 +1 @@
icon: tabler:play-volleyball

View File

@ -0,0 +1,3 @@
title: Releases
icon: tabler:clipboard-text
navigation.redirect: https://github.com/arashsheyda/nuxt-mongoose/releases

View File

View File

@ -1,8 +1,19 @@
export default defineNuxtConfig({ export default defineNuxtConfig({
extends: '@nuxt-themes/docus', extends: '@nuxt-themes/docus',
app: {
head: {
link: [
{
rel: 'icon',
type: 'image/x-icon',
href: '/mongoose-icon.svg',
},
],
},
},
modules: [ modules: [
'@nuxthq/studio', '@nuxthq/studio',
'@nuxt/devtools',
], ],
}) })

View File

@ -10,8 +10,8 @@
"lint": "eslint ." "lint": "eslint ."
}, },
"devDependencies": { "devDependencies": {
"@nuxt-themes/docus": "^1.12.0", "@nuxt-themes/docus": "^1.15.0",
"@nuxthq/studio": "^0.13.2", "@nuxthq/studio": "^1.0.6",
"nuxt": "^3.5.0" "nuxt": "^3.9.1"
} }
} }

7608
docs/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
docs/public/cover.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,15 +0,0 @@
/* eslint-disable eslint-comments/no-unlimited-disable */
/* eslint-disable */
module.exports = function (...args) {
const nuxt = this.nuxt || args[1]
let _a
let version = (nuxt == null ? void 0 : nuxt._version) || (nuxt == null ? void 0 : nuxt.version) || ((_a = nuxt == null ? void 0 : nuxt.constructor) == null ? void 0 : _a.version) || ''
version = version.replace(/^v/g, '')
// Nuxt DevTools is not compatible with Nuxt 2, disabled
if (version.startsWith('2.')) {
return
}
return import('./dist/module.mjs').then(m => m.default.call(this, ...args))
}
const _meta = module.exports.meta = require('./dist/module.json')
module.exports.getMeta = () => Promise.resolve(_meta)

View File

@ -1,72 +1,74 @@
{ {
"name": "nuxt-mongoose", "name": "nuxt-mongoose",
"type": "module", "type": "module",
"version": "0.0.7", "version": "1.0.3",
"private": false,
"packageManager": "pnpm@8.7.4",
"description": "Nuxt 3 module for MongoDB with Mongoose", "description": "Nuxt 3 module for MongoDB with Mongoose",
"license": "MIT", "license": "MIT",
"funding": "https://github.com/sponsors/arashsheyda",
"homepage": "https://nuxt-mongoose.nuxt.space",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/arashsheyda/nuxt-mongoose" "url": "git+https://github.com/arashsheyda/nuxt-mongoose"
}, },
"bugs": {
"url": "https://github.com/arashsheyda/nuxt-mongoose/issues"
},
"keywords": [
"nuxt",
"mongoose",
"mongodb",
"devtools"
],
"exports": { "exports": {
".": { ".": {
"types": "./dist/types.d.ts", "types": "./dist/types.d.ts",
"require": "./module.cjs", "import": "./dist/module.mjs",
"import": "./dist/module.mjs" "require": "./dist/module.cjs"
}, }
"./types": {
"types": "./dist/types.d.ts",
"import": "./dist/types.mjs"
},
"./*": "./*"
}, },
"main": "./module.cjs", "build": {
"externals": [
"ofetch"
]
},
"main": "./dist/module.cjs",
"types": "./dist/types.d.ts", "types": "./dist/types.d.ts",
"files": [ "files": [
"dist" "dist"
], ],
"scripts": { "scripts": {
"build": "pnpm dev:prepare && pnpm build:module && pnpm build:client", "build": "nuxt-module-build && npm run build:client",
"build:client": "nuxi generate client", "build:client": "nuxi generate client",
"build:module": "nuxt-build-module",
"dev": "nuxi dev playground", "dev": "nuxi dev playground",
"dev:prepare": "nuxi prepare client", "dev:prepare": "nuxt-module-build && nuxi prepare client",
"dev:prod": "npm run build && pnpm dev", "dev:client": "nuxi dev client --port 3300",
"release": "npm run lint && npm run build && changelogen --release && npm publish && git push --follow-tags", "dev:prod": "npm run build && npm run dev",
"lint": "eslint .", "release": "npm run lint && npm run build && changelogen --release && npm publish",
"test": "vitest run", "lint": "eslint . --fix"
"test:watch": "vitest watch"
}, },
"dependencies": { "dependencies": {
"@nuxt/devtools-kit": "^0.5.5", "@nuxt/devtools-kit": "1.0.0",
"@nuxt/kit": "^3.5.2", "@nuxt/devtools-ui-kit": "1.0.0",
"@types/fs-extra": "^11.0.1", "@nuxt/kit": "^3.9.0",
"birpc": "^0.2.11", "@vueuse/core": "^10.7.1",
"defu": "^6.1.2", "defu": "^6.1.3",
"flatted": "^3.2.7", "fs-extra": "^11.2.0",
"fs-extra": "^11.1.1", "mongoose": "^7.6.7",
"mongoose": "^7.2.2", "ofetch": "^1.3.3",
"pathe": "^1.1.0", "pathe": "^1.1.1",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"sirv": "^2.0.3", "sirv": "^2.0.4"
"tinyws": "^0.1.0",
"ws": "^8.13.0"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.38.5", "@antfu/eslint-config": "1.0.0-beta.29",
"@nuxt/devtools": "^0.5.5", "@nuxt/module-builder": "^0.5.5",
"@nuxt/devtools-ui-kit": "^0.5.5", "@types/fs-extra": "^11.0.4",
"@nuxt/module-builder": "^0.4.0", "@types/pluralize": "^0.0.32",
"@nuxt/schema": "^3.5.2", "changelogen": "^0.5.5",
"@nuxt/test-utils": "^3.5.2", "eslint": "8.52.0",
"@types/pluralize": "^0.0.29", "nuxt": "^3.9.0",
"@types/ws": "^8.5.4", "sass": "^1.69.7"
"changelogen": "^0.5.3",
"eslint": "^8.39.0",
"nuxt": "^3.5.2",
"sass": "^1.62.1",
"sass-loader": "^13.3.1",
"splitpanes": "^3.1.5",
"vitest": "^0.31.2"
} }
} }

1
playground/.env.example Normal file
View File

@ -0,0 +1 @@
MONGODB_URI="mongodb://localhost:27017/nuxt-mongoose"

View File

@ -1,6 +1,33 @@
import { resolve } from 'node:path'
import { defineNuxtConfig } from 'nuxt/config'
import { defineNuxtModule } from '@nuxt/kit'
import { startSubprocess } from '@nuxt/devtools-kit'
import { CLIENT_PORT } from '../src/constants'
export default defineNuxtConfig({ export default defineNuxtConfig({
devtools: {
enabled: true,
},
modules: [ modules: [
'@nuxt/devtools',
'../src/module', '../src/module',
defineNuxtModule({
setup(_, nuxt) {
if (!nuxt.options.dev)
return
startSubprocess(
{
command: 'npx',
args: ['nuxi', 'dev', '--port', CLIENT_PORT.toString()],
cwd: resolve(__dirname, '../client'),
},
{
id: 'nuxt-mongoose:client',
name: 'Nuxt Mongoose Client Dev',
},
)
},
}),
], ],
}) })

View File

@ -0,0 +1,5 @@
export default defineEventHandler(async () => {
return {
message: 'hi',
}
})

View File

@ -7,10 +7,20 @@ export const UserSchema = defineMongooseModel({
type: 'string', type: 'string',
required: true, required: true,
}, },
slug: { email: {
type: 'string',
required: true,
unique: false,
},
password: {
type: 'string', type: 'string',
required: true, required: true,
unique: true,
}, },
}, },
hooks(schema) {
schema.pre('save', function (this, next) {
this.password = `hash.${this.password}.${Math.random()}`
next()
})
},
}) })

View File

@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

10987
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
export const PATH = '/__nuxt_mongoose__' export const CLIENT_PATH = '/__nuxt-mongoose'
export const PATH_ENTRY = `${PATH}/entry` export const CLIENT_PORT = 3300
export const PATH_CLIENT = `${PATH}/client` export const RPC_NAMESPACE = 'nuxt-mongoose-rpc'

56
src/devtools.ts Normal file
View File

@ -0,0 +1,56 @@
import { existsSync } from 'node:fs'
import type { Nuxt } from 'nuxt/schema'
import type { Resolver } from '@nuxt/kit'
import { extendServerRpc, onDevToolsInitialized } from '@nuxt/devtools-kit'
import type { ClientFunctions, ServerFunctions } from './types'
import type { ModuleOptions } from './module'
import { useViteWebSocket } from './utils'
import { setupRPC } from './rpc'
import { CLIENT_PATH, CLIENT_PORT, RPC_NAMESPACE } from './constants'
export function setupDevToolsUI(options: ModuleOptions, resolve: Resolver['resolve'], nuxt: Nuxt) {
const clientPath = resolve('./client')
const isProductionBuild = existsSync(clientPath)
if (isProductionBuild) {
nuxt.hook('vite:serverCreated', async (server) => {
const sirv = await import('sirv').then(r => r.default || r)
server.middlewares.use(
CLIENT_PATH,
sirv(clientPath, { dev: true, single: true }),
)
})
}
else {
nuxt.hook('vite:extendConfig', (config) => {
config.server = config.server || {}
config.server.proxy = config.server.proxy || {}
config.server.proxy[CLIENT_PATH] = {
target: `http://localhost:${CLIENT_PORT}${CLIENT_PATH}`,
changeOrigin: true,
followRedirects: true,
rewrite: path => path.replace(CLIENT_PATH, ''),
}
})
}
nuxt.hook('devtools:customTabs', (tabs) => {
tabs.push({
name: 'nuxt-mongoose',
title: 'Mongoose',
icon: 'skill-icons:mongodb',
view: {
type: 'iframe',
src: CLIENT_PATH,
},
})
})
const wsServer = useViteWebSocket(nuxt)
onDevToolsInitialized(async () => {
const rpcFunctions = setupRPC({ options, wsServer, nuxt })
extendServerRpc<ClientFunctions, ServerFunctions>(RPC_NAMESPACE, rpcFunctions)
})
}

View File

@ -1,23 +1,47 @@
import { import {
addImportsDir,
addServerPlugin, addServerPlugin,
addTemplate, addTemplate,
createResolver, createResolver,
defineNuxtModule, defineNuxtModule,
logger, logger,
} from '@nuxt/kit' } from '@nuxt/kit'
import { pathExists } from 'fs-extra' import type { ConnectOptions } from 'mongoose'
import { tinyws } from 'tinyws' import defu from 'defu'
import { join } from 'pathe' import { join } from 'pathe'
import { defu } from 'defu' import mongoose from 'mongoose'
import sirv from 'sirv' import { $fetch } from 'ofetch'
import { version } from '../package.json'
import { setupDevToolsUI } from './devtools'
import { PATH_CLIENT, PATH_ENTRY } from './constants' export interface ModuleOptions {
import type { ModuleOptions } from './types' /**
* The MongoDB URI connection
import { setupRPC } from './server-rpc' *
* @default process.env.MONGODB_URI
export type { ModuleOptions } *
*/
uri: string | undefined
/**
* Nuxt DevTools
*
* @default true
*
*/
devtools: boolean
/**
* Mongoose Connections
*
* @default {}
*/
options?: ConnectOptions
/**
* Models Directory for auto-import
*
* @default 'models'
*
*/
modelsDir?: string
}
export default defineNuxtModule<ModuleOptions>({ export default defineNuxtModule<ModuleOptions>({
meta: { meta: {
@ -25,71 +49,54 @@ export default defineNuxtModule<ModuleOptions>({
configKey: 'mongoose', configKey: 'mongoose',
}, },
defaults: { defaults: {
// eslint-disable-next-line n/prefer-global/process
uri: process.env.MONGODB_URI as string, uri: process.env.MONGODB_URI as string,
devtools: true, devtools: true,
options: {}, options: {},
modelsDir: 'models', modelsDir: 'models',
}, },
setup(options, nuxt) { hooks: {
const { resolve } = createResolver(import.meta.url) close: () => {
const runtimeConfig = nuxt.options.runtimeConfig mongoose.disconnect()
},
addImportsDir(resolve('./runtime/composables')) },
async setup(options, nuxt) {
if (nuxt.options.dev) {
$fetch('https://registry.npmjs.org/nuxt-mongoose/latest').then((release) => {
if (release.version > version)
logger.info(`A new version of Nuxt Mongoose (v${release.version}) is available: https://github.com/arashsheyda/nuxt-mongoose/releases/latest`)
}).catch(() => {})
}
if (!options.uri) { if (!options.uri) {
logger.warn('Missing `MONGODB_URI` in `.env`') logger.warn('Missing MongoDB URI. You can set it in your `nuxt.config` or in your `.env` as `MONGODB_URI`')
return return
} }
// Runtime Config const { resolve } = createResolver(import.meta.url)
runtimeConfig.mongoose = defu(runtimeConfig.mongoose || {}, { const config = nuxt.options.runtimeConfig as any
config.mongoose = defu(config.mongoose || {}, {
uri: options.uri, uri: options.uri,
options: options.options, options: options.options,
devtools: options.devtools, devtools: options.devtools,
modelsDir: options.modelsDir, modelsDir: join(nuxt.options.serverDir, options.modelsDir!),
})
// Setup devtools UI
const distResolve = (p: string) => {
const cwd = resolve('.')
if (cwd.endsWith('/dist'))
return resolve(p)
return resolve(`../dist/${p}`)
}
const clientPath = distResolve('./client')
const { middleware: rpcMiddleware } = setupRPC(nuxt, options)
nuxt.hook('vite:serverCreated', async (server) => {
server.middlewares.use(PATH_ENTRY, tinyws() as any)
server.middlewares.use(PATH_ENTRY, rpcMiddleware as any)
if (await pathExists(clientPath))
server.middlewares.use(PATH_CLIENT, sirv(clientPath, { dev: true, single: true }))
})
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore runtime type
nuxt.hook('devtools:customTabs', (iframeTabs) => {
iframeTabs.push({
name: 'mongoose',
title: 'Mongoose',
icon: 'skill-icons:mongodb',
view: {
type: 'iframe',
src: PATH_CLIENT,
},
})
}) })
// virtual imports // virtual imports
nuxt.hook('nitro:config', (nitroConfig) => { nuxt.hook('nitro:config', (_config) => {
nitroConfig.alias = nitroConfig.alias || {} _config.alias = _config.alias || {}
// Inline module runtime in Nitro bundle // Inline module runtime in Nitro bundle
nitroConfig.externals = defu(typeof nitroConfig.externals === 'object' ? nitroConfig.externals : {}, { _config.externals = defu(typeof _config.externals === 'object' ? _config.externals : {}, {
inline: [resolve('./runtime')], inline: [resolve('./runtime')],
}) })
nitroConfig.alias['#nuxt/mongoose'] = resolve('./runtime/server/services') _config.alias['#nuxt/mongoose'] = resolve('./runtime/server/services')
if (_config.imports) {
_config.imports.dirs = _config.imports.dirs || []
_config.imports.dirs?.push(config.mongoose.modelsDir)
}
}) })
addTemplate({ addTemplate({
@ -106,15 +113,9 @@ export default defineNuxtModule<ModuleOptions>({
options.references.push({ path: resolve(nuxt.options.buildDir, 'types/nuxt-mongoose.d.ts') }) options.references.push({ path: resolve(nuxt.options.buildDir, 'types/nuxt-mongoose.d.ts') })
}) })
// Nitro auto imports const isDevToolsEnabled = typeof nuxt.options.devtools === 'boolean' ? nuxt.options.devtools : nuxt.options.devtools.enabled
nuxt.hook('nitro:config', (_nitroConfig) => { if (nuxt.options.dev && isDevToolsEnabled)
if (_nitroConfig.imports) { setupDevToolsUI(options, resolve, nuxt)
_nitroConfig.imports.dirs = _nitroConfig.imports.dirs || []
_nitroConfig.imports.dirs?.push(
join(nuxt.options.serverDir, runtimeConfig.mongoose.modelsDir),
)
}
})
// Add server-plugin for database connection // Add server-plugin for database connection
addServerPlugin(resolve('./runtime/server/plugins/mongoose.db')) addServerPlugin(resolve('./runtime/server/plugins/mongoose.db'))

View File

@ -1,9 +1,8 @@
import mongoose from 'mongoose' import mongoose from 'mongoose'
import type { NuxtDevtoolsServerContext, ServerFunctions } from '../types' import type { DevtoolsServerContext, ServerFunctions } from '../types'
export function setupDatabaseRPC({ options }: NuxtDevtoolsServerContext): any {
mongoose.connect(options.uri, options.options)
// eslint-disable-next-line no-empty-pattern
export function setupDatabaseRPC({}: DevtoolsServerContext) {
return { return {
async readyState() { async readyState() {
return mongoose.connection.readyState return mongoose.connection.readyState
@ -62,8 +61,8 @@ export function setupDatabaseRPC({ options }: NuxtDevtoolsServerContext): any {
const skip = (options.page - 1) * options.limit const skip = (options.page - 1) * options.limit
const cursor = mongoose.connection.db.collection(collection).find().skip(skip) const cursor = mongoose.connection.db.collection(collection).find().skip(skip)
if (options.limit !== 0) if (options.limit !== 0)
cursor.limit(options.limit) cursor?.limit(options.limit)
return await cursor.toArray() return await cursor?.toArray()
}, },
async getDocument(collection: string, document: any) { async getDocument(collection: string, document: any) {
try { try {

23
src/rpc/index.ts Normal file
View File

@ -0,0 +1,23 @@
import mongoose from 'mongoose'
import type { DevtoolsServerContext, ServerFunctions } from '../types'
import { setupDatabaseRPC } from './database'
import { setupResourceRPC } from './resource'
export function setupRPC(ctx: DevtoolsServerContext): ServerFunctions {
mongoose.connect(ctx.options.uri, ctx.options.options)
return {
getOptions() {
return ctx.options
},
...setupDatabaseRPC(ctx),
...setupResourceRPC(ctx),
async reset() {
const ws = await ctx.wsServer
ws.send('nuxt-mongoose:reset')
},
}
}

View File

@ -1,21 +1,22 @@
import fs from 'fs-extra' import fs from 'fs-extra'
import { resolve } from 'pathe' import { join } from 'pathe'
import type { Collection, NuxtDevtoolsServerContext, Resource, ServerFunctions } from '../types' import mongoose from 'mongoose'
import { generateApiRoute, generateSchemaFile } from '../utils/schematics' import type { Collection, DevtoolsServerContext, Resource, ServerFunctions } from '../types'
import { capitalize, pluralize, singularize } from '../utils/formatting' import { capitalize, generateApiRoute, generateSchemaFile, pluralize, singularize } from '../utils'
export function setupResourceRPC({ nuxt }: DevtoolsServerContext): any {
const config = nuxt.options.runtimeConfig.mongoose
export function setupResourceRPC({ nuxt, rpc }: NuxtDevtoolsServerContext): any {
return { return {
// TODO: maybe separate functions
async generateResource(collection: Collection, resources: Resource[]) { async generateResource(collection: Collection, resources: Resource[]) {
const singular = singularize(collection.name).toLowerCase() const singular = singularize(collection.name).toLowerCase()
const plural = pluralize(collection.name).toLowerCase() const plural = pluralize(collection.name).toLowerCase()
const dbName = capitalize(singular) const dbName = capitalize(singular)
if (collection.fields) { if (collection.fields) {
const schemaPath = resolve(nuxt.options.serverDir, 'utils/models', `${singular}.schema.ts`) const schemaPath = join(config.modelsDir, `${singular}.schema.ts`)
if (!fs.existsSync(schemaPath)) { if (!fs.existsSync(schemaPath)) {
fs.ensureDirSync(resolve(nuxt.options.serverDir, 'utils/models')) fs.ensureDirSync(config.modelsDir)
fs.writeFileSync(schemaPath, generateSchemaFile(dbName, collection.fields)) fs.writeFileSync(schemaPath, generateSchemaFile(dbName, collection.fields))
} }
@ -34,9 +35,9 @@ export function setupResourceRPC({ nuxt, rpc }: NuxtDevtoolsServerContext): any
? (routeTypes[route.type] as any)(route.by) ? (routeTypes[route.type] as any)(route.by)
: routeTypes[route.type] : routeTypes[route.type]
const filePath = resolve(nuxt.options.serverDir, 'api', plural, fileName) const filePath = join(nuxt.options.serverDir, 'api', plural, fileName)
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
fs.ensureDirSync(resolve(nuxt.options.serverDir, `api/${plural}`)) fs.ensureDirSync(join(nuxt.options.serverDir, `api/${plural}`))
const content = generateApiRoute(route.type, { model, by: route.by }) const content = generateApiRoute(route.type, { model, by: route.by })
fs.writeFileSync(filePath, content) fs.writeFileSync(filePath, content)
} }
@ -44,14 +45,13 @@ export function setupResourceRPC({ nuxt, rpc }: NuxtDevtoolsServerContext): any
} }
// create collection if not exists // create collection if not exists
const collections = await rpc.functions.listCollections() const collections = await mongoose.connection.db.listCollections().toArray()
if (!collections.find((c: any) => c.name === plural)) if (!collections.find((c: any) => c.name === plural))
await rpc.functions.createCollection(plural) return await mongoose.connection.db.createCollection(plural)
}, },
async resourceSchema(collection: string) { async resourceSchema(collection: string) {
// TODO: use magicast
const singular = singularize(collection).toLowerCase() const singular = singularize(collection).toLowerCase()
const schemaPath = resolve(nuxt.options.serverDir, 'utils/models', `${singular}.schema.ts`) const schemaPath = join(config.modelsDir, `${singular}.schema.ts`)
if (fs.existsSync(schemaPath)) { if (fs.existsSync(schemaPath)) {
const content = fs.readFileSync(schemaPath, 'utf-8').match(/schema: \{(.|\n)*\}/g) const content = fs.readFileSync(schemaPath, 'utf-8').match(/schema: \{(.|\n)*\}/g)
if (content) { if (content) {

View File

@ -1,8 +1,8 @@
/** /**
* Due to an upstream bug in Nuxt 3 we need to stub the plugin here, track:https://github.com/nuxt/nuxt/issues/18556 * Due to an upstream bug in Nuxt 3 we need to stub the plugin here, track:https://github.com/nuxt/nuxt/issues/18556
* */ */
import type { NitroApp } from 'nitropack' import type { NitroApp } from 'nitropack'
import { defineMongooseConnection } from '../services/mongoose' import { defineMongooseConnection } from '../services'
type NitroAppPlugin = (nitro: NitroApp) => void type NitroAppPlugin = (nitro: NitroApp) => void

View File

@ -1 +1,49 @@
export { defineMongooseConnection, defineMongooseModel } from './mongoose' import { logger } from '@nuxt/kit'
import mongoose from 'mongoose'
import type { ConnectOptions, Model, SchemaDefinition, SchemaOptions } from 'mongoose'
import { useRuntimeConfig } from '#imports'
export async function defineMongooseConnection({ uri, options }: { uri?: string; options?: ConnectOptions } = {}): Promise<void> {
const config = useRuntimeConfig().mongoose
const mongooseUri = uri || config.uri
const mongooseOptions = options || config.options
try {
await mongoose.connect(mongooseUri, { ...mongooseOptions })
logger.success('Connected to `MongoDB`')
}
catch (err) {
logger.error('Error connecting to `MongoDB`', err)
}
}
export function defineMongooseModel<T>(
nameOrOptions: string | {
name: string
schema: SchemaDefinition<T>
options?: SchemaOptions
hooks?: (schema: mongoose.Schema<T>) => void
},
schema?: SchemaDefinition<T>,
options?: SchemaOptions,
hooks?: (schema: mongoose.Schema<T>) => void,
): Model<T> {
let name: string
if (typeof nameOrOptions === 'string') {
name = nameOrOptions
}
else {
name = nameOrOptions.name
schema = nameOrOptions.schema
options = nameOrOptions.options
hooks = nameOrOptions.hooks
}
const newSchema = new mongoose.Schema<T>(schema, options as any)
if (hooks)
hooks(newSchema)
return mongoose.model<T>(name, newSchema)
}

View File

@ -1,36 +0,0 @@
import type { ConnectOptions, Model, SchemaDefinition, SchemaOptions } from 'mongoose'
import mongoose from 'mongoose'
import { logger } from '@nuxt/kit'
import { useRuntimeConfig } from '#imports'
export async function defineMongooseConnection({ uri, options }: { uri?: string; options?: ConnectOptions } = {}): Promise<void> {
const config = useRuntimeConfig().mongoose
const mongooseUri = uri || config.uri
const mongooseOptions = options || config.options
try {
await mongoose.connect(mongooseUri, { ...mongooseOptions })
logger.info('Connected to mongoose database')
}
catch (err) {
logger.error('Error connecting to database', err)
}
}
export function defineMongooseModel(nameOrOptions: string | { name: string; schema: SchemaDefinition; options?: SchemaOptions }, schema?: SchemaDefinition, options?: SchemaOptions): Model<any> {
let name: string
if (typeof nameOrOptions === 'string') {
name = nameOrOptions
}
else {
name = nameOrOptions.name
schema = nameOrOptions.schema
options = nameOrOptions.options
}
const newSchema = new mongoose.Schema({
...schema,
}, { ...options })
return mongoose.model(name, newSchema)
}

View File

@ -1,103 +0,0 @@
import type { TinyWSRequest } from 'tinyws'
import type { NodeIncomingMessage, NodeServerResponse } from 'h3'
import type { WebSocket } from 'ws'
import { createBirpcGroup } from 'birpc'
import type { ChannelOptions } from 'birpc'
import { parse, stringify } from 'flatted'
import type { Nuxt } from 'nuxt/schema'
import type { ClientFunctions, ModuleOptions, NuxtDevtoolsServerContext, ServerFunctions } from '../types'
import { setupDatabaseRPC } from './database'
import { setupResourceRPC } from './resource'
export function setupRPC(nuxt: Nuxt, options: ModuleOptions): any {
const serverFunctions = {} as ServerFunctions
const extendedRpcMap = new Map<string, any>()
const rpc = createBirpcGroup<ClientFunctions, ServerFunctions>(
serverFunctions,
[],
{
resolver: (name, fn) => {
if (fn)
return fn
if (!name.includes(':'))
return
const [namespace, fnName] = name.split(':')
return extendedRpcMap.get(namespace)?.[fnName]
},
onError(error, name) {
console.error(`[nuxt-devtools] RPC error on executing "${name}":`, error)
},
},
)
function refresh(event: keyof ServerFunctions) {
rpc.broadcast.refresh.asEvent(event)
}
function extendServerRpc(namespace: string, functions: any): any {
extendedRpcMap.set(namespace, functions)
return {
broadcast: new Proxy({}, {
get: (_, key) => {
if (typeof key !== 'string')
return
return (rpc.broadcast as any)[`${namespace}:${key}`]
},
}),
}
}
const ctx: NuxtDevtoolsServerContext = {
nuxt,
options,
rpc,
refresh,
extendServerRpc,
}
// @ts-expect-error untyped
nuxt.devtools = ctx
Object.assign(serverFunctions, {
...setupDatabaseRPC(ctx),
...setupResourceRPC(ctx),
} satisfies Partial<ServerFunctions>)
const wsClients = new Set<WebSocket>()
const middleware = async (req: NodeIncomingMessage & TinyWSRequest, _res: NodeServerResponse, next: Function) => {
// Handle WebSocket
if (req.ws) {
const ws = await req.ws()
wsClients.add(ws)
const channel: ChannelOptions = {
post: d => ws.send(d),
on: fn => ws.on('message', fn),
serialize: stringify,
deserialize: parse,
}
rpc.updateChannels((c) => {
c.push(channel)
})
ws.on('close', () => {
wsClients.delete(ws)
rpc.updateChannels((c) => {
const index = c.indexOf(channel)
if (index >= 0)
c.splice(index, 1)
})
})
}
else {
next()
}
}
return {
middleware,
...ctx,
}
}

View File

@ -1,3 +1,47 @@
export * from './rpc' import type { Nuxt } from 'nuxt/schema'
export * from './server-ctx' import type { WebSocketServer } from 'vite'
export * from './module-options' import type { ModuleOptions } from '../module'
export interface ServerFunctions {
getOptions(): ModuleOptions
// Database - collections
readyState(): Promise<any>
createCollection(name: string): Promise<any>
listCollections(): Promise<any>
getCollection(name: string): Promise<any>
dropCollection(name: string): Promise<any>
// Database - documents
createDocument(collection: string, data: any): Promise<any>
countDocuments(collection: string): Promise<any>
listDocuments(collection: string, options: any): Promise<any>
getDocument(collection: string, id: string): Promise<any>
updateDocument(collection: string, data: any): Promise<any>
deleteDocument(collection: string, id: string): Promise<any>
// Resource - api-routes & models
generateResource(collection: Collection, resources: Resource[]): Promise<any>
resourceSchema(collection: string): Promise<any>
reset(): void
}
export interface ClientFunctions {
}
export interface DevtoolsServerContext {
nuxt: Nuxt
options: ModuleOptions
wsServer: Promise<WebSocketServer>
}
export interface Collection {
name: string
fields?: object[]
}
export interface Resource {
type: 'index' | 'create' | 'show' | 'put' | 'delete'
by?: string
}

View File

@ -1,8 +0,0 @@
import type { ConnectOptions } from 'mongoose'
export interface ModuleOptions {
uri: string
devtools: boolean
options?: ConnectOptions
modelsDir?: string
}

View File

@ -1,34 +0,0 @@
export interface ServerFunctions {
// Database - collections
readyState(): Promise<any>
createCollection(name: string): Promise<any>
listCollections(): Promise<any>
getCollection(name: string): Promise<any>
dropCollection(name: string): Promise<any>
// Database - documents
createDocument(collection: string, data: any): Promise<any>
countDocuments(collection: string): Promise<any>
listDocuments(collection: string, options: any): Promise<any>
getDocument(collection: string, id: string): Promise<any>
updateDocument(collection: string, data: any): Promise<any>
deleteDocument(collection: string, id: string): Promise<any>
// Resource - api-routes & models
generateResource(collection: Collection, resources: Resource[]): Promise<any>
resourceSchema(collection: string): Promise<any>
}
export interface ClientFunctions {
refresh(type: string): void
}
export interface Collection {
name: string
fields?: {}[]
}
export interface Resource {
type: 'index' | 'create' | 'show' | 'put' | 'delete'
by?: string
}

View File

@ -1,15 +0,0 @@
import type { BirpcGroup } from 'birpc'
import type { Nuxt } from 'nuxt/schema'
import type { ClientFunctions, ServerFunctions } from './rpc'
import type { ModuleOptions } from './module-options'
export interface NuxtDevtoolsServerContext {
nuxt: Nuxt
options: ModuleOptions
rpc: BirpcGroup<ClientFunctions, ServerFunctions>
refresh: (event: keyof ServerFunctions) => void
extendServerRpc: <ClientFunctions = {}, ServerFunctions = {}>(name: string, functions: ServerFunctions) => BirpcGroup<ClientFunctions, ServerFunctions>
}

View File

@ -1,22 +0,0 @@
import plrz from 'pluralize'
export function normalizeToKebabOrSnakeCase(str: string) {
const STRING_DASHERIZE_REGEXP = /\s/g
const STRING_DECAMELIZE_REGEXP = /([a-z\d])([A-Z])/g
return str
.replace(STRING_DECAMELIZE_REGEXP, '$1-$2')
.toLowerCase()
.replace(STRING_DASHERIZE_REGEXP, '-')
}
export function pluralize(str: string) {
return plrz.plural(str)
}
export function singularize(str: string) {
return plrz.singular(str)
}
export function capitalize(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1)
}

View File

@ -1,4 +1,35 @@
import { capitalize } from './formatting' import type { WebSocketServer } from 'vite'
import type { Nuxt } from 'nuxt/schema'
import plrz from 'pluralize'
export function useViteWebSocket(nuxt: Nuxt) {
return new Promise<WebSocketServer>((_resolve) => {
nuxt.hooks.hook('vite:serverCreated', (viteServer) => {
_resolve(viteServer.ws)
})
})
}
export function normalizeToKebabOrSnakeCase(str: string) {
const STRING_DASHERIZE_REGEXP = /\s/g
const STRING_DECAMELIZE_REGEXP = /([a-z\d])([A-Z])/g
return str
.replace(STRING_DECAMELIZE_REGEXP, '$1-$2')
.toLowerCase()
.replace(STRING_DASHERIZE_REGEXP, '-')
}
export function pluralize(str: string) {
return plrz.plural(str)
}
export function singularize(str: string) {
return plrz.singular(str)
}
export function capitalize(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
export function generateSchemaFile(name: string, fields: any) { export function generateSchemaFile(name: string, fields: any) {
name = capitalize(name) name = capitalize(name)

View File

@ -1,3 +1,3 @@
{ {
"extends": "./client/.nuxt/tsconfig.json" "extends": "./playground/.nuxt/tsconfig.json"
} }