Browse Source

feat: implement custom HTTP fetch utility and update user schema

tags/pure-sql-nuxt4 pure-sql-nuxt4
npmrun 7 days ago
parent
commit
29811ce001
  1. 2
      app/app.vue
  2. 95
      app/utils/http.ts
  3. 2
      bun.lock
  4. 15
      nuxt.config.ts
  5. 20
      packages/drizzle-pkg/lib/schema/auth.ts
  6. 19
      server/api/hello.get.ts
  7. 4
      server/plugins/03.error-logger.ts
  8. 16
      server/utils/handler.ts
  9. 24
      server/utils/response.ts

2
app/app.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
const { data, pending, error, refresh } = await useFetch('/api/hello')
const { data, pending, error, refresh } = await useHttpFetch('/api/hello')
const userCount = computed(() => data.value?.users?.length ?? 0)
</script>

95
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<T = unknown> = {
code: number
message: string
data: T
}
/** 从 Nitro 推断的响应体上剥离一层 `ApiResponse`,得到 `data` 字段类型 */
export type UnwrapApiResponse<T> = T extends ApiResponse<infer D> ? D : T
export function unwrapApiBody<T>(payload: ApiResponse<T>): 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<ReqT extends NitroFetchRequest> = 'get' extends AvailableRouterMethod<ReqT>
? 'get'
: AvailableRouterMethod<ReqT>
type HttpFetchOptions<
_ResT,
DataT,
PickKeys extends KeysOf<DataT>,
DefaultT,
ReqT extends NitroFetchRequest,
Method extends AvailableRouterMethod<ReqT>,
> = Omit<UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, '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<ReqT> = ResT extends void
? DefaultMethod<ReqT>
: AvailableRouterMethod<ReqT>,
_ResT = ResT extends void ? TypedInternalResponse<ReqT, unknown, Lowercase<Method>> : ResT,
DataT = UnwrapApiResponse<_ResT>,
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
DefaultT = undefined,
>(
url: ReqT | Ref<ReqT, ReqT> | (() => ReqT),
opts?: HttpFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
): AsyncData<DefaultT | PickFrom<DataT, PickKeys>, ErrorT | undefined> {
return _useHttpFetch(url, opts) as AsyncData<DefaultT | PickFrom<DataT, PickKeys>, ErrorT | undefined>
}
export function useLazyHttpFetch<
ResT = void,
ErrorT = FetchError,
ReqT extends NitroFetchRequest = NitroFetchRequest,
Method extends AvailableRouterMethod<ReqT> = ResT extends void
? DefaultMethod<ReqT>
: AvailableRouterMethod<ReqT>,
_ResT = ResT extends void ? TypedInternalResponse<ReqT, unknown, Lowercase<Method>> : ResT,
DataT = UnwrapApiResponse<_ResT>,
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
DefaultT = undefined,
>(
url: ReqT | Ref<ReqT, ReqT> | (() => ReqT),
opts?: HttpFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
): AsyncData<DefaultT | PickFrom<DataT, PickKeys>, ErrorT | undefined> {
return _useLazyHttpFetch(url, opts) as AsyncData<
DefaultT | PickFrom<DataT, PickKeys>,
ErrorT | undefined
>
}

2
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",

15
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']
},
}
},
}
},
})

20
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(),
});

19
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,
}
]
}
})
})

4
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
);
});
});

16
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 = <T extends EventHandlerRequest, D>(
handlerOrConfig?: EventHandler<T, D> | IConfig,
_handler?: EventHandler<T, D>,
@ -16,13 +19,20 @@ export const defineWrappedResponseHandler = <T extends EventHandlerRequest, D>(
throw new Error('handler or config is required');
}
const config = Object.assign({ ...defaultConfig }, typeof handlerOrConfig === 'object' ? handlerOrConfig : {});
return defineEventHandler<T>(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
}
})
}

24
server/utils/response.ts

@ -0,0 +1,24 @@
export const R = {
success: <T>(data: T) => {
return {
code: 0,
message: 'success',
data: data,
} as const
},
error: <T>(message: string, data: T) => {
return {
code: 1,
message: message,
data: null,
} as const
},
throwError: <T>(code: number, message: string, data: T) => {
throw createError({
statusCode: code,
statusMessage: message,
data: data,
}) as never
}
} as const
Loading…
Cancel
Save