3.0
Publish to npm / publish (push) Failing after 29s

This commit is contained in:
2026-04-08 01:07:10 +08:00
parent c100973590
commit d44938e456
17 changed files with 1574 additions and 1366 deletions
-7
View File
@@ -1,7 +0,0 @@
/**
* SimApi - 轻量 API 请求库
*/
export { useSimApi } from './simapi'
export type { SimApiAuthConfig, SimApiConfig, Versions } from './simapi'
export { SimApiVersion, AppVersion } from './simapi'
+323
View File
@@ -0,0 +1,323 @@
/**
* SimApi Core — 纯 TypeScript 核心,无框架依赖
*
* 支持所有 JS/TS 环境(Node.js、浏览器、小程序等)。
*
* @example
* import { SimApiCore } from '@simcu/simapi'
* const api = new SimApiCore()
* api.setEndpoints({ default: 'https://api.example.com' })
* const res = await api.query('/users/list', { page: 1 })
*/
import {
AppVersion,
SimApiVersion,
type SimApiVersions,
type SimApiAuthConfig,
type SimApiApiConfig,
type SimApiOptions,
type SimApiBaseResponse,
} from './types'
export { SimApiVersion, AppVersion } from './types'
export type {
SimApiVersions,
SimApiAuthConfig,
SimApiApiConfig,
SimApiOptions,
SimApiBaseResponse,
} from './types'
// ── Helper: Fetch with Timeout ────────────────────────────────────────
function fetchWithTimeout(
url: string,
options: RequestInit,
timeout: number = 10000
): Promise<Response> {
return Promise.race([
fetch(url, options),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`Request timeout after ${timeout}ms`)), timeout)
),
])
}
// ── Helper: Fetch POST with JSON body ────────────────────────────────────
async function fetchPost<T = any>(
url: string,
body: any,
headers: Record<string, string>,
timeout: number
): Promise<SimApiBaseResponse<T>> {
const options: RequestInit = {
method: 'POST',
headers: headers as HeadersInit,
body: body instanceof FormData ? body : JSON.stringify(body),
credentials: 'omit', // 从不发送 Cookie
}
const response = await fetchWithTimeout(url, options, timeout)
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw {
status: response.status,
statusText: response.statusText,
data: errorData,
message: `HTTP ${response.status}: ${response.statusText}`,
}
}
return response.json()
}
// ── SimApiCore ────────────────────────────────────────
export class SimApiCore {
debug: boolean = true
uiAppVersion?: string
auth: SimApiAuthConfig = {
token_name: 'simapi-auth-token',
check_url: '/auth/check',
logout_url: '/auth/logout',
login_url: '/auth/login',
}
api: SimApiApiConfig = {
endpoints: { default: '' },
defaultEndpoint: 'default',
businessCallback: {
401: () => localStorage.removeItem(this.auth.token_name),
common: () => {},
},
responseCallback: {
success: (response: any) => response,
error: (_err: any) => {},
},
timeout: 10000,
}
constructor(options?: SimApiOptions) {
if (options) {
this.configure(options)
}
}
/**
* 从 window.simapi 读取配置并初始化
*
* 支持字段:endpoints, defaultEndpoint, debug, uiAppVersion
* 业务回调(businessCallback / responseCallback)需在代码中处理
*/
autoInit(): void {
const config = (window as any).simapi
if (!config) return
if (config.debug !== undefined) {
this.debug = config.debug
}
if (config.uiAppVersion !== undefined) {
this.uiAppVersion = config.uiAppVersion
}
if (config.endpoints) {
this.api.endpoints = { ...this.api.endpoints, ...config.endpoints }
}
if (config.defaultEndpoint) {
this.api.defaultEndpoint = config.defaultEndpoint
}
}
configure(options: SimApiOptions): void {
if (options.debug !== undefined) {
this.debug = options.debug
}
if (options.uiAppVersion !== undefined) {
this.uiAppVersion = options.uiAppVersion
}
if (options.auth) {
this.auth = { ...this.auth, ...options.auth }
}
if (options.api) {
this.api = {
...this.api,
...options.api,
endpoints: { ...this.api.endpoints, ...(options.api.endpoints ?? {}) },
businessCallback: { ...this.api.businessCallback, ...(options.api.businessCallback ?? {}) },
responseCallback: { ...this.api.responseCallback, ...(options.api.responseCallback ?? {}) },
}
}
}
setEndpoints(endpoints: { [name: string]: string }): void {
this.api.endpoints = { ...this.api.endpoints, ...endpoints }
}
getEndpoint(name?: string): string {
return this.api.endpoints[name ?? this.api.defaultEndpoint] ?? ''
}
setBusinessCallback(code: number | string, callback: (data: any) => void): void {
this.api.businessCallback[code] = callback
}
getToken(): string {
return localStorage.getItem(this.auth.token_name) ?? ''
}
setToken(token: string): void {
localStorage.setItem(this.auth.token_name, token)
}
removeToken(): void {
localStorage.removeItem(this.auth.token_name)
}
get isLoggedIn(): boolean {
return !!localStorage.getItem(this.auth.token_name)
}
genS4(): string {
return (((1 + Math.random()) * 0x10000 * Date.parse(new Date().toString())) | 0)
.toString(16)
.substring(1)
}
/**
* 日志工具(仅在 debug 模式下输出)
*
* @example
* api.logDebug('用户登录', { id: 1, name: 'test' })
* api.logDebug('请求开始', uri, params)
*/
logDebug(...args: any[]): void {
if (!this.debug) return
console.log('[DEBUG]', ...args)
}
/**
* 获取版本信息
*
* @param endpointName - 指定从哪个 endpoint 获取版本,默认使用 default endpoint
* @returns 版本信息对象
*
* @example
* // 从默认 endpoint 获取
* const versions = await api.getVersion()
*
* // 从指定 endpoint 获取
* const versions = await api.getVersion('backup')
*/
async getVersion(endpointName?: string): Promise<SimApiVersions> {
try {
const resp = await this.query<any>('/versions', {}, endpointName)
if (resp?.data) {
const d = resp.data
const versions: SimApiVersions = {
uiApp: this.uiAppVersion ?? AppVersion,
uiSimApi: SimApiVersion,
apiApp: d.App?.split('+')[0] ?? '0.0.0',
apiSimApi: d.SimApi?.split('+')[0] ?? '0.0.0',
apiAppFull: d.App ?? '0.0.0',
apiSimApiFull: d.SimApi ?? '0.0.0',
}
if (this.debug) {
console.log(`UI主应用版本: ${versions.uiApp}\nUISimApi版本: ${versions.uiSimApi}\nAPI主应用版本: ${versions.apiApp}\nAPISimApi版本: ${versions.apiSimApi}`)
}
return versions
}
} catch {
// 版本获取失败返回默认值
}
return {
uiApp: AppVersion,
uiSimApi: SimApiVersion,
apiApp: '0.0.0',
apiSimApi: '0.0.0',
apiAppFull: '0.0.0',
apiSimApiFull: '0.0.0',
}
}
async query<T = any>(
uri: string,
params: any = {},
endpointKey?: string,
extraHeaders?: Record<string, string>
): Promise<SimApiBaseResponse<T>> {
const headers: Record<string, string> = { ...extraHeaders, ...{} }
const queryId = this.genS4()
if (!(params instanceof FormData)) {
headers['Content-Type'] = 'application/json'
}
const token = this.getToken()
if (token) {
headers['Token'] = token
}
if (this.debug) {
headers['Query-Id'] = queryId
console.log('[REQUEST*]', queryId, '->', uri, 'AUTH:', localStorage.getItem(this.auth.token_name))
}
const url = this.getEndpoint(endpointKey) + uri
try {
const respData = await fetchPost<T>(
url,
params,
headers,
this.api.timeout ?? 10000
)
if (this.debug) {
console.log('[RESPONSE]', queryId, '->', respData)
}
const processedData = this.api.responseCallback.success(respData) as SimApiBaseResponse<T>
// 业务回调处理
if (this.api.businessCallback.hasOwnProperty(processedData.code)) {
this.api.businessCallback[processedData.code](processedData)
} else if (this.api.businessCallback['common'] && processedData.code !== 200) {
this.api.businessCallback['common'](processedData)
}
// 直接返回,不再根据 code 抛出错误
return processedData
} catch (error) {
if (this.debug) {
console.log('[RESPONSE]', queryId, '->', error)
}
this.api.responseCallback.error(error)
throw error
}
}
async login(request: Record<string, any>): Promise<SimApiBaseResponse<string>> {
const result = await this.query<string>(this.auth.login_url, request)
if (result?.data) {
this.setToken(result.data)
}
return result
}
async logout(url?: string | null): Promise<any> {
this.removeToken()
if (url !== null) {
return this.query(url ?? this.auth.logout_url).catch(() => true)
}
return true
}
async checkLogin(url?: string | null): Promise<void> {
if (url !== null) {
await this.query(url ?? this.auth.check_url).catch(() => {})
} else if (this.getToken()) {
this.api.businessCallback[401]?.(null)
}
}
}
+91
View File
@@ -0,0 +1,91 @@
import { defineStore } from 'pinia'
import { SimApiCore } from './simapi.core'
import type { SimApiBaseResponse, SimApiOptions, SimApiVersions } from './types'
// ============ Pinia Store ============
// 仅作为 core 的代理映射,不维护任何独立状态
export const useSimApi = defineStore('simapi', {
state: () => ({
// 在 state 中实例化 core
_core: new SimApiCore(),
}),
getters: {
// 直接映射 core 的属性和方法
debug: (state) => state._core.debug,
token: (state) => state._core.getToken(),
isLoggedIn: (state) => state._core.isLoggedIn,
api: (state) => state._core.api,
auth: (state) => state._core.auth,
},
actions: {
// 所有方法直接代理到 core
autoInit(): void {
this._core.autoInit()
},
configure(options: SimApiOptions): void {
this._core.configure(options)
},
setDebug(debug: boolean): void {
this._core.debug = debug
},
setEndpoints(endpoints: { [name: string]: string }): void {
this._core.setEndpoints(endpoints)
},
setBusinessCallback(
code: number | string,
callback: (data: SimApiBaseResponse) => void
): void {
this._core.setBusinessCallback(code, callback)
},
getToken(): string {
return this._core.getToken()
},
setToken(token: string): void {
this._core.setToken(token)
},
removeToken(): void {
this._core.removeToken()
},
async login(request: Record<string, any>): Promise<SimApiBaseResponse<string>> {
return this._core.login(request)
},
async logout(url?: string | null): Promise<any> {
return this._core.logout(url)
},
async checkLogin(url?: string | null): Promise<void> {
return this._core.checkLogin(url)
},
async query<T = any>(
uri: string,
params?: any,
endpointKey?: string,
extraHeaders?: Record<string, string>
): Promise<SimApiBaseResponse<T>> {
return this._core.query<T>(uri, params, endpointKey, extraHeaders)
},
getEndpoint(name?: string): string {
return this._core.getEndpoint(name)
},
async getVersion(endpointName?: string): Promise<SimApiVersions> {
return this._core.getVersion(endpointName)
},
},
})
-207
View File
@@ -1,207 +0,0 @@
/**
* SimApi Pinia Store
*
* 使用方法:
* import { useSimApi } from 'simapi'
* const api = useSimApi()
* await api.query('/api/xxx', data)
*/
import { defineStore } from 'pinia'
import axios, { AxiosRequestHeaders } from 'axios'
// ============ 类型定义 ============
export const SimApiVersion = '1.0.0'
export const AppVersion = '1.0.0'
export interface Versions {
uiApp: string
uiSimApi: string
apiApp: string
apiSimApi: string
apiAppFull: string
apiSimApiFull: string
}
export interface BusinessCallback {
[key: number | string]: (data: any) => void
}
export interface SimApiAuthConfig {
token_name: string
check_url: string
logout_url: string
login_url: string
}
export interface SimApiConfig {
endpoints: { [name: string]: string }
defaultEndpoint: string
businessCallback: BusinessCallback
}
// ============ Store 定义 ============
export const useSimApi = defineStore('simapi', {
// ============ State ============
state: () => ({
debug: true,
auth: {
token_name: 'simapi-auth-token',
check_url: '/auth/check',
logout_url: '/auth/logout',
login_url: '/auth/login'
} as SimApiAuthConfig,
api: {
endpoints: { default: '' },
defaultEndpoint: 'default',
businessCallback: {
401: () => localStorage.removeItem('simapi-auth-token'),
common: () => {}
}
} as SimApiConfig,
versions: {
uiApp: AppVersion,
uiSimApi: SimApiVersion,
apiApp: '0.0.0',
apiSimApi: '0.0.0',
apiAppFull: '0.0.0',
apiSimApiFull: '0.0.0'
} as Versions
}),
// ============ Getters ============
getters: {
token: (state) => localStorage.getItem(state.auth.token_name) || '',
isLoggedIn: (state) => !!localStorage.getItem(state.auth.token_name)
},
// ============ Actions ============
actions: {
/** 设置端点配置 */
setEndpoints(endpoints: { [name: string]: string }): void {
this.api.endpoints = { ...this.api.endpoints, ...endpoints }
// 获取并打印版本信息
this.getVersions()
},
/** 设置业务错误回调 */
setBusinessCallback(code: number | string, callback: (data: any) => void): void {
this.api.businessCallback[code] = callback
},
/** 设置调试模式 */
setDebug(debug: boolean): void {
this.debug = debug
},
/** 打印调试日志 */
debug(title: string, data: any): void {
if (this.debug) {
console.log('[DEBUG]', title, data)
}
},
/** 生成随机字符串 */
genS4(): string {
return (((1 + Math.random()) * 0x10000 * Date.parse(new Date())) | 0).toString(16).substring(1)
},
/** 获取端点地址 */
getEndpoint(name?: string): string {
return this.api.endpoints[name || this.api.defaultEndpoint] || ''
},
/** 获取并打印版本信息 */
async getVersions(): Promise<void> {
try {
const resp = await axios.post(this.getEndpoint() + '/versions', {}, { timeout: 5000 })
if (resp.data?.data) {
this.versions = {
uiApp: AppVersion,
uiSimApi: SimApiVersion,
apiApp: resp.data.data.App?.split('+')[0] || '0.0.0',
apiSimApi: resp.data.data.SimApi?.split('+')[0] || '0.0.0',
apiAppFull: resp.data.data.App || '0.0.0',
apiSimApiFull: resp.data.data.SimApi || '0.0.0'
}
}
} catch (e) {
// 版本获取失败不影响主流程
}
// 打印版本信息,格式与 Angular 一致
console.log(`UI主应用版本: ${this.versions.uiApp}\nUISimApi版本: ${this.versions.uiSimApi}\nAPI主应用版本: ${this.versions.apiApp}\nAPISimApi版本: ${this.versions.apiSimApi}`)
},
/** 发起请求 */
async query(uri: string, params: any = {}): Promise<any> {
const headers: Record<string, string> = {}
const queryId = this.genS4()
if (!(params instanceof FormData)) {
headers['Content-Type'] = 'application/json'
}
const token = this.token
if (token) {
headers['Token'] = token
}
if (this.debug) {
headers['Query-Id'] = queryId
console.log('[REQUEST*]', queryId, '->', uri, 'AUTH:', token)
}
const url = this.getEndpoint() + uri
try {
const response = await axios.post(url, params, { headers: headers as AxiosRequestHeaders })
if (this.debug) {
console.log('[RESPONSE]', queryId, '->', response.data)
}
return this.handleResponse(response.data)
} catch (error) {
if (this.debug) {
console.log('[RESPONSE]', queryId, '->', error)
}
throw error
}
},
/** 处理响应 */
handleResponse(data: any): any {
if (data.code !== 200) {
const callback = this.api.businessCallback[data.code] || this.api.businessCallback['common']
callback?.(data)
}
return data
},
/** 登录 */
async login(request: Record<string, any>): Promise<any> {
const result = await this.query(this.auth.login_url, request)
if (result.data) {
localStorage.setItem(this.auth.token_name, result.data)
}
return result
},
/** 登出 */
async logout(url?: string | null): Promise<any> {
localStorage.removeItem(this.auth.token_name)
if (url !== null) {
return this.query(url ?? this.auth.logout_url).catch(() => true)
}
return true
},
/** 获取 Token */
getToken(): string {
return localStorage.getItem(this.auth.token_name) || ''
}
}
})
+70
View File
@@ -0,0 +1,70 @@
/**
* SimApi 类型定义
*/
// 版本号占位符,构建时由 GitHub Action 替换
export const SimApiVersion = '0.0.0-version-placeholder'
export const AppVersion = '0.0.0-version-placeholder'
/** 版本信息 */
export interface SimApiVersions {
uiApp: string
uiSimApi: string
apiApp: string
apiSimApi: string
apiAppFull: string
apiSimApiFull: string
}
/** 认证配置 */
export interface SimApiAuthConfig {
/** localStorage key,默认 'simapi-auth-token' */
token_name: string
/** 检查登录接口,默认 '/auth/check' */
check_url: string
/** 登出接口,默认 '/auth/logout' */
logout_url: string
/** 登录接口,默认 '/auth/login' */
login_url: string
}
/** 业务错误码回调 */
export interface SimApiBusinessCallback {
[key: number | string]: (data: any) => void
}
/** 响应拦截回调 */
export interface SimApiResponseCallback {
success: (response: any) => any
error: (err: any) => void
}
/** API 配置 */
export interface SimApiApiConfig {
/** 多端点映射 */
endpoints: { [name: string]: string }
/** 默认端点名称 */
defaultEndpoint: string
/** 业务错误码回调 */
businessCallback: SimApiBusinessCallback
/** 响应拦截器 */
responseCallback: SimApiResponseCallback
/** 请求超时时间(毫秒),默认 10000 */
timeout?: number
}
/** SimApi 完整配置 */
export interface SimApiOptions {
debug?: boolean
/** UI 应用版本,如果不指定则使用库内置的占位符版本 */
uiAppVersion?: string
auth?: Partial<SimApiAuthConfig>
api?: Partial<SimApiApiConfig>
}
/** SimApi 标准响应格式 */
export interface SimApiBaseResponse<T = any> {
code: number
message: string
data?: T
}