9 changed files with 182 additions and 15 deletions
@ -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 |
|||
> |
|||
} |
|||
@ -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(), |
|||
}); |
|||
@ -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…
Reference in new issue