feat: experimental generate resource
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { addServerPlugin, addTemplate, createResolver, defineNuxtModule, logger } from '@nuxt/kit'
|
||||
import { addImportsDir, addServerPlugin, addTemplate, createResolver, defineNuxtModule, logger } from '@nuxt/kit'
|
||||
import { pathExists } from 'fs-extra'
|
||||
import { tinyws } from 'tinyws'
|
||||
import { defu } from 'defu'
|
||||
@ -22,6 +22,8 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
setup(options, nuxt) {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
addImportsDir(resolve('./runtime/composables'))
|
||||
|
||||
if (!options.uri)
|
||||
console.warn('Missing `MONGODB_URI` in `.env`')
|
||||
|
||||
|
||||
8
src/runtime/composables/useMongoose.ts
Normal file
8
src/runtime/composables/useMongoose.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import type { mongo } from 'mongoose'
|
||||
import { connection } from 'mongoose'
|
||||
|
||||
export function useMongoose(): { db: mongo.Db } {
|
||||
return {
|
||||
db: connection?.db,
|
||||
}
|
||||
}
|
||||
@ -1,39 +1,38 @@
|
||||
import { logger } from '@nuxt/kit'
|
||||
import { ObjectId } from 'mongodb'
|
||||
import { connection as MongooseConnection, connect } from 'mongoose'
|
||||
import mongoose from 'mongoose'
|
||||
import type { NuxtDevtoolsServerContext, ServerFunctions } from '../types'
|
||||
|
||||
export function setupDatabaseRPC({ nuxt }: NuxtDevtoolsServerContext): any {
|
||||
// TODO:
|
||||
connect('mongodb://127.0.0.1:27017/arcane')
|
||||
mongoose.connect('mongodb://127.0.0.1:27017/arcane')
|
||||
|
||||
return {
|
||||
async createCollection(name: string) {
|
||||
return await MongooseConnection.db.createCollection(name)
|
||||
return await mongoose.connection.db.createCollection(name)
|
||||
},
|
||||
async listCollections() {
|
||||
return await MongooseConnection.db.listCollections().toArray()
|
||||
return await mongoose.connection.db.listCollections().toArray()
|
||||
},
|
||||
async getCollection(name: string) {
|
||||
return MongooseConnection.db.collection(name)
|
||||
return mongoose.connection.db.collection(name)
|
||||
},
|
||||
async dropCollection(name: string) {
|
||||
return await MongooseConnection.db.collection(name).drop()
|
||||
return await mongoose.connection.db.collection(name).drop()
|
||||
},
|
||||
|
||||
async createDocument(collection: string, data: any) {
|
||||
return await MongooseConnection.db.collection(collection).insertOne(data)
|
||||
return await mongoose.connection.db.collection(collection).insertOne(data)
|
||||
},
|
||||
async listDocuments(collection: string) {
|
||||
return await MongooseConnection.db.collection(collection).find().toArray()
|
||||
return await mongoose.connection.db.collection(collection).find().toArray()
|
||||
},
|
||||
async getDocument(collection: string, document: {}) {
|
||||
return await MongooseConnection.db.collection(collection).findOne({ document })
|
||||
return await mongoose.connection.db.collection(collection).findOne({ document })
|
||||
},
|
||||
async updateDocument(collection: string, data: any) {
|
||||
const { _id, ...rest } = data
|
||||
try {
|
||||
return await MongooseConnection.db.collection(collection).findOneAndUpdate({ _id: new ObjectId(_id) }, { $set: rest })
|
||||
return await mongoose.connection.db.collection(collection).findOneAndUpdate({ _id: new mongoose.Types.ObjectId(_id) }, { $set: rest })
|
||||
}
|
||||
catch (error) {
|
||||
logger.log(error)
|
||||
@ -41,7 +40,7 @@ export function setupDatabaseRPC({ nuxt }: NuxtDevtoolsServerContext): any {
|
||||
}
|
||||
},
|
||||
async deleteDocument(collection: string, id: string) {
|
||||
return await MongooseConnection.db.collection(collection).deleteOne({ _id: new ObjectId(id) })
|
||||
return await mongoose.connection.db.collection(collection).deleteOne({ _id: new mongoose.Types.ObjectId(id) })
|
||||
},
|
||||
} satisfies Partial<ServerFunctions>
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ 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
|
||||
@ -63,6 +64,7 @@ export function setupRPC(nuxt: Nuxt, options: ModuleOptions): any {
|
||||
|
||||
Object.assign(serverFunctions, {
|
||||
...setupDatabaseRPC(ctx),
|
||||
...setupResourceRPC(ctx),
|
||||
} satisfies Partial<ServerFunctions>)
|
||||
|
||||
const wsClients = new Set<WebSocket>()
|
||||
|
||||
77
src/server-rpc/resource.ts
Normal file
77
src/server-rpc/resource.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import fs from 'fs-extra'
|
||||
import { resolve } from 'pathe'
|
||||
import mongoose from 'mongoose'
|
||||
import type { Collection, NuxtDevtoolsServerContext, Resource, ServerFunctions } from '../types'
|
||||
import { generateApiRoute, generateSchemaFile } from '../utils/schematics'
|
||||
import { capitalize, pluralize, singularize } from '../utils/formatting'
|
||||
|
||||
export function setupResourceRPC({ nuxt }: NuxtDevtoolsServerContext): any {
|
||||
return {
|
||||
async generateResource(collection: Collection, resources: Resource[]) {
|
||||
const singular = singularize(collection.name).toLowerCase()
|
||||
const plural = pluralize(collection.name).toLowerCase()
|
||||
const dbName = capitalize(singular)
|
||||
|
||||
if (collection.fields) {
|
||||
if (!fs.existsSync(resolve(nuxt.options.serverDir, 'models', `${singular}.schema.ts`))) {
|
||||
fs.ensureDirSync(resolve(nuxt.options.serverDir, 'models'))
|
||||
fs.writeFileSync(
|
||||
resolve(nuxt.options.serverDir, 'models', `${singular}.schema.ts`),
|
||||
generateSchemaFile(dbName, collection.fields),
|
||||
)
|
||||
}
|
||||
|
||||
const model = { name: dbName, path: `${singular}.schema` }
|
||||
fs.ensureDirSync(resolve(nuxt.options.serverDir, `api/${plural}`))
|
||||
|
||||
// create resources
|
||||
// TODO: fix this
|
||||
resources.forEach((route: any) => {
|
||||
let fileName = ''
|
||||
if (route.type === 'index')
|
||||
fileName = 'index.get.ts'
|
||||
|
||||
if (route.type === 'create')
|
||||
fileName = 'create.post.ts'
|
||||
|
||||
if (route.type === 'show')
|
||||
fileName = `[_${route.by}].get.ts`.replace('_', '')
|
||||
|
||||
if (route.type === 'put')
|
||||
fileName = `[_${route.by}].put.ts`.replace('_', '')
|
||||
|
||||
if (route.type === 'delete')
|
||||
fileName = `[_${route.by}].delete.ts`.replace('_', '')
|
||||
|
||||
if (!fs.existsSync(resolve(nuxt.options.serverDir, `api/${plural}`, fileName))) {
|
||||
const content = generateApiRoute(route.type, { model, by: route.by })
|
||||
fs.writeFileSync(resolve(nuxt.options.serverDir, 'api', plural, fileName), content)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// create collection if not exists
|
||||
if (!mongoose.connection.modelNames().includes(dbName))
|
||||
await mongoose.connection.db.createCollection(plural)
|
||||
|
||||
// create rows and columns
|
||||
},
|
||||
async resourceSchema(collection: string) {
|
||||
// get schema file if exists
|
||||
const singular = singularize(collection).toLowerCase()
|
||||
|
||||
if (fs.existsSync(resolve(nuxt.options.serverDir, 'models', `${singular}.schema.ts`))) {
|
||||
const schemaPath = resolve(nuxt.options.serverDir, 'models', `${singular}.schema.ts`)
|
||||
|
||||
const content = fs.readFileSync(schemaPath, 'utf-8').match(/schema: \{(.|\n)*\}/g)
|
||||
|
||||
if (content) {
|
||||
const schemaString = content[0].replace('schema: ', '').slice(0, -3)
|
||||
// eslint-disable-next-line no-eval
|
||||
const schema = eval(`(${schemaString})`)
|
||||
return schema
|
||||
}
|
||||
}
|
||||
},
|
||||
} satisfies Partial<ServerFunctions>
|
||||
}
|
||||
@ -1,18 +1,32 @@
|
||||
export interface ServerFunctions {
|
||||
// collections
|
||||
// Database - collections
|
||||
createCollection(name: string): Promise<any>
|
||||
listCollections(): Promise<any>
|
||||
getCollection(name: string): Promise<any>
|
||||
dropCollection(name: string): Promise<any>
|
||||
|
||||
// documents
|
||||
// Database - documents
|
||||
createDocument(collection: string, data: any): Promise<any>
|
||||
listDocuments(collection: string): 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: string
|
||||
by?: string
|
||||
}
|
||||
|
||||
22
src/utils/formatting.ts
Normal file
22
src/utils/formatting.ts
Normal file
@ -0,0 +1,22 @@
|
||||
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)
|
||||
}
|
||||
49
src/utils/schematics.ts
Normal file
49
src/utils/schematics.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { capitalize } from './formatting'
|
||||
|
||||
export function generateSchemaFile(name: string, fields: any) {
|
||||
name = capitalize(name)
|
||||
// TODO: fix spacing
|
||||
const outputObject = JSON.stringify(
|
||||
fields.reduce((acc: any, curr: any) => {
|
||||
const { name, ...rest } = curr
|
||||
acc[name] = rest
|
||||
return acc
|
||||
}, {}),
|
||||
null, 2)
|
||||
.replace(/"([^"]+)":/g, '$1:')
|
||||
.replace(/"(\w+)":/g, '$1:')
|
||||
.replace(/\s*"\w+":/g, match => match.trim())
|
||||
.replace(/"string"/g, '\'string\'')
|
||||
|
||||
return `import { defineMongooseModel } from '#nuxt/mongoose'
|
||||
|
||||
export const ${name}Schema = defineMongooseModel({
|
||||
name: '${name}',
|
||||
schema: ${outputObject},
|
||||
})
|
||||
`
|
||||
}
|
||||
|
||||
export function generateApiRoute(action: string, { model, by }: { model: { name: string; path: string }; by?: string }) {
|
||||
const modelName = capitalize(model.name)
|
||||
const schemaImport = `import { ${modelName}Schema } from '../../models/${model.path}'\n\n`
|
||||
const operation = {
|
||||
index: `return await ${modelName}Schema.find({})`,
|
||||
create: `return await new ${modelName}Schema(body).save()`,
|
||||
show: `return await ${modelName}Schema.findOne({ ${by}: event.context.params?.${by} })`,
|
||||
put: `return await ${modelName}Schema.findOneAndUpdate({ ${by}: event.context.params?.${by} }, body, { new: true })`,
|
||||
delete: `return await ${modelName}Schema.findOneAndDelete({ ${by}: event.context.params?.${by} })`,
|
||||
}[action]
|
||||
|
||||
const main = `try {
|
||||
${operation}
|
||||
}
|
||||
catch (error) {
|
||||
return error
|
||||
}`
|
||||
|
||||
return `${schemaImport}export default defineEventHandler(async (event) => {
|
||||
${(action === 'create' || action === 'put') ? `const body = await readBody(event)\n ${main}` : main}
|
||||
})
|
||||
`
|
||||
}
|
||||
Reference in New Issue
Block a user