diff --git a/.gitignore b/.gitignore index c2f9149..d02a5a2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules .pnpm-debug.log dist -build \ No newline at end of file +build +mydb.sqlite diff --git a/example2/db/db.ts b/example2/db/db.ts new file mode 100644 index 0000000..99b0c3b --- /dev/null +++ b/example2/db/db.ts @@ -0,0 +1,3 @@ + +import { drizzle } from 'drizzle-orm/bun-sqlite'; +export const db = drizzle('mydb.sqlite'); diff --git a/example2/db/model/index.ts b/example2/db/model/index.ts new file mode 100644 index 0000000..f6a0203 --- /dev/null +++ b/example2/db/model/index.ts @@ -0,0 +1,3 @@ + +export * from './user.model' +export * from './post.model' \ No newline at end of file diff --git a/example2/db/model/post.model.ts b/example2/db/model/post.model.ts new file mode 100644 index 0000000..941efba --- /dev/null +++ b/example2/db/model/post.model.ts @@ -0,0 +1,30 @@ +import { relations } from 'drizzle-orm'; +import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { usersTable } from './user.model'; + + + + +export const postTable = sqliteTable('posts', { + id: int().primaryKey({ autoIncrement: true }), + userId: int() + .notNull() + .references(() => usersTable.id), + title: text().notNull(), + content: text().notNull(), + createdAt: int() + .notNull() + .$defaultFn(() => Date.now()), + updatedAt: int() + .notNull() + .$defaultFn(() => Date.now()) +}) + + + +export const postsRelations = relations(postTable, ({ one }) => ({ + user: one(usersTable, { + fields: [postTable.userId], + references: [usersTable.id] + }) +})) \ No newline at end of file diff --git a/example2/db/model/user.model.ts b/example2/db/model/user.model.ts new file mode 100644 index 0000000..b77a9e5 --- /dev/null +++ b/example2/db/model/user.model.ts @@ -0,0 +1,41 @@ +import { relations } from 'drizzle-orm'; +import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from 'drizzle-zod'; +import { postTable } from './post.model'; +import z from 'zod'; + +// 1. Drizzle 表定义 +export const usersTable = sqliteTable('users', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull().unique(), + bio: text(), + createdAt: int() + .notNull() + .$defaultFn(() => Date.now()) +}) + + + +// 2. Zod Schema(基于 Drizzle 表生成,并可扩展校验) +export const selectUsersSchema = createSelectSchema(usersTable); +export const insertUsersSchema = createInsertSchema(usersTable); +export const updateUsersSchema = createUpdateSchema(usersTable) + + + + +// 3. 类型定义(可选,但推荐) 导出 TypeScript 类型(方便路由、service 等使用) + +export const usersModel = { + insertUsersDto: insertUsersSchema + .omit({ id: true, createdAt: true }) + .describe('创建用户请求'), + updateUsersDto: updateUsersSchema.omit({ id: true, createdAt: true }), + selectUsersTable: selectUsersSchema + .describe('用户信息响应') +} +export const usersRelations = relations(usersTable, ({ many }) => ({ + posts: many(postTable) +})) + +export type SelectUsersTable = z.infer diff --git a/example2/drizzle.config.ts b/example2/drizzle.config.ts new file mode 100644 index 0000000..037a8e3 --- /dev/null +++ b/example2/drizzle.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'drizzle-kit'; +export default defineConfig({ + out: './drizzle', + schema: './example2/db/index.ts', + dialect: 'sqlite', + dbCredentials: { + url: 'mydb.sqlite', + }, +}); \ No newline at end of file diff --git a/example2/index.ts b/example2/index.ts new file mode 100644 index 0000000..3ee3c06 --- /dev/null +++ b/example2/index.ts @@ -0,0 +1,80 @@ +import { Elysia, redirect } from "elysia" +import openapi from "../src" + +import { db } from "./db/db" +import z from "zod/v4" +import { eq } from "drizzle-orm" +import { JSONSchema } from "effect" +import { fromTypes } from "../src/gen" + +import path from 'node:path' +import { usersModel, usersTable } from "./db/model" +console.log("tsconfig path:", path.join(import.meta.dir, '../tsconfig.json')) +export const app = new Elysia() + .use( + openapi({ + references: fromTypes( + process.env.NODE_ENV === "production" + ? "dist/index.d.ts" + : "example2/index.ts", + { + tsconfigPath: path.join(import.meta.dir, '../tsconfig.json') + } + ), + provider: 'scalar', + mapJsonSchema: { + zod: z.toJSONSchema, + effect: JSONSchema.make + }, + documentation: { + info: { + title: 'Elysia Scalar', + version: '1.3.1a' + }, + tags: [ + { + name: 'Test', + description: 'Hello' + } + ], + components: { + securitySchemes: { + bearer: { + type: 'http', + scheme: 'bearer' + }, + cookie: { + type: 'apiKey', + in: 'cookie', + name: 'session_id' + } + } + } + } + }) + ) + .model(usersModel) + .get('/', () => { + redirect('/openapi') + }) + .post('/', async ({ body }) => { + // create user + return await db.insert(usersTable).values({ id: Math.floor(Math.random() * 10), ...body }) + }, { + body: 'insertUsersDto' + }) + .put('/:id', async ({ params: { id }, body }) => { + return await db.update(usersTable).set(body).where(eq(usersTable.id, id)) + }, { + params: z.object({ + id: z.coerce.number() + }), + body: 'updateUsersDto' + }) + .get('/list', async () => { + return await db.select().from(usersTable) + }) + .listen(4050) + + +console.log("http://localhost:4050") \ No newline at end of file diff --git a/package.json b/package.json index ee4473a..440dda8 100644 --- a/package.json +++ b/package.json @@ -68,13 +68,15 @@ "license": "MIT", "scripts": { "dev": "bun run --watch example/index.ts", + "dev2": "bun run --watch example2/index.ts", + "db:push": "bunx drizzle-kit push --config=example2/drizzle.config.ts", "test": "bun test && npm run test:node", "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", "build": "bun build.ts", "release": "npm run build && npm run test && npm publish --access public" }, "dependencies": {}, - "devDependencies": { + "devDependencies": { "@apidevtools/swagger-parser": "^12.0.0", "@scalar/types": "^0.2.13", "@sinclair/typemap": "^0.10.1", @@ -85,9 +87,12 @@ "openapi-types": "^12.1.3", "tsup": "^8.5.0", "typescript": "^5.9.2", - "zod": "^4.1.5" + "zod": "^4.1.5", + "drizzle-orm": "^0.44.5", + "drizzle-zod": "^0.8.3", + "drizzle-kit": "^0.31.4" }, - "peerDependencies": { + "peerDependencies": { "elysia": ">= 1.4.0" } } diff --git a/src/gen/index.ts b/src/gen/index.ts index 6847f63..7be8244 100644 --- a/src/gen/index.ts +++ b/src/gen/index.ts @@ -232,22 +232,38 @@ export const fromTypes = let targetFile: string if (!tmpRoot) { - const os = process.getBuiltinModule('os') - - tmpRoot = join( - os && typeof os.tmpdir === 'function' - ? os.tmpdir() - : projectRoot, - '.ElysiaAutoOpenAPI' - ) + // On Windows, prefer project root over system temp directory to avoid permission issues + if (process.platform === 'win32') { + tmpRoot = join(projectRoot, 'node_modules/.cache/.ElysiaAutoOpenAPI') + } else { + const os = process.getBuiltinModule('os') + tmpRoot = join( + os && typeof os.tmpdir === 'function' + ? os.tmpdir() + : projectRoot, + '.ElysiaAutoOpenAPI' + ) + } } // Since it's already a declaration file // We can just read it directly if (targetFilePath.endsWith('.d.ts')) targetFile = targetFilePath else { - if (fs.existsSync(tmpRoot)) - fs.rmSync(tmpRoot, { recursive: true, force: true }) + if (fs.existsSync(tmpRoot)) { + try { + fs.rmSync(tmpRoot, { recursive: true, force: true }) + } catch (error) { + // On Windows, files might be locked by other processes + // Silently ignore cleanup errors to not interrupt the main functionality + if (!silent) { + console.warn( + '[@elysiajs/openapi/gen] Warning: Could not clean up temporary directory:', + tmpRoot + ) + } + } + } fs.mkdirSync(tmpRoot, { recursive: true }) @@ -380,8 +396,20 @@ export const fromTypes = const declaration = fs.readFileSync(targetFile, 'utf8') // Check just in case of race-condition - if (!debug && fs.existsSync(tmpRoot)) - fs.rmSync(tmpRoot, { recursive: true, force: true }) + if (!debug && fs.existsSync(tmpRoot)) { + try { + fs.rmSync(tmpRoot, { recursive: true, force: true }) + } catch (error) { + // On Windows, files might be locked by other processes + // Silently ignore cleanup errors to not interrupt the main functionality + if (!silent) { + console.warn( + '[@elysiajs/openapi/gen] Warning: Could not clean up temporary directory:', + tmpRoot + ) + } + } + } let instance = declaration.match( instanceName @@ -414,7 +442,19 @@ export const fromTypes = return } finally { - if (!debug && tmpRoot && fs.existsSync(tmpRoot)) - fs.rmSync(tmpRoot, { recursive: true, force: true }) + if (!debug && tmpRoot && fs.existsSync(tmpRoot)) { + try { + fs.rmSync(tmpRoot, { recursive: true, force: true }) + } catch (error) { + // On Windows, files might be locked by other processes + // Silently ignore cleanup errors to not interrupt the main functionality + if (!silent) { + console.warn( + '[@elysiajs/openapi/gen] Warning: Could not clean up temporary directory:', + tmpRoot + ) + } + } + } } }