chore: seal Design Vision v1 and monorepo scaffold
Archive the differentiated YuXinGu product docs, AI engineering system, design contract, and Go/Vue scaffold. Next execution prioritizes Cece-parity over early innovation (see .ai/product/STRATEGY.md). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import type { ApiResponse } from '@yuxingu/types'
|
||||
|
||||
/** Platform adapters so H5 and mini-program share one client. */
|
||||
export interface RequestOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||
path: string
|
||||
body?: unknown
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface ClientAdapters {
|
||||
/** Perform HTTP and return parsed JSON envelope. */
|
||||
request: <T>(opts: RequestOptions) => Promise<ApiResponse<T>>
|
||||
getToken?: () => string | null | Promise<string | null>
|
||||
}
|
||||
|
||||
export interface CreateClientOptions {
|
||||
baseURL: string
|
||||
adapters: ClientAdapters
|
||||
}
|
||||
|
||||
/**
|
||||
* createClient builds a typed API facade.
|
||||
* Side effect: network via adapters.request.
|
||||
*/
|
||||
export function createClient(opts: CreateClientOptions) {
|
||||
const { baseURL, adapters } = opts
|
||||
|
||||
async function call<T>(path: string, init?: Omit<RequestOptions, 'path'>): Promise<T> {
|
||||
const token = adapters.getToken ? await adapters.getToken() : null
|
||||
const headers: Record<string, string> = { ...(init?.headers || {}) }
|
||||
if (token) headers.Authorization = `Bearer ${token}`
|
||||
|
||||
const res = await adapters.request<T>({
|
||||
method: init?.method || 'GET',
|
||||
path: joinURL(baseURL, path),
|
||||
body: init?.body,
|
||||
headers,
|
||||
})
|
||||
if (res.code !== 0) {
|
||||
throw new Error(res.message || `api error ${res.code}`)
|
||||
}
|
||||
return res.data as T
|
||||
}
|
||||
|
||||
return {
|
||||
healthz: () => call<{ status: string }>('/api/v1/healthz'),
|
||||
ping: () => call<{ pong: boolean }>('/api/v1/ping'),
|
||||
}
|
||||
}
|
||||
|
||||
/** Browser fetch adapter for user-h5. */
|
||||
export function createBrowserAdapters(): ClientAdapters {
|
||||
return {
|
||||
request: async <T>({ method = 'GET', path, body, headers }) => {
|
||||
const res = await fetch(path, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(headers || {}),
|
||||
},
|
||||
body: body === undefined ? undefined : JSON.stringify(body),
|
||||
})
|
||||
return (await res.json()) as ApiResponse<T>
|
||||
},
|
||||
getToken: () => localStorage.getItem('yxg_token'),
|
||||
}
|
||||
}
|
||||
|
||||
function joinURL(base: string, path: string): string {
|
||||
if (path.startsWith('http')) return path
|
||||
const b = base.replace(/\/$/, '')
|
||||
const p = path.startsWith('/') ? path : `/${path}`
|
||||
return `${b}${p}`
|
||||
}
|
||||
Reference in New Issue
Block a user