@ubean/server 0.1.13 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics-entry.d.ts +2 -0
- package/dist/analytics-entry.js +2 -0
- package/dist/cache-C84ix1Vq.js +173 -0
- package/dist/cache-b-MZlyv0.d.ts +48 -0
- package/dist/cache-directive-C1Nekkza.js +304 -0
- package/dist/cache-directive-CAxJAQyE.d.ts +175 -0
- package/dist/cache-directive.d.ts +2 -0
- package/dist/cache-directive.js +2 -0
- package/dist/cache-entry.d.ts +3 -0
- package/dist/cache-entry.js +3 -0
- package/dist/cron-entry.d.ts +2 -0
- package/dist/cron-entry.js +2 -0
- package/dist/cron-scheduler-BF33PPn4.d.ts +77 -0
- package/dist/cron-scheduler-BVuXv7nn.js +258 -0
- package/dist/database-CfpFznl-.d.ts +67 -0
- package/dist/database-DNrY44SQ.js +352 -0
- package/dist/database.d.ts +2 -0
- package/dist/database.js +2 -0
- package/dist/email-BjfRiR9b.js +354 -0
- package/dist/email-BvpEuNn_.d.ts +226 -0
- package/dist/email.d.ts +2 -0
- package/dist/email.js +2 -0
- package/dist/feature-flags-CdLwsMD2.js +657 -0
- package/dist/feature-flags-DWkS6p0D.d.ts +386 -0
- package/dist/fetch-memo-rbkxxnW4.js +338 -0
- package/dist/index.d.ts +183 -488
- package/dist/index.js +352 -2023
- package/dist/middleware.d.ts +2 -0
- package/dist/middleware.js +3 -0
- package/dist/observability-Cio6Qq1H.js +339 -0
- package/dist/observability-DUNUEjj3.d.ts +70 -0
- package/dist/observability.d.ts +2 -0
- package/dist/observability.js +2 -0
- package/dist/queue-Bwzi3mhK.js +210 -0
- package/dist/queue-GOfTAWlz.d.ts +55 -0
- package/dist/queue.d.ts +2 -0
- package/dist/queue.js +2 -0
- package/dist/realtime.d.ts +2 -0
- package/dist/realtime.js +2 -0
- package/dist/security.d.ts +2 -0
- package/dist/security.js +2 -0
- package/dist/sessions-BLqFFQTL.d.ts +217 -0
- package/dist/sessions-BsBsyFAG.js +450 -0
- package/dist/single-flight-BJyhDLdU.d.ts +422 -0
- package/dist/single-flight-mJ4ZKbx1.js +715 -0
- package/dist/sse-Ct72zhic.d.ts +95 -0
- package/dist/sse-a6Ky9Vcl.js +310 -0
- package/dist/static-DPHaovQe.js +90 -0
- package/dist/static-K2dRvjpS.d.ts +11 -0
- package/dist/static.d.ts +2 -0
- package/dist/static.js +2 -0
- package/dist/storage-BZLMaqHr.js +162 -0
- package/dist/storage-QdlPtPtR.d.ts +48 -0
- package/dist/storage.d.ts +2 -0
- package/dist/storage.js +2 -0
- package/package.json +68 -6
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { $ as getGlobalAnalyticsProvider, A as removeExperiment, B as AnalyticsProvider, C as extractFlagContext, D as getVariantFromContext, E as getVariantAssignment, F as useFlags, G as createAnalyticsMiddleware, H as AnalyticsRecord, I as AnalyticsContext, J as createMockAnalyticsProvider, K as createLogAnalyticsProvider, L as AnalyticsEvent, M as setGlobalFeatureFlagStore, N as useExperiments, O as listExperimentNames, P as useFlagContext, Q as getAnalyticsProvider, R as AnalyticsMiddlewareOptions, S as evaluateFlagWithReason, T as getVariant, U as AnalyticsTrackOptions, V as AnalyticsProviderOptions, W as clearAnalyticsProviders, X as defineAnalyticsProvider, Y as defineAnalytics, Z as extractAnalyticsContext, _ as createMemoryFeatureFlagStore, a as FeatureFlagOptions, at as trackRaw, b as evaluateFlag, c as FlagContext, d as FlagValue, et as listAnalyticsProviders, f as Segment, g as createFeatureFlagsMiddleware, h as clearFeatureFlags, i as FeatureFlagDefinition, it as trackPageView, j as removeFeatureFlag, k as listFlagNames, l as FlagEvaluation, m as Variant, n as ExperimentDefinition, nt as setGlobalAnalyticsProvider, o as FeatureFlagStore, ot as unregisterAnalyticsProvider, p as SegmentRule, q as createMemoryAnalyticsProvider, r as ExperimentOptions, rt as trackEvent, s as FeatureFlagsMiddlewareOptions, st as useAnalytics, t as ExperimentAssignment, tt as registerAnalyticsProvider, u as FlagKind, v as defineExperiment, w as getGlobalFeatureFlagStore, x as evaluateFlagFromContext, y as defineFeatureFlag, z as AnalyticsProperties } from "./feature-flags-DWkS6p0D.js";
|
|
2
|
+
export { AnalyticsContext, AnalyticsEvent, AnalyticsMiddlewareOptions, AnalyticsProperties, AnalyticsProvider, AnalyticsProviderOptions, AnalyticsRecord, AnalyticsTrackOptions, ExperimentAssignment, ExperimentDefinition, ExperimentOptions, FeatureFlagDefinition, FeatureFlagOptions, FeatureFlagStore, FeatureFlagsMiddlewareOptions, FlagContext, FlagEvaluation, FlagKind, FlagValue, Segment, SegmentRule, Variant, clearAnalyticsProviders, clearFeatureFlags, createAnalyticsMiddleware, createFeatureFlagsMiddleware, createLogAnalyticsProvider, createMemoryAnalyticsProvider, createMemoryFeatureFlagStore, createMockAnalyticsProvider, defineAnalytics, defineAnalyticsProvider, defineExperiment, defineFeatureFlag, evaluateFlag, evaluateFlagFromContext, evaluateFlagWithReason, extractAnalyticsContext, extractFlagContext, getAnalyticsProvider, getGlobalAnalyticsProvider, getGlobalFeatureFlagStore, getVariant, getVariantAssignment, getVariantFromContext, listAnalyticsProviders, listExperimentNames, listFlagNames, registerAnalyticsProvider, removeExperiment, removeFeatureFlag, setGlobalAnalyticsProvider, setGlobalFeatureFlagStore, trackEvent, trackPageView, trackRaw, unregisterAnalyticsProvider, useAnalytics, useExperiments, useFlagContext, useFlags };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { A as getAnalyticsProvider, C as createAnalyticsMiddleware, D as defineAnalytics, E as createMockAnalyticsProvider, F as trackEvent, I as trackPageView, L as trackRaw, M as listAnalyticsProviders, N as registerAnalyticsProvider, O as defineAnalyticsProvider, P as setGlobalAnalyticsProvider, R as unregisterAnalyticsProvider, S as clearAnalyticsProviders, T as createMemoryAnalyticsProvider, _ as removeFeatureFlag, a as defineFeatureFlag, b as useFlagContext, c as evaluateFlagWithReason, d as getVariant, f as getVariantAssignment, g as removeExperiment, h as listFlagNames, i as defineExperiment, j as getGlobalAnalyticsProvider, k as extractAnalyticsContext, l as extractFlagContext, m as listExperimentNames, n as createFeatureFlagsMiddleware, o as evaluateFlag, p as getVariantFromContext, r as createMemoryFeatureFlagStore, s as evaluateFlagFromContext, t as clearFeatureFlags, u as getGlobalFeatureFlagStore, v as setGlobalFeatureFlagStore, w as createLogAnalyticsProvider, x as useFlags, y as useExperiments, z as useAnalytics } from "./feature-flags-CdLwsMD2.js";
|
|
2
|
+
export { clearAnalyticsProviders, clearFeatureFlags, createAnalyticsMiddleware, createFeatureFlagsMiddleware, createLogAnalyticsProvider, createMemoryAnalyticsProvider, createMemoryFeatureFlagStore, createMockAnalyticsProvider, defineAnalytics, defineAnalyticsProvider, defineExperiment, defineFeatureFlag, evaluateFlag, evaluateFlagFromContext, evaluateFlagWithReason, extractAnalyticsContext, extractFlagContext, getAnalyticsProvider, getGlobalAnalyticsProvider, getGlobalFeatureFlagStore, getVariant, getVariantAssignment, getVariantFromContext, listAnalyticsProviders, listExperimentNames, listFlagNames, registerAnalyticsProvider, removeExperiment, removeFeatureFlag, setGlobalAnalyticsProvider, setGlobalFeatureFlagStore, trackEvent, trackPageView, trackRaw, unregisterAnalyticsProvider, useAnalytics, useExperiments, useFlagContext, useFlags };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
//#region src/cache.ts
|
|
2
|
+
function createMemoryStore(maxEntries = 200) {
|
|
3
|
+
const store = /* @__PURE__ */ new Map();
|
|
4
|
+
function evict() {
|
|
5
|
+
if (store.size <= maxEntries) return;
|
|
6
|
+
const entries = Array.from(store.entries()).sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
7
|
+
const removeCount = Math.ceil(entries.length * .2);
|
|
8
|
+
for (let i = 0; i < removeCount; i++) store.delete(entries[i][0]);
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
store,
|
|
12
|
+
async get(key) {
|
|
13
|
+
const entry = store.get(key);
|
|
14
|
+
if (!entry) return void 0;
|
|
15
|
+
if (Date.now() > entry.expiresAt) return;
|
|
16
|
+
return entry;
|
|
17
|
+
},
|
|
18
|
+
async peek(key) {
|
|
19
|
+
return store.get(key);
|
|
20
|
+
},
|
|
21
|
+
async set(key, entry, ttl) {
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
store.set(key, {
|
|
24
|
+
...entry,
|
|
25
|
+
createdAt: now,
|
|
26
|
+
expiresAt: now + ttl * 1e3
|
|
27
|
+
});
|
|
28
|
+
evict();
|
|
29
|
+
},
|
|
30
|
+
async delete(key) {
|
|
31
|
+
return store.delete(key);
|
|
32
|
+
},
|
|
33
|
+
async clear() {
|
|
34
|
+
store.clear();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
let globalStore = null;
|
|
39
|
+
function useCacheStore(store) {
|
|
40
|
+
if (store) {
|
|
41
|
+
globalStore = store;
|
|
42
|
+
return store;
|
|
43
|
+
}
|
|
44
|
+
if (!globalStore) globalStore = createMemoryStore();
|
|
45
|
+
return globalStore;
|
|
46
|
+
}
|
|
47
|
+
function clearCacheStore() {
|
|
48
|
+
globalStore = null;
|
|
49
|
+
}
|
|
50
|
+
function buildCacheKey(c) {
|
|
51
|
+
const url = new URL(c.req.url);
|
|
52
|
+
return `${c.req.method}:${url.pathname}${url.search}`;
|
|
53
|
+
}
|
|
54
|
+
function serializeResponse(res) {
|
|
55
|
+
return res.arrayBuffer().then((body) => {
|
|
56
|
+
const headers = {};
|
|
57
|
+
res.headers.forEach((v, k) => {
|
|
58
|
+
const kl = k.toLowerCase();
|
|
59
|
+
if (kl !== "set-cookie" && kl !== "authorization") headers[k] = v;
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
body,
|
|
63
|
+
headers,
|
|
64
|
+
status: res.status,
|
|
65
|
+
statusText: res.statusText
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function entryToResponse(entry) {
|
|
70
|
+
const headers = new Headers(entry.headers);
|
|
71
|
+
headers.set("X-Cache", "HIT");
|
|
72
|
+
headers.set("Age", String(Math.max(0, Math.floor((Date.now() - entry.createdAt) / 1e3))));
|
|
73
|
+
return new Response(entry.body, {
|
|
74
|
+
status: entry.status,
|
|
75
|
+
statusText: entry.statusText,
|
|
76
|
+
headers
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function isCacheableRequest(c) {
|
|
80
|
+
if (c.req.method !== "GET" && c.req.method !== "HEAD") return false;
|
|
81
|
+
if (c.req.header("authorization")) return false;
|
|
82
|
+
if (c.req.header("cookie")) {
|
|
83
|
+
if (!(c.req.header("cache-control") || "").includes("public")) return false;
|
|
84
|
+
}
|
|
85
|
+
const cc = c.req.header("cache-control");
|
|
86
|
+
if (cc && (cc.includes("no-cache") || cc.includes("no-store"))) return false;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
function isCacheableResponse(res) {
|
|
90
|
+
if (res.status !== 200) return false;
|
|
91
|
+
if (res.headers.get("set-cookie")) return false;
|
|
92
|
+
const cc = res.headers.get("cache-control");
|
|
93
|
+
if (cc && (cc.includes("private") || cc.includes("no-store"))) return false;
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
function matchPath(pattern, path) {
|
|
97
|
+
const DOUBLE = "__DWC__";
|
|
98
|
+
const SINGLE = "__SWC__";
|
|
99
|
+
let s = pattern;
|
|
100
|
+
s = s.replace(/\/\*\*/g, `/${DOUBLE}`);
|
|
101
|
+
s = s.replace(/\*/g, SINGLE);
|
|
102
|
+
s = s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
103
|
+
s = s.replace(new RegExp(SINGLE, "g"), "[^/]*");
|
|
104
|
+
s = s.replace(new RegExp(`/${DOUBLE}`, "g"), "(?:/.*)?");
|
|
105
|
+
return new RegExp(`^${s}$`).test(path);
|
|
106
|
+
}
|
|
107
|
+
function createCacheMiddleware(options = {}) {
|
|
108
|
+
const store = options.store || useCacheStore();
|
|
109
|
+
function findTtl(path) {
|
|
110
|
+
if (!options.rules) return null;
|
|
111
|
+
const patterns = Object.keys(options.rules);
|
|
112
|
+
for (const pattern of patterns) if (matchPath(pattern, path)) return options.rules[pattern].ttl;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return async function cacheMiddleware(c, next) {
|
|
116
|
+
const path = new URL(c.req.url).pathname;
|
|
117
|
+
const ttl = findTtl(path);
|
|
118
|
+
if (ttl != null && ttl > 0 && isCacheableRequest(c)) {
|
|
119
|
+
const key = buildCacheKey(c);
|
|
120
|
+
const existing = await store.get(key);
|
|
121
|
+
if (existing) return entryToResponse(existing);
|
|
122
|
+
}
|
|
123
|
+
await next();
|
|
124
|
+
if (ttl == null || ttl <= 0) return;
|
|
125
|
+
const res = c.res;
|
|
126
|
+
if (!res || !isCacheableRequest(c) || !isCacheableResponse(res)) return;
|
|
127
|
+
const serialized = await serializeResponse(res.clone());
|
|
128
|
+
await store.set(buildCacheKey(c), serialized, ttl);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function cachedEventHandler(handler, options = { ttl: 60 }) {
|
|
132
|
+
const store = useCacheStore();
|
|
133
|
+
const ttl = options.ttl;
|
|
134
|
+
const keyBase = options.name;
|
|
135
|
+
return (async (c) => {
|
|
136
|
+
const key = keyBase || buildCacheKey(c);
|
|
137
|
+
if (ttl > 0 && isCacheableRequest(c)) {
|
|
138
|
+
const existing = await store.get(key);
|
|
139
|
+
if (existing) return entryToResponse(existing);
|
|
140
|
+
}
|
|
141
|
+
const res = await handler(c);
|
|
142
|
+
if (ttl > 0 && isCacheableResponse(res)) {
|
|
143
|
+
const serialized = await serializeResponse(res.clone());
|
|
144
|
+
await store.set(key, serialized, ttl);
|
|
145
|
+
}
|
|
146
|
+
return res;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async function invalidateRouteCache(keyPattern) {
|
|
150
|
+
const store = useCacheStore();
|
|
151
|
+
if (!keyPattern) {
|
|
152
|
+
await store.clear();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (typeof keyPattern === "string") {
|
|
156
|
+
await store.delete(keyPattern);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const memStore = store;
|
|
160
|
+
if (memStore.store) {
|
|
161
|
+
for (const k of Array.from(memStore.store.keys())) if (keyPattern.test(k)) await store.delete(k);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function resolveRouteCacheRules(routeRules) {
|
|
165
|
+
const result = {};
|
|
166
|
+
for (const [path, rule] of Object.entries(routeRules)) if (rule.cache && rule.cache.ttl != null) result[path] = {
|
|
167
|
+
ttl: rule.cache.ttl,
|
|
168
|
+
swr: rule.cache.swr
|
|
169
|
+
};
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
export { invalidateRouteCache as a, createMemoryStore as i, clearCacheStore as n, resolveRouteCacheRules as o, createCacheMiddleware as r, useCacheStore as s, cachedEventHandler as t };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Context, Next } from "hono";
|
|
2
|
+
import { RouteRule } from "@ubean/shared";
|
|
3
|
+
//#region src/cache.d.ts
|
|
4
|
+
interface CacheEntry {
|
|
5
|
+
body: ArrayBuffer;
|
|
6
|
+
headers: Record<string, string>;
|
|
7
|
+
status: number;
|
|
8
|
+
statusText: string;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
}
|
|
12
|
+
interface CacheStore {
|
|
13
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
14
|
+
set(key: string, entry: Omit<CacheEntry, 'createdAt' | 'expiresAt'>, ttl: number): Promise<void>;
|
|
15
|
+
delete(key: string): Promise<boolean>;
|
|
16
|
+
clear(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* 返回 entry(无论是否过期),不进行过期清理(P9-03 ISR SWR 用)。
|
|
19
|
+
*
|
|
20
|
+
* 不实现此方法的存储后端将不支持 ISR SWR(自动降级为同步重新生成)。
|
|
21
|
+
*/
|
|
22
|
+
peek?(key: string): Promise<CacheEntry | undefined>;
|
|
23
|
+
/**
|
|
24
|
+
* 内部 Map(仅内存存储实现暴露)。供 `invalidateCachePattern` /
|
|
25
|
+
* `isIsrEntryStale` 等函数通过 `memStore.store` 访问底层存储进行模式匹配。
|
|
26
|
+
*
|
|
27
|
+
* 其他后端(Redis 等)不实现此属性,模式匹配将返回 0/false。
|
|
28
|
+
*/
|
|
29
|
+
store?: Map<string, CacheEntry>;
|
|
30
|
+
}
|
|
31
|
+
declare function createMemoryStore(maxEntries?: number): CacheStore;
|
|
32
|
+
declare function useCacheStore(store?: CacheStore): CacheStore;
|
|
33
|
+
declare function clearCacheStore(): void;
|
|
34
|
+
interface CacheRule {
|
|
35
|
+
ttl: number;
|
|
36
|
+
swr?: boolean | number;
|
|
37
|
+
name?: string;
|
|
38
|
+
}
|
|
39
|
+
declare function createCacheMiddleware(options?: {
|
|
40
|
+
store?: CacheStore;
|
|
41
|
+
rules?: Record<string, CacheRule>;
|
|
42
|
+
defaultTtl?: number;
|
|
43
|
+
}): (c: Context, next: Next) => Promise<Response | undefined>;
|
|
44
|
+
declare function cachedEventHandler<T extends (c: Context) => Response | Promise<Response>>(handler: T, options?: CacheRule): T;
|
|
45
|
+
declare function invalidateRouteCache(keyPattern?: string | RegExp): Promise<void>;
|
|
46
|
+
declare function resolveRouteCacheRules(routeRules: Record<string, RouteRule>): Record<string, CacheRule>;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { clearCacheStore as a, invalidateRouteCache as c, cachedEventHandler as i, resolveRouteCacheRules as l, CacheRule as n, createCacheMiddleware as o, CacheStore as r, createMemoryStore as s, CacheEntry as t, useCacheStore as u };
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { o as revalidateDataCachePath, s as revalidateDataCacheTag, t as clearFetchDataCache } from "./fetch-memo-rbkxxnW4.js";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
//#region src/cache-directive.ts
|
|
4
|
+
/**
|
|
5
|
+
* 组件级缓存 —— 运行时原语
|
|
6
|
+
*
|
|
7
|
+
* 通过 `defineCachedFunction()` 显式包装异步函数,缓存其返回值。
|
|
8
|
+
*
|
|
9
|
+
* 设计要点:
|
|
10
|
+
* - **值缓存**(非 HTTP 响应缓存):缓存异步函数的返回值(JSON 可序列化),
|
|
11
|
+
* 与 `cache.ts`(HTTP 响应缓存)解耦。
|
|
12
|
+
* - **AsyncLocalStorage 作用域**:`cacheLife()` / `cacheTag()` 通过 ALS 写入
|
|
13
|
+
* 当前缓存的执行作用域,无需手动传递 scope 对象。
|
|
14
|
+
* - **标签失效**:`revalidateTag(tag)` 失效所有带该标签的缓存条目;
|
|
15
|
+
* `revalidatePath(path)` 失效匹配路径的缓存条目。
|
|
16
|
+
* - **零外部依赖**:使用内置 `AsyncLocalStorage` + `Map` 内存存储。
|
|
17
|
+
*
|
|
18
|
+
* 用户通过 `defineCachedFunction(fn, options)` 显式声明缓存函数,无需 Vite 插件
|
|
19
|
+
* 参与,运行时通过此模块的 API 执行缓存逻辑。
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* 默认内存组件缓存存储。带标签反向索引以加速 `revalidateTag`。
|
|
23
|
+
*/
|
|
24
|
+
function createComponentMemoryStore(maxEntries = 500) {
|
|
25
|
+
const store = /* @__PURE__ */ new Map();
|
|
26
|
+
/** tag → 关联的缓存键集合 */
|
|
27
|
+
const tagIndex = /* @__PURE__ */ new Map();
|
|
28
|
+
function addToTagIndex(key, tags) {
|
|
29
|
+
for (const tag of tags) {
|
|
30
|
+
let set = tagIndex.get(tag);
|
|
31
|
+
if (!set) {
|
|
32
|
+
set = /* @__PURE__ */ new Set();
|
|
33
|
+
tagIndex.set(tag, set);
|
|
34
|
+
}
|
|
35
|
+
set.add(key);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function removeFromTagIndex(key, tags) {
|
|
39
|
+
for (const tag of tags) {
|
|
40
|
+
const set = tagIndex.get(tag);
|
|
41
|
+
if (set) {
|
|
42
|
+
set.delete(key);
|
|
43
|
+
if (set.size === 0) tagIndex.delete(tag);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function evict() {
|
|
48
|
+
if (store.size <= maxEntries) return;
|
|
49
|
+
const entries = Array.from(store.entries()).sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
50
|
+
const removeCount = Math.ceil(entries.length * .2);
|
|
51
|
+
for (let i = 0; i < removeCount; i++) {
|
|
52
|
+
const [key, entry] = entries[i];
|
|
53
|
+
removeFromTagIndex(key, entry.tags);
|
|
54
|
+
store.delete(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
async get(key) {
|
|
59
|
+
const entry = store.get(key);
|
|
60
|
+
if (!entry) return void 0;
|
|
61
|
+
if (Date.now() > entry.expiresAt) {
|
|
62
|
+
removeFromTagIndex(key, entry.tags);
|
|
63
|
+
store.delete(key);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
return entry;
|
|
67
|
+
},
|
|
68
|
+
async set(key, entry, ttl) {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const existing = store.get(key);
|
|
71
|
+
if (existing) removeFromTagIndex(key, existing.tags);
|
|
72
|
+
const full = {
|
|
73
|
+
...entry,
|
|
74
|
+
createdAt: now,
|
|
75
|
+
expiresAt: now + ttl * 1e3
|
|
76
|
+
};
|
|
77
|
+
store.set(key, full);
|
|
78
|
+
addToTagIndex(key, full.tags);
|
|
79
|
+
evict();
|
|
80
|
+
},
|
|
81
|
+
async delete(key) {
|
|
82
|
+
const entry = store.get(key);
|
|
83
|
+
if (entry) removeFromTagIndex(key, entry.tags);
|
|
84
|
+
return store.delete(key);
|
|
85
|
+
},
|
|
86
|
+
async clear() {
|
|
87
|
+
store.clear();
|
|
88
|
+
tagIndex.clear();
|
|
89
|
+
},
|
|
90
|
+
async keys() {
|
|
91
|
+
return Array.from(store.keys());
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
let globalComponentStore = null;
|
|
96
|
+
function useComponentCacheStore(store) {
|
|
97
|
+
if (store) {
|
|
98
|
+
globalComponentStore = store;
|
|
99
|
+
return store;
|
|
100
|
+
}
|
|
101
|
+
if (!globalComponentStore) globalComponentStore = createComponentMemoryStore();
|
|
102
|
+
return globalComponentStore;
|
|
103
|
+
}
|
|
104
|
+
function clearComponentCacheStore() {
|
|
105
|
+
globalComponentStore = null;
|
|
106
|
+
}
|
|
107
|
+
const cacheScopeStorage = new AsyncLocalStorage();
|
|
108
|
+
/**
|
|
109
|
+
* 设置当前缓存作用域的 TTL(秒)。
|
|
110
|
+
*
|
|
111
|
+
* 必须在 `defineCachedFunction()` 包装的函数体内调用,否则为空操作(允许在
|
|
112
|
+
* 非缓存上下文中调用以简化条件逻辑)。
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const getUser = defineCachedFunction(
|
|
117
|
+
* async (id: string) => {
|
|
118
|
+
* cacheLife(3600); // 缓存 1 小时
|
|
119
|
+
* return await db.query.user.findById(id);
|
|
120
|
+
* },
|
|
121
|
+
* { name: 'getUser' }
|
|
122
|
+
* );
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
function cacheLife(seconds) {
|
|
126
|
+
const scope = cacheScopeStorage.getStore();
|
|
127
|
+
if (scope) scope.ttl = Math.max(0, Math.floor(seconds));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 为当前缓存作用域添加标签。
|
|
131
|
+
*
|
|
132
|
+
* 标签可用于通过 `revalidateTag(tag)` 精确失效缓存。
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const getUser = defineCachedFunction(
|
|
137
|
+
* async (id: string) => {
|
|
138
|
+
* cacheTag('users', `user:${id}`);
|
|
139
|
+
* return await db.query.user.findById(id);
|
|
140
|
+
* },
|
|
141
|
+
* { name: 'getUser' }
|
|
142
|
+
* );
|
|
143
|
+
*
|
|
144
|
+
* // 失效所有带 'users' 标签的缓存
|
|
145
|
+
* await revalidateTag('users');
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
function cacheTag(...tags) {
|
|
149
|
+
const scope = cacheScopeStorage.getStore();
|
|
150
|
+
if (scope) scope.tags.push(...tags);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 默认缓存键生成器:`name + ":" + JSON.stringify(args)`。
|
|
154
|
+
*/
|
|
155
|
+
function defaultGetKey(name, args) {
|
|
156
|
+
if (args.length === 0) return name;
|
|
157
|
+
try {
|
|
158
|
+
return `${name}:${JSON.stringify(args)}`;
|
|
159
|
+
} catch {
|
|
160
|
+
return `${name}:__unserializable__`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 将异步函数包装为带缓存的函数。
|
|
165
|
+
*
|
|
166
|
+
* 用户在源码中显式调用此函数声明缓存函数(无需 Vite 插件参与)。
|
|
167
|
+
*
|
|
168
|
+
* 工作流程:
|
|
169
|
+
* 1. 根据参数生成缓存键
|
|
170
|
+
* 2. 查询缓存 → 命中则返回反序列化的值
|
|
171
|
+
* 3. 未命中 → 在 `AsyncLocalStorage` 作用域中执行原函数
|
|
172
|
+
* - 函数体内的 `cacheLife()` / `cacheTag()` 写入作用域
|
|
173
|
+
* 4. 将结果序列化存入缓存(带标签 + TTL)
|
|
174
|
+
* 5. 返回结果
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* import { defineCachedFunction, cacheLife, cacheTag } from '@ubean/server';
|
|
179
|
+
*
|
|
180
|
+
* const getUser = defineCachedFunction(
|
|
181
|
+
* async (id: string) => {
|
|
182
|
+
* cacheLife(3600);
|
|
183
|
+
* cacheTag('users', `user:${id}`);
|
|
184
|
+
* return await db.query.user.findById(id);
|
|
185
|
+
* },
|
|
186
|
+
* { name: 'getUser' }
|
|
187
|
+
* );
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* @param fn 原始异步函数
|
|
191
|
+
* @param options 缓存选项
|
|
192
|
+
*/
|
|
193
|
+
function defineCachedFunction(fn, options) {
|
|
194
|
+
const store = useComponentCacheStore();
|
|
195
|
+
const defaultTtl = options.defaultTtl ?? 60;
|
|
196
|
+
return async (...args) => {
|
|
197
|
+
const key = options.getKey ? options.getKey(...args) : defaultGetKey(options.name, args);
|
|
198
|
+
const cached = await store.get(key);
|
|
199
|
+
if (cached) try {
|
|
200
|
+
return JSON.parse(cached.value);
|
|
201
|
+
} catch {}
|
|
202
|
+
const scope = {
|
|
203
|
+
ttl: defaultTtl,
|
|
204
|
+
tags: []
|
|
205
|
+
};
|
|
206
|
+
const result = await cacheScopeStorage.run(scope, () => fn(...args));
|
|
207
|
+
try {
|
|
208
|
+
const value = JSON.stringify(result);
|
|
209
|
+
await store.set(key, {
|
|
210
|
+
value,
|
|
211
|
+
tags: scope.tags
|
|
212
|
+
}, scope.ttl);
|
|
213
|
+
} catch {}
|
|
214
|
+
return result;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* @deprecated 请使用 `defineCachedFunction()`。保留旧名称仅为向后兼容。
|
|
219
|
+
*/
|
|
220
|
+
const wrapWithCache = defineCachedFunction;
|
|
221
|
+
/**
|
|
222
|
+
* 失效所有带指定标签的缓存条目。
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* // 用户数据更新后,失效所有 'users' 标签的缓存
|
|
227
|
+
* await revalidateTag('users');
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
async function revalidateTag(tag) {
|
|
231
|
+
const store = useComponentCacheStore();
|
|
232
|
+
let deleted = 0;
|
|
233
|
+
if (store.keys) {
|
|
234
|
+
const allKeys = await store.keys();
|
|
235
|
+
for (const key of allKeys) {
|
|
236
|
+
const entry = await store.get(key);
|
|
237
|
+
if (entry && entry.tags.includes(tag)) {
|
|
238
|
+
await store.delete(key);
|
|
239
|
+
deleted++;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
deleted += await revalidateDataCacheTag(tag);
|
|
244
|
+
return deleted;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 失效多个标签的缓存条目。
|
|
248
|
+
*/
|
|
249
|
+
async function revalidateTags(...tags) {
|
|
250
|
+
let total = 0;
|
|
251
|
+
for (const tag of tags) total += await revalidateTag(tag);
|
|
252
|
+
return total;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 失效缓存键匹配指定模式的条目。
|
|
256
|
+
*
|
|
257
|
+
* 支持 `*`(单段通配)和 `**`(多段通配)。
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```ts
|
|
261
|
+
* // 失效所有 getUser 开头的缓存
|
|
262
|
+
* await revalidatePath('getUser:*');
|
|
263
|
+
* // 失效特定用户缓存
|
|
264
|
+
* await revalidatePath('getUser:["user-123"]');
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
async function revalidatePath(pattern) {
|
|
268
|
+
const store = useComponentCacheStore();
|
|
269
|
+
let deleted = 0;
|
|
270
|
+
if (store.keys) {
|
|
271
|
+
const allKeys = await store.keys();
|
|
272
|
+
const regex = pattern instanceof RegExp ? pattern : globToRegex(pattern);
|
|
273
|
+
for (const key of allKeys) if (regex.test(key)) {
|
|
274
|
+
await store.delete(key);
|
|
275
|
+
deleted++;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
deleted += await revalidateDataCachePath(pattern);
|
|
279
|
+
return deleted;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* 清空所有组件级缓存。
|
|
283
|
+
*/
|
|
284
|
+
async function clearComponentCache() {
|
|
285
|
+
await useComponentCacheStore().clear();
|
|
286
|
+
clearFetchDataCache();
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 将 glob 模式转换为正则表达式。
|
|
290
|
+
* `*` → `[^/]*`,`**` → `.*`。
|
|
291
|
+
*/
|
|
292
|
+
function globToRegex(pattern) {
|
|
293
|
+
const DOUBLE = "__DWC__";
|
|
294
|
+
const SINGLE = "__SWC__";
|
|
295
|
+
let s = pattern;
|
|
296
|
+
s = s.replace(/\/\*\*/g, `/${DOUBLE}`);
|
|
297
|
+
s = s.replace(/\*/g, SINGLE);
|
|
298
|
+
s = s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
299
|
+
s = s.replace(new RegExp(SINGLE, "g"), "[^/]*");
|
|
300
|
+
s = s.replace(new RegExp(`/${DOUBLE}`, "g"), "(?:/.*)?");
|
|
301
|
+
return new RegExp(`^${s}$`);
|
|
302
|
+
}
|
|
303
|
+
//#endregion
|
|
304
|
+
export { createComponentMemoryStore as a, revalidateTag as c, wrapWithCache as d, clearComponentCacheStore as i, revalidateTags as l, cacheTag as n, defineCachedFunction as o, clearComponentCache as r, revalidatePath as s, cacheLife as t, useComponentCacheStore as u };
|