diff --git a/app/app.vue b/app/app.vue
index 28d8f53..3214b6f 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -1,5 +1,5 @@
diff --git a/app/utils/http.ts b/app/utils/http.ts
new file mode 100644
index 0000000..2f0e6fb
--- /dev/null
+++ b/app/utils/http.ts
@@ -0,0 +1,95 @@
+import { createUseFetch } from '#imports'
+import type { AsyncData, UseFetchOptions } from '#app'
+import type { KeysOf, PickFrom } from '#app/composables/asyncData'
+import type { NitroFetchRequest, TypedInternalResponse, AvailableRouterMethod } from 'nitropack/types'
+import type { FetchError } from 'ofetch'
+import type { Ref } from 'vue'
+
+/** 与 `R.success` / `R.error` 返回结构对齐 */
+export type ApiResponse = {
+ code: number
+ message: string
+ data: T
+}
+
+/** 从 Nitro 推断的响应体上剥离一层 `ApiResponse`,得到 `data` 字段类型 */
+export type UnwrapApiResponse = T extends ApiResponse ? D : T
+
+export function unwrapApiBody(payload: ApiResponse): T {
+ if (payload.code !== 0) {
+ throw new Error(payload.message)
+ }
+ return payload.data
+}
+
+export const request = $fetch.create({})
+
+const httpFetchDefaults = {
+ retry: 0,
+ $fetch: request,
+ transform: unwrapApiBody,
+}
+
+const _useHttpFetch = createUseFetch(httpFetchDefaults)
+const _useLazyHttpFetch = createUseFetch({
+ ...httpFetchDefaults,
+ lazy: true,
+})
+
+type DefaultMethod = 'get' extends AvailableRouterMethod
+ ? 'get'
+ : AvailableRouterMethod
+
+type HttpFetchOptions<
+ _ResT,
+ DataT,
+ PickKeys extends KeysOf,
+ DefaultT,
+ ReqT extends NitroFetchRequest,
+ Method extends AvailableRouterMethod,
+> = Omit, 'transform'>
+
+/**
+ * 带项目默认选项的 `useFetch`,并在类型上将 `data` 视为接口 `{ code, data, message }` 中的内层 `data`。
+ *
+ * 说明:Nuxt的 `createUseFetch` 在类型上不会把工厂里的 `transform` 的出参当作 `AsyncData['data']`,
+ * 因此这里用薄包装修正 `DataT`(见 `UnwrapApiResponse`)。
+ */
+export function useHttpFetch<
+ ResT = void,
+ ErrorT = FetchError,
+ ReqT extends NitroFetchRequest = NitroFetchRequest,
+ Method extends AvailableRouterMethod = ResT extends void
+ ? DefaultMethod
+ : AvailableRouterMethod,
+ _ResT = ResT extends void ? TypedInternalResponse> : ResT,
+ DataT = UnwrapApiResponse<_ResT>,
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = undefined,
+>(
+ url: ReqT | Ref | (() => ReqT),
+ opts?: HttpFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
+): AsyncData, ErrorT | undefined> {
+ return _useHttpFetch(url, opts) as AsyncData, ErrorT | undefined>
+}
+
+export function useLazyHttpFetch<
+ ResT = void,
+ ErrorT = FetchError,
+ ReqT extends NitroFetchRequest = NitroFetchRequest,
+ Method extends AvailableRouterMethod = ResT extends void
+ ? DefaultMethod
+ : AvailableRouterMethod,
+ _ResT = ResT extends void ? TypedInternalResponse> : ResT,
+ DataT = UnwrapApiResponse<_ResT>,
+ PickKeys extends KeysOf = KeysOf,
+ DefaultT = undefined,
+>(
+ url: ReqT | Ref | (() => ReqT),
+ opts?: HttpFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
+): AsyncData, ErrorT | undefined> {
+ return _useLazyHttpFetch(url, opts) as AsyncData<
+ DefaultT | PickFrom,
+ ErrorT | undefined
+ >
+}
diff --git a/bun.lock b/bun.lock
index dbc8b73..8be5baf 100644
--- a/bun.lock
+++ b/bun.lock
@@ -12,7 +12,7 @@
"drizzle-zod": "0.8.3",
"log4js": "6.9.1",
"logger": "workspace:*",
- "mime": "^4.1.0",
+ "mime": "4.1.0",
"multer": "2.1.1",
"nuxt": "4.4.2",
"pg": "8.20.0",
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 4f8e5fe..94d068f 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -2,4 +2,19 @@
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
+ nitro: {
+ typescript: {
+ tsConfig: {
+ compilerOptions: {
+ resolvePackageJsonExports: true,
+ resolvePackageJsonImports: true,
+ baseUrl: './',
+ paths: {
+ 'drizzle-pkg': ['./packages/drizzle-pkg/lib'],
+ 'logger': ['./packages/logger/lib']
+ },
+ }
+ },
+ }
+ },
})
diff --git a/packages/drizzle-pkg/lib/schema/auth.ts b/packages/drizzle-pkg/lib/schema/auth.ts
index 0532ada..c11e658 100644
--- a/packages/drizzle-pkg/lib/schema/auth.ts
+++ b/packages/drizzle-pkg/lib/schema/auth.ts
@@ -1,8 +1,18 @@
-import { integer, pgTable, varchar } from "drizzle-orm/pg-core";
+import { sql } from "drizzle-orm";
+import { integer, pgTable, timestamp, varchar } from "drizzle-orm/pg-core";
export const usersTable = pgTable("users", {
- id: integer().primaryKey().generatedAlwaysAsIdentity(),
- name: varchar().notNull(),
- age: integer().notNull(),
- email: varchar().notNull().unique(),
+ id: integer().primaryKey(),
+ email: varchar().unique(),
+ username: varchar().notNull().unique(),
+ nickname: varchar(),
+ password: varchar().notNull(),
+ avatar: varchar(),
+ tel: varchar(),
+ role: varchar().notNull().default('user').$type<('user' | 'admin')>(),
+ createdAt: timestamp('created_at').defaultNow().notNull(),
+ updatedAt: timestamp('updated_at')
+ .defaultNow()
+ .$onUpdate(() => sql`CURRENT_TIMESTAMP`)
+ .notNull(),
});
\ No newline at end of file
diff --git a/server/api/hello.get.ts b/server/api/hello.get.ts
index a59dc93..00cb859 100644
--- a/server/api/hello.get.ts
+++ b/server/api/hello.get.ts
@@ -1,6 +1,19 @@
+import { dbGlobal } from "drizzle-pkg/lib/db"
+import { usersTable } from "drizzle-pkg/lib/schema/auth"
-export default defineWrappedResponseHandler((event) => {
- return {
+export default defineWrappedResponseHandler(async (event) => {
+
+ await dbGlobal.insert(usersTable).values({
+ id: 1,
+ username: "test",
+ password: "test",
+ email: "test@test.com",
+ nickname: "test",
+ avatar: "test",
+ tel: "test",
+ role: "user",
+ })
+ return R.success({
hello: "aa",
users: [
{
@@ -10,5 +23,5 @@ export default defineWrappedResponseHandler((event) => {
age: 23,
}
]
- }
+ })
})
\ No newline at end of file
diff --git a/server/plugins/03.error-logger.ts b/server/plugins/03.error-logger.ts
index 9a7b1ec..c0385fe 100644
--- a/server/plugins/03.error-logger.ts
+++ b/server/plugins/03.error-logger.ts
@@ -36,8 +36,8 @@ export default defineNitroPlugin((nitroApp) => {
event?.method ?? "",
event?.path ?? "(no request)",
tags ? `[${tags}]` : "",
- error.message,
- error.stack ?? ""
+ "\n",
+ error
);
});
});
diff --git a/server/utils/handler.ts b/server/utils/handler.ts
index 55d98cf..f5f9854 100644
--- a/server/utils/handler.ts
+++ b/server/utils/handler.ts
@@ -1,3 +1,4 @@
+import log4js from "logger";
interface IConfig {
@@ -7,6 +8,8 @@ const defaultConfig: IConfig = {
}
+const logger = log4js.getLogger("ERROR");
+
export const defineWrappedResponseHandler = (
handlerOrConfig?: EventHandler | IConfig,
_handler?: EventHandler,
@@ -16,13 +19,20 @@ export const defineWrappedResponseHandler = (
throw new Error('handler or config is required');
}
const config = Object.assign({ ...defaultConfig }, typeof handlerOrConfig === 'object' ? handlerOrConfig : {});
-
+
return defineEventHandler(async (event) => {
try {
const response = await handler(event)
return response
- } catch (err) {
- return { err }
+ } catch (error) {
+ logger.error(
+ event?.method ?? "",
+ event?.path ?? "(no request)",
+ `[request]`,
+ "\n",
+ error
+ );
+ return error
}
})
}
\ No newline at end of file
diff --git a/server/utils/response.ts b/server/utils/response.ts
new file mode 100644
index 0000000..ba81a29
--- /dev/null
+++ b/server/utils/response.ts
@@ -0,0 +1,24 @@
+
+export const R = {
+ success: (data: T) => {
+ return {
+ code: 0,
+ message: 'success',
+ data: data,
+ } as const
+ },
+ error: (message: string, data: T) => {
+ return {
+ code: 1,
+ message: message,
+ data: null,
+ } as const
+ },
+ throwError: (code: number, message: string, data: T) => {
+ throw createError({
+ statusCode: code,
+ statusMessage: message,
+ data: data,
+ }) as never
+ }
+} as const
\ No newline at end of file