UNPKG

alova

Version:

The Request Toolkit For Ultimate Efficiency

1,163 lines (1,145 loc) 223 kB
/** * @alova/client 2.0.0 (https://alova.js.org) * Document https://alova.js.org * Copyright 2025 Scott hu. All Rights Reserved * Licensed under MIT (git://github.com/alovajs/alova/blob/main/LICENSE) */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('alova')) : typeof define === 'function' && define.amd ? define(['exports', 'alova'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.AlovaClientHook = {}, global.alova)); })(this, (function (exports, alova) { 'use strict'; /** * @alova/shared 1.3.1 (https://alova.js.org) * Document https://alova.js.org * Copyright 2025 Scott Hu. All Rights Reserved * Licensed under MIT (https://github.com/alovajs/alova/blob/main/LICENSE) */ const undefStr = 'undefined'; // The following unified processing functions or variables added to reduce the amount of compiled code const PromiseCls = Promise; const promiseResolve = (value) => PromiseCls.resolve(value); const promiseReject = (value) => PromiseCls.reject(value); const ObjectCls = Object; const RegExpCls = RegExp; const undefinedValue = undefined; const nullValue = null; const trueValue = true; const falseValue = false; const promiseThen = (promise, onFulfilled, onrejected) => promise.then(onFulfilled, onrejected); const promiseCatch = (promise, onrejected) => promise.catch(onrejected); const promiseFinally = (promise, onfinally) => promise.finally(onfinally); const promiseAll = (values) => PromiseCls.all(values); const JSONStringify = (value, replacer, space) => JSON.stringify(value, replacer, space); const JSONParse = (value) => JSON.parse(value); const setTimeoutFn = (fn, delay = 0) => setTimeout(fn, delay); const clearTimeoutTimer = (timer) => clearTimeout(timer); const objectKeys = (obj) => ObjectCls.keys(obj); const objectValues = (obj) => ObjectCls.values(obj); const forEach = (ary, fn) => ary.forEach(fn); const pushItem = (ary, ...item) => ary.push(...item); const mapItem = (ary, callbackfn) => ary.map(callbackfn); const filterItem = (ary, predicate) => ary.filter(predicate); const shift = (ary) => ary.shift(); const splice = (ary, start, deleteCount = 0, ...items) => ary.splice(start, deleteCount, ...items); const len = (data) => data.length; const isArray = (arg) => Array.isArray(arg); const deleteAttr = (arg, attr) => delete arg[attr]; const typeOf = (arg) => typeof arg; const regexpTest = (reg, str) => reg.test(`${str}`); const includes = (ary, target) => ary.includes(target); const valueObject = (value, writable = falseValue) => ({ value, writable }); const defineProperty = (o, key, value, isDescriptor = falseValue) => ObjectCls.defineProperty(o, key, isDescriptor ? value : valueObject(value, falseValue)); // Whether it is running on the server side, node and bun are judged by process, and deno is judged by Deno. // Some frameworks (such as Alipay and uniapp) will inject the process object as a global variable which `browser` is true typeof window === undefStr && (typeof process !== undefStr ? !process.browser : typeof Deno !== undefStr); /** cache mode */ // only cache in memory, it's default option const MEMORY = 'memory'; // persistent cache, and will be read to memory when page is refreshed, it means that the memory cache always exist until cache is expired. const STORAGE_RESTORE = 'restore'; /** * Empty function for compatibility processing */ const noop = () => { }; /** * A function that returns the parameter itself, used for compatibility processing * Since some systems use self as a reserved word, $self is used to distinguish it. * @param arg any parameter * @returns return parameter itself */ const $self = (arg) => arg; /** * Determine whether the parameter is a function any parameter * @returns Whether the parameter is a function */ const isFn = (arg) => typeOf(arg) === 'function'; /** * Determine whether the parameter is a number any parameter * @returns Whether the parameter is a number */ const isNumber = (arg) => typeOf(arg) === 'number' && !Number.isNaN(arg); /** * Determine whether the parameter is a string any parameter * @returns Whether the parameter is a string */ const isString = (arg) => typeOf(arg) === 'string'; /** * Determine whether the parameter is an object any parameter * @returns Whether the parameter is an object */ const isObject = (arg) => arg !== nullValue && typeOf(arg) === 'object'; /** * Global toString any parameter stringified parameters */ const globalToString = (arg) => ObjectCls.prototype.toString.call(arg); /** * Determine whether it is a normal object any parameter * @returns Judgment result */ const isPlainObject = (arg) => globalToString(arg) === '[object Object]'; /** * Determine whether it is an instance of a certain class any parameter * @returns Judgment result */ const instanceOf = (arg, cls) => arg instanceof cls; /** * Unified timestamp acquisition function * @returns Timestamp */ const getTime = (date) => (date ? date.getTime() : Date.now()); /** * Get the alova instance through the method instance alova example */ const getContext = (methodInstance) => methodInstance.context; /** * Get method instance configuration data * @returns Configuration object */ const getConfig = (methodInstance) => methodInstance.config; /** * Get alova configuration data alova configuration object */ const getContextOptions = (alovaInstance) => alovaInstance.options; /** * Get alova configuration data through method instance alova configuration object */ const getOptions = (methodInstance) => getContextOptions(getContext(methodInstance)); /** * Create uuid simple version uuid */ const uuid = () => { const timestamp = new Date().getTime(); return Math.floor(Math.random() * timestamp).toString(36); }; /** * Get the key value of the method instance method instance * @returns The key value of this method instance */ const getMethodInternalKey = (methodInstance) => methodInstance.key; /** * Get the request method object * @param methodHandler Request method handle * @param args Method call parameters request method object */ const getHandlerMethod$1 = (methodHandler, assert, args = []) => { const methodInstance = isFn(methodHandler) ? methodHandler(...args) : methodHandler; assert(!!methodInstance.key, 'hook handler must be a method instance or a function that returns method instance'); return methodInstance; }; /** * Is it special data * @param data Submit data * @returns Judgment result */ const isSpecialRequestBody = (data) => { const dataTypeString = globalToString(data); return (/^\[object (Blob|FormData|ReadableStream|URLSearchParams)\]$/i.test(dataTypeString) || instanceOf(data, ArrayBuffer)); }; const objAssign = (target, ...sources) => ObjectCls.assign(target, ...sources); /** * Excludes specified attributes from a data collection and returns a new data collection data collection * @param keys Excluded keys new data collection */ const omit = (obj, ...keys) => { const result = {}; for (const key in obj) { if (!keys.includes(key)) { result[key] = obj[key]; } } return result; }; /** * the same as `Promise.withResolvers` * @returns promise with resolvers. */ function usePromise() { let retResolve; let retReject; const promise = new Promise((resolve, reject) => { retResolve = resolve; retReject = reject; }); return { promise, resolve: retResolve, reject: retReject }; } /** * Get cached configuration parameters, fixedly returning an object in the format { e: function, c: any, f: any, m: number, s: boolean, t: string } e is the abbreviation of expire, which returns the cache expiration time point (timestamp) in milliseconds. * c is controlled, indicating whether it is a controlled cache * f is the original value of cacheFor, which is used to call to obtain cached data when c is true. * m is the abbreviation of mode, storage mode * s is the abbreviation of storage, whether to store it locally * t is the abbreviation of tag, which stores tags persistently. * @param methodInstance method instance * @returns Unified cache parameter object */ const getLocalCacheConfigParam = (methodInstance) => { const { cacheFor } = getConfig(methodInstance); const getCacheExpireTs = (cacheExpire) => isNumber(cacheExpire) ? getTime() + cacheExpire : getTime(cacheExpire || undefinedValue); let cacheMode = MEMORY; let expire = () => 0; let store = falseValue; let tag = undefinedValue; const controlled = isFn(cacheFor); if (!controlled) { let expireColumn = cacheFor; if (isPlainObject(cacheFor)) { const { mode = MEMORY, expire, tag: configTag } = cacheFor || {}; cacheMode = mode; store = mode === STORAGE_RESTORE; tag = configTag ? configTag.toString() : undefinedValue; expireColumn = expire; } expire = (mode) => getCacheExpireTs(isFn(expireColumn) ? expireColumn({ method: methodInstance, mode }) : expireColumn); } return { f: cacheFor, c: controlled, e: expire, m: cacheMode, s: store, t: tag }; }; /** * Create class instance * @param Cls Constructor * @param args Constructor parameters class instance */ const newInstance = (Cls, ...args) => new Cls(...args); /** * Unified configuration * @param data * @returns unified configuration */ const sloughConfig = (config, args = []) => isFn(config) ? config(...args) : config; /** * Create an executor that calls multiple times synchronously and only executes it once asynchronously */ const createSyncOnceRunner = (delay = 0) => { let timer = undefinedValue; // Executing multiple calls to this function will execute once asynchronously return (fn) => { if (timer) { clearTimeout(timer); } timer = setTimeoutFn(fn, delay); }; }; /** * Create an asynchronous function queue, the asynchronous function will be executed serially queue add function */ const createAsyncQueue = (catchError = falseValue) => { const queue = []; let completedHandler = undefinedValue; let executing = false; const executeQueue = async () => { executing = trueValue; while (len(queue) > 0) { const asyncFunc = shift(queue); if (asyncFunc) { await asyncFunc(); } } completedHandler && completedHandler(); executing = falseValue; }; const addQueue = (asyncFunc) => newInstance((PromiseCls), (resolve, reject) => { const wrappedFunc = () => promiseThen(asyncFunc(), resolve, err => { catchError ? resolve(undefinedValue) : reject(err); }); pushItem(queue, wrappedFunc); if (!executing) { executeQueue(); } }); const onComplete = (fn) => { completedHandler = fn; }; return { addQueue, onComplete }; }; /** * Traverse the target object deeply target audience * @param callback Traversal callback * @param preorder Whether to traverse in preorder, the default is true * @param key The currently traversed key * @param parent The parent node currently traversed */ const walkObject = (target, callback, preorder = trueValue, key, parent) => { const callCallback = () => { if (parent && key) { target = callback(target, key, parent); if (target !== parent[key]) { parent[key] = target; } } }; // Preorder traversal preorder && callCallback(); if (isObject(target)) { for (const i in target) { if (!instanceOf(target, String)) { walkObject(target[i], callback, preorder, i, target); } } } // Postal order traversal !preorder && callCallback(); return target; }; const cacheKeyPrefix = '$a.'; /** * build common cache key. */ const buildNamespacedCacheKey = (namespace, key) => cacheKeyPrefix + namespace + key; /** * Calculate retry delay time based on avoidance strategy and number of retries avoid parameters * @param retryTimes Number of retries * @returns Retry delay time */ const delayWithBackoff = (backoff, retryTimes) => { let { startQuiver, endQuiver } = backoff; const { delay, multiplier = 1 } = backoff; let retryDelayFinally = (delay || 0) * multiplier ** (retryTimes - 1); // If start quiver or end quiver has a value, you need to increase the random jitter value in the specified range if (startQuiver || endQuiver) { startQuiver = startQuiver || 0; endQuiver = endQuiver || 1; retryDelayFinally += retryDelayFinally * startQuiver + Math.random() * retryDelayFinally * (endQuiver - startQuiver); retryDelayFinally = Math.floor(retryDelayFinally); // round delay } return retryDelayFinally; }; /** * Build the complete url baseURL path url parameters complete url */ const buildCompletedURL = (baseURL, url, params) => { // Check if the URL starts with http/https const startsWithPrefix = /^https?:\/\//i.test(url); if (!startsWithPrefix) { // If the Base url ends with /, remove / baseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL; // If it does not start with /or http protocol, you need to add / // Compatible with some RESTful usage fix: https://github.com/alovajs/alova/issues/382 if (url !== '') { // Since absolute URLs (http/https) are handled above, // we only need to ensure relative URLs start with a forward slash url = url.startsWith('/') ? url : `/${url}`; } } // fix: https://github.com/alovajs/alova/issues/653 const completeURL = startsWithPrefix ? url : baseURL + url; // Convert params object to get string // Filter out those whose value is undefined const paramsStr = isString(params) ? params : mapItem(filterItem(objectKeys(params), key => params[key] !== undefinedValue), key => `${key}=${params[key]}`).join('&'); // Splice the get parameters behind the url. Note that the url may already have parameters. return paramsStr ? +completeURL.includes('?') ? `${completeURL}&${paramsStr}` : `${completeURL}?${paramsStr}` : completeURL; }; /** * alova error class */ class AlovaError extends Error { constructor(prefix, message, errorCode) { super(message + (errorCode ? `\n\nFor detailed: https://alova.js.org/error#${errorCode}` : '')); this.name = `[alova${prefix ? `/${prefix}` : ''}]`; } } /** * Custom assertion function that throws an error when the expression is false * When errorCode is passed in, a link to the error document will be provided to guide the user to correct it. * @param expression Judgment expression, true or false * @param message Assert message */ const createAssert = (prefix = '') => (expression, message, errorCode) => { if (!expression) { throw newInstance(AlovaError, prefix, message, errorCode); } }; const bridgeObject = JSON.parse; const injectReferingObject = () => (bridgeObject.bridgeData || {}); const createEventManager = () => { const eventMap = {}; return { eventMap, on(type, handler) { const eventTypeItem = (eventMap[type] = eventMap[type] || []); pushItem(eventTypeItem, handler); // return the off function return () => { eventMap[type] = filterItem(eventTypeItem, item => item !== handler); }; }, off(type, handler) { const handlers = eventMap[type]; if (!handlers) { return; } if (handler) { const index = handlers.indexOf(handler); index > -1 && handlers.splice(index, 1); } else { delete eventMap[type]; } }, emit(type, event) { const handlers = eventMap[type] || []; return mapItem(handlers, handler => handler(event)); } }; }; const decorateEvent = (onEvent, decoratedHandler) => { const emitter = createEventManager(); const eventType = uuid(); const eventReturn = onEvent(event => emitter.emit(eventType, event)); return (handler) => { emitter.on(eventType, event => { decoratedHandler(handler, event); }); return eventReturn; }; }; class FrameworkReadableState { constructor(state, key, dehydrate, exportState) { this.s = state; this.k = key; this.$dhy = dehydrate; this.$exp = exportState; } get v() { return this.$dhy(this.s); } get e() { return this.$exp(this.s); } } class FrameworkState extends FrameworkReadableState { constructor(state, key, dehydrate, exportState, update) { super(state, key, dehydrate, exportState); this.$upd = update; } set v(newValue) { this.$upd(this.s, newValue); } get v() { return this.$dhy(this.s); } } const defaultVisitorMeta = { authRole: null }; const defaultLoginMeta = { authRole: 'login' }; const defaultLogoutMeta = { authRole: 'logout' }; const defaultRefreshTokenMeta = { authRole: 'refreshToken' }; const checkMethodRole = ({ meta }, metaMatches) => { if (isPlainObject(meta)) { for (const key in meta) { if (Object.prototype.hasOwnProperty.call(meta, key)) { const matchedMetaItem = metaMatches[key]; if (instanceOf(matchedMetaItem, RegExp) ? matchedMetaItem.test(meta[key]) : meta[key] === matchedMetaItem) { return trueValue; } } } } return falseValue; }; const waitForTokenRefreshed = (method, waitingList) => newInstance(PromiseCls, resolve => { pushItem(waitingList, { method, resolve }); }); const callHandlerIfMatchesMeta = (method, authorizationInterceptor, defaultMeta, response) => { if (checkMethodRole(method, (authorizationInterceptor === null || authorizationInterceptor === void 0 ? void 0 : authorizationInterceptor.metaMatches) || defaultMeta)) { const handler = isFn(authorizationInterceptor) ? authorizationInterceptor : isPlainObject(authorizationInterceptor) && isFn(authorizationInterceptor.handler) ? authorizationInterceptor.handler : noop; return handler(response, method); } }; const refreshTokenIfExpired = async (method, waitingList, updateRefreshStatus, handlerParams, refreshToken, tokenRefreshing) => { // When the number of handle params is greater than 2, it means that this function is called from the response, and the original interface needs to be requested again. const fromResponse = len(handlerParams) >= 2; let isExpired = refreshToken === null || refreshToken === void 0 ? void 0 : refreshToken.isExpired(...handlerParams); // Compatible with synchronous and asynchronous functions if (instanceOf(isExpired, PromiseCls)) { isExpired = await isExpired; } if (isExpired) { try { // Make another judgment in the response to prevent multiple requests to refresh the token, intercept and wait for the token sent before the token refresh is completed. let intentToRefreshToken = trueValue; if (fromResponse && tokenRefreshing) { intentToRefreshToken = falseValue; // The requests waiting here indicate that the token is being refreshed. When they pass, there is no need to refresh the token again. await waitForTokenRefreshed(method, waitingList); } if (intentToRefreshToken) { updateRefreshStatus(trueValue); // Call refresh token await (refreshToken === null || refreshToken === void 0 ? void 0 : refreshToken.handler(...handlerParams)); updateRefreshStatus(falseValue); // After the token refresh is completed, the requests in the waiting list are notified. forEach(waitingList, ({ resolve }) => resolve()); } if (fromResponse) { // Because the original interface is being requested again, superposition with the previous request will result in repeated calls to transform, so it is necessary to leave transform empty to remove one call. const { config } = method; const methodTransformData = config.transform; config.transform = undefinedValue; const resentData = await method; config.transform = methodTransformData; return resentData; } } finally { updateRefreshStatus(falseValue); splice(waitingList, 0, len(waitingList)); // Clear waiting list } } }; const onResponded2Record = (onRespondedHandlers) => { let successHandler = undefinedValue; let errorHandler = undefinedValue; let onCompleteHandler = undefinedValue; if (isFn(onRespondedHandlers)) { successHandler = onRespondedHandlers; } else if (isPlainObject(onRespondedHandlers)) { const { onSuccess, onError, onComplete } = onRespondedHandlers; successHandler = isFn(onSuccess) ? onSuccess : successHandler; errorHandler = isFn(onError) ? onError : errorHandler; onCompleteHandler = isFn(onComplete) ? onComplete : onCompleteHandler; } return { onSuccess: successHandler, onError: errorHandler, onComplete: onCompleteHandler }; }; /** * Create a client-side token authentication interceptor * @param options Configuration parameters * @returns token authentication interceptor function */ const createClientTokenAuthentication = ({ visitorMeta, login, logout, refreshToken, assignToken = noop }) => { let tokenRefreshing = falseValue; const waitingList = []; const onAuthRequired = onBeforeRequest => async (method) => { const isVisitorRole = checkMethodRole(method, visitorMeta || defaultVisitorMeta); const isLoginRole = checkMethodRole(method, (login === null || login === void 0 ? void 0 : login.metaMatches) || defaultLoginMeta); // Ignored, login, and token refresh requests do not perform token authentication. if (!isVisitorRole && !isLoginRole && !checkMethodRole(method, (refreshToken === null || refreshToken === void 0 ? void 0 : refreshToken.metaMatches) || defaultRefreshTokenMeta)) { // If the token is being refreshed, wait for the refresh to complete before sending a request. if (tokenRefreshing) { await waitForTokenRefreshed(method, waitingList); } await refreshTokenIfExpired(method, waitingList, refreshing => { tokenRefreshing = refreshing; }, [method], refreshToken); } // Requests from non-guest and logged-in roles will enter the assignment token function if (!isVisitorRole && !isLoginRole) { await assignToken(method); } return onBeforeRequest === null || onBeforeRequest === void 0 ? void 0 : onBeforeRequest(method); }; const onResponseRefreshToken = originalResponded => { const respondedRecord = onResponded2Record(originalResponded); return { ...respondedRecord, onSuccess: async (response, method) => { await callHandlerIfMatchesMeta(method, login, defaultLoginMeta, response); await callHandlerIfMatchesMeta(method, logout, defaultLogoutMeta, response); return (respondedRecord.onSuccess || $self)(response, method); } }; }; return { waitingList, onAuthRequired, onResponseRefreshToken }; }; /** * Create a server-side token authentication interceptor * @param options Configuration parameters * @returns token authentication interceptor function */ const createServerTokenAuthentication = ({ visitorMeta, login, logout, refreshTokenOnSuccess, refreshTokenOnError, assignToken = noop }) => { let tokenRefreshing = falseValue; const waitingList = []; const onAuthRequired = onBeforeRequest => async (method) => { const isVisitorRole = checkMethodRole(method, visitorMeta || defaultVisitorMeta); const isLoginRole = checkMethodRole(method, (login === null || login === void 0 ? void 0 : login.metaMatches) || defaultLoginMeta); // Ignored, login, and token refresh requests do not perform token authentication. if (!isVisitorRole && !isLoginRole && !checkMethodRole(method, (refreshTokenOnSuccess === null || refreshTokenOnSuccess === void 0 ? void 0 : refreshTokenOnSuccess.metaMatches) || defaultRefreshTokenMeta) && !checkMethodRole(method, (refreshTokenOnError === null || refreshTokenOnError === void 0 ? void 0 : refreshTokenOnError.metaMatches) || defaultRefreshTokenMeta)) { // If the token is being refreshed, wait for the refresh to complete before sending a request. if (tokenRefreshing) { await waitForTokenRefreshed(method, waitingList); } } if (!isVisitorRole && !isLoginRole) { await assignToken(method); } return onBeforeRequest === null || onBeforeRequest === void 0 ? void 0 : onBeforeRequest(method); }; const onResponseRefreshToken = onRespondedHandlers => { const respondedRecord = onResponded2Record(onRespondedHandlers); return { ...respondedRecord, onSuccess: async (response, method) => { if (!checkMethodRole(method, visitorMeta || defaultVisitorMeta) && !checkMethodRole(method, (login === null || login === void 0 ? void 0 : login.metaMatches) || defaultLoginMeta) && !checkMethodRole(method, (refreshTokenOnSuccess === null || refreshTokenOnSuccess === void 0 ? void 0 : refreshTokenOnSuccess.metaMatches) || defaultRefreshTokenMeta)) { const dataResent = await refreshTokenIfExpired(method, waitingList, refreshing => { tokenRefreshing = refreshing; }, [response, method], refreshTokenOnSuccess, tokenRefreshing); if (dataResent) { return dataResent; } } await callHandlerIfMatchesMeta(method, login, defaultLoginMeta, response); await callHandlerIfMatchesMeta(method, logout, defaultLogoutMeta, response); return (respondedRecord.onSuccess || $self)(response, method); }, onError: async (error, method) => { if (!checkMethodRole(method, visitorMeta || defaultVisitorMeta) && !checkMethodRole(method, (login === null || login === void 0 ? void 0 : login.metaMatches) || defaultLoginMeta) && !checkMethodRole(method, (refreshTokenOnError === null || refreshTokenOnError === void 0 ? void 0 : refreshTokenOnError.metaMatches) || defaultRefreshTokenMeta)) { const dataResent = await refreshTokenIfExpired(method, waitingList, refreshing => { tokenRefreshing = refreshing; }, [error, method], refreshTokenOnError, tokenRefreshing); if (dataResent) { return dataResent; } } return (respondedRecord.onError || noop)(error, method); } }; }; return { waitingList, onAuthRequired, onResponseRefreshToken }; }; /** * Compatible functions, throwing parameters * @param error mistake */ const throwFn = (error) => { throw error; }; function useCallback(onCallbackChange = noop) { let callbacks = []; const setCallback = (fn) => { if (!callbacks.includes(fn)) { callbacks.push(fn); onCallbackChange(callbacks); } // Return unregister function return () => { callbacks = filterItem(callbacks, e => e !== fn); onCallbackChange(callbacks); }; }; const triggerCallback = (...args) => { if (callbacks.length > 0) { return forEach(callbacks, fn => fn(...args)); } }; const removeAllCallback = () => { callbacks = []; onCallbackChange(callbacks); }; return [setCallback, triggerCallback, removeAllCallback]; } /** * Create a debounce function and trigger the function immediately when delay is 0 * Scenario: When calling useWatcher and setting immediate to true, the first call must be executed immediately, otherwise it will cause a delayed call * @param {GeneralFn} fn callback function * @param {number|(...args: any[]) => number} delay Delay description, dynamic delay can be achieved when set as a function * @returns Delayed callback function */ const debounce = (fn, delay) => { let timer = nullValue; return function debounceFn(...args) { const bindFn = fn.bind(this, ...args); const delayMill = isNumber(delay) ? delay : delay(...args); timer && clearTimeoutTimer(timer); if (delayMill > 0) { timer = setTimeoutFn(bindFn, delayMill); } else { bindFn(); } }; }; /** * Get the request method object * @param methodHandler Request method handle * @param args Method call parameters * @returns request method object */ const getHandlerMethod = (methodHandler, args = []) => { const methodInstance = isFn(methodHandler) ? methodHandler(...args) : methodHandler; const assert = createAssert('scene'); assert(instanceOf(methodInstance, alova.Method), 'hook handler must be a method instance or a function that returns method instance'); return methodInstance; }; /** * Convert each value of the object and return the new object * @param obj object * @param callback callback function * @returns converted object */ const mapObject = (obj, callback) => { const ret = {}; for (const key in obj) { ret[key] = callback(obj[key], key, obj); } return ret; }; var EnumHookType; (function (EnumHookType) { EnumHookType[EnumHookType["USE_REQUEST"] = 1] = "USE_REQUEST"; EnumHookType[EnumHookType["USE_WATCHER"] = 2] = "USE_WATCHER"; EnumHookType[EnumHookType["USE_FETCHER"] = 3] = "USE_FETCHER"; })(EnumHookType || (EnumHookType = {})); /** * create simple and unified, framework-independent states creators and handlers. * @param statesHook states hook from `promiseStatesHook` function of alova * @param referingObject refering object exported from `promiseStatesHook` function * @returns simple and unified states creators and handlers */ function statesHookHelper(statesHook, referingObject = { trackedKeys: {}, bindError: falseValue, initialRequest: falseValue, ...injectReferingObject() }) { const ref = (initialValue) => (statesHook.ref ? statesHook.ref(initialValue) : { current: initialValue }); referingObject = ref(referingObject).current; const exportState = (state) => (statesHook.export || $self)(state, referingObject); const memorize = (fn) => { if (!isFn(statesHook.memorize)) { return fn; } const memorizedFn = statesHook.memorize(fn); memorizedFn.memorized = trueValue; return memorizedFn; }; const { dehydrate } = statesHook; // For performance reasons, only value is different, and the key is tracked can be updated. const update = (newValue, state, key) => newValue !== dehydrate(state, key, referingObject) && referingObject.trackedKeys[key] && statesHook.update(newValue, state, key, referingObject); const mapDeps = (deps) => mapItem(deps, item => (instanceOf(item, FrameworkReadableState) ? item.e : item)); const createdStateList = []; // key of deps on computed const depKeys = {}; return { create: (initialValue, key) => { pushItem(createdStateList, key); // record the keys of created states. return newInstance((FrameworkState), statesHook.create(initialValue, key, referingObject), key, state => dehydrate(state, key, referingObject), exportState, (state, newValue) => update(newValue, state, key)); }, computed: (getter, depList, key) => { // Collect all dependencies in computed forEach(depList, dep => { if (dep.k) { depKeys[dep.k] = trueValue; } }); return newInstance((FrameworkReadableState), statesHook.computed(getter, mapDeps(depList), key, referingObject), key, state => dehydrate(state, key, referingObject), exportState); }, effectRequest: (effectRequestParams) => statesHook.effectRequest(effectRequestParams, referingObject), ref, watch: (source, callback) => statesHook.watch(mapDeps(source), callback, referingObject), onMounted: (callback) => statesHook.onMounted(callback, referingObject), onUnmounted: (callback) => statesHook.onUnmounted(callback, referingObject), memorize, /** * refering object that sharing some value with this object. */ __referingObj: referingObject, /** * expose provider for specified use hook. * @param object object that contains state proxy, framework state, operating function and event binder. * @returns provider component. */ exposeProvider: (object) => { const provider = {}; const originalStatesMap = {}; const stateKeys = []; for (const key in object) { const value = object[key]; const isValueFunction = isFn(value); // if it's a memorized function, don't memorize it any more, add it to provider directly. // if it's start with `on`, that indicates it is an event binder, we should define a new function which return provider object. // if it's a common function, add it to provider with memorize mode. // Note that: in some situation, state is a function such as solid's signal, and state value is set to function in react, the state will be detected as a function. so we should check whether the key is in `trackedKeys` if (isValueFunction && !referingObject.trackedKeys[key]) { provider[key] = key.startsWith('on') ? (...args) => { value(...args); // eslint-disable-next-line return completedProvider; } : value.memorized ? value : memorize(value); } else { // collect states of current exposures, and open tracked for these ststes if (!includes(['uploading', 'downloading'], key) && !key.startsWith('__')) { pushItem(stateKeys, key); } const isFrameworkState = instanceOf(value, FrameworkReadableState); if (isFrameworkState) { originalStatesMap[key] = value.s; } // otherwise, it's a state proxy or framework state, add it to provider with getter mode. ObjectCls.defineProperty(provider, key, { get: () => { // record the key that is being tracked. referingObject.trackedKeys[key] = trueValue; return isFrameworkState ? value.e : value; }, // set need to set an function, // otherwise it will throw `TypeError: Cannot set property __referingObj of #<Object> which has only a getter` when setting value set: noop, enumerable: trueValue, configurable: trueValue }); } } const { update: nestedHookUpdate, __proxyState: nestedProxyState } = provider; // reset the tracked keys and bingError flag, so that the nest hook providers can be initialized. // Always track the dependencies in computed referingObject.trackedKeys = { ...depKeys }; referingObject.bindError = falseValue; const { then: providerThen } = provider; const extraProvider = { // expose referingObject automatically. __referingObj: referingObject, // the new updating function that can update the new states and nested hook states. update: memorize((newStates) => { objectKeys(newStates).forEach(key => { if (includes(createdStateList, key)) { update(newStates[key], originalStatesMap[key], key); } else if (key in provider && isFn(nestedHookUpdate)) { nestedHookUpdate({ [key]: newStates[key] }); } }); }), __proxyState: memorize((key) => { if (includes(createdStateList, key) && instanceOf(object[key], FrameworkReadableState)) { // need to tag the key that is being tracked so that it can be updated with `state.v = xxx`. referingObject.trackedKeys[key] = trueValue; return object[key]; } return nestedProxyState(key); }), /** * send and wait for responding with `await` * this is always used in `nuxt3` and suspense in vue3 * @example * ```js * const { loading, data, error } = await useRequest(...); * ``` */ then(onfulfilled, onrejected) { // open all the states to track. forEach(stateKeys, key => { referingObject.trackedKeys[key] = trueValue; }); const handleFullfilled = () => { // eslint-disable-next-line deleteAttr(completedProvider, 'then'); // eslint-disable-next-line onfulfilled(completedProvider); }; isFn(providerThen) ? providerThen(handleFullfilled, onrejected) : handleFullfilled(); } }; const completedProvider = objAssign(provider, extraProvider); return completedProvider; }, /** * transform state proxies to object. * @param states proxy array of framework states * @param filterKey filter key of state proxy * @returns an object that contains the states of target form */ objectify: (states, filterKey) => states.reduce((result, item) => { result[item.k] = filterKey ? item[filterKey] : item; return result; }, {}), transformState2Proxy: (state, key) => newInstance((FrameworkState), state, key, state => dehydrate(state, key, referingObject), exportState, (state, newValue) => update(newValue, state, key)) }; } const coreAssert = createAssert(''); const requestHookAssert = createAssert('useRequest'); const watcherHookAssert = createAssert('useWatcher'); const fetcherHookAssert = createAssert('useFetcher'); const coreHookAssert = (hookType) => ({ [EnumHookType.USE_REQUEST]: requestHookAssert, [EnumHookType.USE_WATCHER]: watcherHookAssert, [EnumHookType.USE_FETCHER]: fetcherHookAssert })[hookType]; /** * Assert whether it is a method instance * @param methodInstance method instance */ const assertMethod = (assert, methodInstance) => assert(instanceOf(methodInstance, alova.Method), 'expected a method instance.'); const KEY_SUCCESS = 'success'; const KEY_ERROR = 'error'; const KEY_COMPLETE = 'complete'; var createHook = (ht, c, eventManager, ro) => ({ /** The method instance of the last request */ m: undefinedValue, /** sent method keys */ rf: {}, /** frontStates */ fs: {}, /** eventManager */ em: eventManager, /** hookType, useRequest=1, useWatcher=2, useFetcher=3 */ ht, /** hook config */ c, /** referingObject */ ro, /** merged states */ ms: {} }); // base event class AlovaEventBase { constructor(method, args) { this.method = method; this.args = args; } clone() { return { ...this }; } static spawn(method, args) { return newInstance((AlovaEventBase), method, args); } } class AlovaSuccessEvent extends AlovaEventBase { constructor(base, data, fromCache) { super(base.method, base.args); this.data = data; this.fromCache = fromCache; } } class AlovaErrorEvent extends AlovaEventBase { constructor(base, error) { super(base.method, base.args); this.error = error; } } class AlovaCompleteEvent extends AlovaEventBase { constructor(base, status, data, fromCache, error) { super(base.method, base.args); this.status = status; this.data = data; this.fromCache = status === 'error' ? false : fromCache; this.error = error; } } /** Sq top level events */ class SQEvent { constructor(behavior, method, silentMethod) { this.behavior = behavior; this.method = method; this.silentMethod = silentMethod; } } /** Sq global events */ class GlobalSQEvent extends SQEvent { constructor(behavior, method, silentMethod, queueName, retryTimes) { super(behavior, method, silentMethod); this.queueName = queueName; this.retryTimes = retryTimes; } } class GlobalSQSuccessEvent extends GlobalSQEvent { constructor(behavior, method, silentMethod, queueName, retryTimes, data, vDataResponse) { super(behavior, method, silentMethod, queueName, retryTimes); this.data = data; this.vDataResponse = vDataResponse; } } class GlobalSQErrorEvent extends GlobalSQEvent { constructor(behavior, method, silentMethod, queueName, retryTimes, error, retryDelay) { super(behavior, method, silentMethod, queueName, retryTimes); this.error = error; this.retryDelay = retryDelay; } } class GlobalSQFailEvent extends GlobalSQEvent { constructor(behavior, method, silentMethod, queueName, retryTimes, error) { super(behavior, method, silentMethod, queueName, retryTimes); this.error = error; } } /** Sq event */ class ScopedSQEvent extends SQEvent { constructor(behavior, method, silentMethod, args) { super(behavior, method, silentMethod); this.args = args; } } class ScopedSQSuccessEvent extends ScopedSQEvent { constructor(behavior, method, silentMethod, args, data) { super(behavior, method, silentMethod, args); this.data = data; } } class ScopedSQErrorEvent extends ScopedSQEvent { constructor(behavior, method, silentMethod, args, error) { super(behavior, method, silentMethod, args); this.error = error; } } class ScopedSQRetryEvent extends ScopedSQEvent { constructor(behavior, method, silentMethod, args, retryTimes, retryDelay) { super(behavior, method, silentMethod, args); this.retryTimes = retryTimes; this.retryDelay = retryDelay; } } class ScopedSQCompleteEvent extends ScopedSQEvent { constructor(behavior, method, silentMethod, args, status, data, error) { super(behavior, method, silentMethod, args); this.status = status; this.data = data; this.error = error; } } class RetriableRetryEvent extends AlovaEventBase { constructor(base, retryTimes, retryDelay) { super(base.method, base.args); this.retryTimes = retryTimes; this.retryDelay = retryDelay; } } class RetriableFailEvent extends AlovaErrorEvent { constructor(base, error, retryTimes) { super(base, error); this.retryTimes = retryTimes; } } const defaultMiddleware = (_, next) => next(); const stateCache = {}; /** * @description Get State cache data * @param baseURL Base URL * @param key Request key value * @returns Cached response data, if not returned {} */ const getStateCache = (namespace, key) => { const cachedState = stateCache[namespace] || {}; return cachedState[key] ? Array.from(cachedState[key]) : []; }; /** * @description Set State cache data * @param baseURL Base URL * @param key Request key value * @param data cache data */ const setStateCache = (namespace, key, hookInstance) => { const cachedState = (stateCache[namespace] = stateCache[namespace] || {}); if (!cachedState[key]) { cachedState[key] = newInstance((Set)); } cachedState[key].add(hookInstance); }; /** * @description Clear State cache data * @param baseURL Base URL * @param key Request key value */ const removeStateCache = (namespace, key, hookInstance) => { const cachedState = stateCache[namespace]; const hookSet = cachedState[key]; if (cachedState && hookSet) { hookInstance ? hookSet.delete(hookInstance) : hookSet.clear(); if (hookSet.size === 0) { deleteAttr(cachedState, key); } } }; /** * Unified processing of request logic for useRequest/useWatcher/useFetcher and other request hook functions * @param hookInstance hook instance * @param methodHandler Request method object or get function * @param sendCallingArgs send function parameters * @returns Request status */ function useHookToSendRequest(hookInstance, methodHandler, sendCallingArgs = []) { const currentHookAssert = coreHookAssert(hookInstance.ht); let methodInstance = getHandlerMethod$1(methodHandler, currentHookAssert, sendCallingArgs); const { fs: frontStates, ht: hookType, c: useHookConfig } = hookInstance; const { loading: loadingState, data: dataState, error: errorState } = frontStates; const isFetcher = hookType === EnumHookType.USE_FETCHER; const { force: forceRequest = falseValue, middleware = defaultMiddleware } = useHookConfig; const alovaInstance = getContext(methodInstance); const { id } = alovaInstance; // If it is a silent request, on success will be called directly after the request, on error will not be triggered, and progress will not be updated. const methodKey = getMethodInternalKey(methodInstance); const { abortLast = trueValue } = useHookConfig; const isFirstRequest = !hookInstance.m; hookInstance.m = methodInstance; return (async () => { // Initialize status data, which does not need to be loaded when pul