@ubean/server 0.1.13 → 0.2.0

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.
Files changed (56) hide show
  1. package/dist/analytics-entry.d.ts +2 -0
  2. package/dist/analytics-entry.js +2 -0
  3. package/dist/cache-C84ix1Vq.js +173 -0
  4. package/dist/cache-b-MZlyv0.d.ts +48 -0
  5. package/dist/cache-directive-C1Nekkza.js +304 -0
  6. package/dist/cache-directive-CAxJAQyE.d.ts +175 -0
  7. package/dist/cache-directive.d.ts +2 -0
  8. package/dist/cache-directive.js +2 -0
  9. package/dist/cache-entry.d.ts +3 -0
  10. package/dist/cache-entry.js +3 -0
  11. package/dist/cron-entry.d.ts +2 -0
  12. package/dist/cron-entry.js +2 -0
  13. package/dist/cron-scheduler-BF33PPn4.d.ts +77 -0
  14. package/dist/cron-scheduler-BVuXv7nn.js +258 -0
  15. package/dist/database-CfpFznl-.d.ts +67 -0
  16. package/dist/database-DNrY44SQ.js +352 -0
  17. package/dist/database.d.ts +2 -0
  18. package/dist/database.js +2 -0
  19. package/dist/email-BjfRiR9b.js +354 -0
  20. package/dist/email-BvpEuNn_.d.ts +226 -0
  21. package/dist/email.d.ts +2 -0
  22. package/dist/email.js +2 -0
  23. package/dist/feature-flags-CdLwsMD2.js +657 -0
  24. package/dist/feature-flags-DWkS6p0D.d.ts +386 -0
  25. package/dist/fetch-memo-rbkxxnW4.js +338 -0
  26. package/dist/index.d.ts +183 -488
  27. package/dist/index.js +352 -2023
  28. package/dist/middleware.d.ts +2 -0
  29. package/dist/middleware.js +3 -0
  30. package/dist/observability-Cio6Qq1H.js +339 -0
  31. package/dist/observability-DUNUEjj3.d.ts +70 -0
  32. package/dist/observability.d.ts +2 -0
  33. package/dist/observability.js +2 -0
  34. package/dist/queue-Bwzi3mhK.js +210 -0
  35. package/dist/queue-GOfTAWlz.d.ts +55 -0
  36. package/dist/queue.d.ts +2 -0
  37. package/dist/queue.js +2 -0
  38. package/dist/realtime.d.ts +2 -0
  39. package/dist/realtime.js +2 -0
  40. package/dist/security.d.ts +2 -0
  41. package/dist/security.js +2 -0
  42. package/dist/sessions-BLqFFQTL.d.ts +217 -0
  43. package/dist/sessions-BsBsyFAG.js +450 -0
  44. package/dist/single-flight-BJyhDLdU.d.ts +422 -0
  45. package/dist/single-flight-mJ4ZKbx1.js +715 -0
  46. package/dist/sse-Ct72zhic.d.ts +95 -0
  47. package/dist/sse-a6Ky9Vcl.js +310 -0
  48. package/dist/static-DPHaovQe.js +90 -0
  49. package/dist/static-K2dRvjpS.d.ts +11 -0
  50. package/dist/static.d.ts +2 -0
  51. package/dist/static.js +2 -0
  52. package/dist/storage-BZLMaqHr.js +162 -0
  53. package/dist/storage-QdlPtPtR.d.ts +48 -0
  54. package/dist/storage.d.ts +2 -0
  55. package/dist/storage.js +2 -0
  56. package/package.json +68 -6
@@ -0,0 +1,338 @@
1
+ //#region src/fetch-memo.ts
2
+ const MEMO_TTL_MS = 0;
3
+ /**
4
+ * 生成 memoization key
5
+ */
6
+ function memoKey(input, init) {
7
+ let url;
8
+ let method = "GET";
9
+ if (typeof input === "string") url = input;
10
+ else if (input instanceof URL) url = input.toString();
11
+ else if (input instanceof Request) {
12
+ url = input.url;
13
+ method = input.method;
14
+ } else url = String(input);
15
+ if (init?.method) method = init.method.toUpperCase();
16
+ if (method !== "GET" && method !== "HEAD") return "";
17
+ if (init?.body) return "";
18
+ return `${method}:${url}`;
19
+ }
20
+ /**
21
+ * 创建 fetch memoization 中间件
22
+ *
23
+ * 在请求作用域内包装 globalThis.fetch,对相同 GET URL 自动去重
24
+ */
25
+ function createFetchMemoizationMiddleware(options = {}) {
26
+ const { exclude } = options;
27
+ return async function fetchMemoizationMiddleware(c, next) {
28
+ const ctx = {
29
+ cache: /* @__PURE__ */ new Map(),
30
+ originalFetch: globalThis.fetch,
31
+ patched: false
32
+ };
33
+ const patchedFetch = async (input, init) => {
34
+ const key = memoKey(input, init);
35
+ if (!key || exclude && exclude(typeof input === "string" ? input : input.toString())) return ctx.originalFetch(input, init);
36
+ const existing = ctx.cache.get(key);
37
+ if (existing) return existing.promise.then((res) => res.clone());
38
+ const promise = ctx.originalFetch(input, init).then((res) => {
39
+ if (!res.ok && res.status >= 500) ctx.cache.delete(key);
40
+ return res;
41
+ });
42
+ ctx.cache.set(key, {
43
+ promise,
44
+ timestamp: Date.now()
45
+ });
46
+ return promise;
47
+ };
48
+ const prevFetch = globalThis.fetch;
49
+ globalThis.fetch = patchedFetch;
50
+ ctx.patched = true;
51
+ try {
52
+ await next();
53
+ } finally {
54
+ globalThis.fetch = prevFetch;
55
+ ctx.cache.clear();
56
+ }
57
+ };
58
+ }
59
+ /**
60
+ * 手动创建一个 memoized fetch 函数(不依赖中间件/AsyncLocalStorage)
61
+ *
62
+ * 适用于在非请求上下文中手动使用 memoization
63
+ */
64
+ function createMemoizedFetch(options = {}) {
65
+ const originalFetch = options.originalFetch || globalThis.fetch.bind(globalThis);
66
+ const ttl = options.ttl ?? MEMO_TTL_MS;
67
+ const cache = /* @__PURE__ */ new Map();
68
+ const memoizedFetch = async (input, init) => {
69
+ const key = memoKey(input, init);
70
+ if (!key) return originalFetch(input, init);
71
+ if (ttl > 0) {
72
+ const existing = cache.get(key);
73
+ if (existing && Date.now() - existing.timestamp > ttl) cache.delete(key);
74
+ }
75
+ const existing = cache.get(key);
76
+ if (existing) return existing.promise.then((res) => res.clone());
77
+ const promise = originalFetch(input, init).then((res) => {
78
+ if (!res.ok && res.status >= 500) cache.delete(key);
79
+ return res;
80
+ });
81
+ cache.set(key, {
82
+ promise,
83
+ timestamp: Date.now()
84
+ });
85
+ return promise;
86
+ };
87
+ return {
88
+ fetch: memoizedFetch,
89
+ clear: () => cache.clear(),
90
+ size: () => cache.size
91
+ };
92
+ }
93
+ const DATA_CACHE_MAX_ENTRIES = 1e3;
94
+ /** 不参与缓存键的 header(自动设置、易变)。 */
95
+ const SKIP_HEADER_KEYS = /* @__PURE__ */ new Set([
96
+ "user-agent",
97
+ "accept-encoding",
98
+ "connection",
99
+ "host",
100
+ "content-length"
101
+ ]);
102
+ /** Data Cache 存储:key → entry */
103
+ const dataCacheStore = /* @__PURE__ */ new Map();
104
+ /** tag → 关联的缓存键集合(反向索引,加速 revalidateTag) */
105
+ const dataCacheTagIndex = /* @__PURE__ */ new Map();
106
+ function dataCacheAddToTagIndex(key, tags) {
107
+ for (const tag of tags) {
108
+ let set = dataCacheTagIndex.get(tag);
109
+ if (!set) {
110
+ set = /* @__PURE__ */ new Set();
111
+ dataCacheTagIndex.set(tag, set);
112
+ }
113
+ set.add(key);
114
+ }
115
+ }
116
+ function dataCacheRemoveFromTagIndex(key, tags) {
117
+ for (const tag of tags) {
118
+ const set = dataCacheTagIndex.get(tag);
119
+ if (set) {
120
+ set.delete(key);
121
+ if (set.size === 0) dataCacheTagIndex.delete(tag);
122
+ }
123
+ }
124
+ }
125
+ function dataCacheEvict() {
126
+ if (dataCacheStore.size <= DATA_CACHE_MAX_ENTRIES) return;
127
+ const entries = Array.from(dataCacheStore.entries()).sort((a, b) => a[1].createdAt - b[1].createdAt);
128
+ const removeCount = Math.ceil(entries.length * .2);
129
+ for (let i = 0; i < removeCount; i++) {
130
+ const [key, entry] = entries[i];
131
+ dataCacheRemoveFromTagIndex(key, entry.tags);
132
+ dataCacheStore.delete(key);
133
+ }
134
+ }
135
+ function isDevMode() {
136
+ return process.env.NODE_ENV !== "production";
137
+ }
138
+ /**
139
+ * 生成 Data Cache 键:`method:url:sorted(headers)`。
140
+ *
141
+ * 排除自动设置/易变的 header(User-Agent / Accept-Encoding / Connection / Host /
142
+ * Content-Length),保证相同语义的请求命中同一缓存键。
143
+ */
144
+ function dataCacheKey(input, init) {
145
+ let url;
146
+ let method = "GET";
147
+ let headers;
148
+ if (typeof input === "string") {
149
+ url = input;
150
+ headers = new Headers(init?.headers);
151
+ } else if (input instanceof URL) {
152
+ url = input.toString();
153
+ headers = new Headers(init?.headers);
154
+ } else if (input instanceof Request) {
155
+ url = input.url;
156
+ method = input.method;
157
+ headers = new Headers(input.headers);
158
+ if (init?.headers) new Headers(init.headers).forEach((v, k) => headers.set(k, v));
159
+ } else {
160
+ url = String(input);
161
+ headers = new Headers(init?.headers);
162
+ }
163
+ if (init?.method) method = init.method.toUpperCase();
164
+ const headerEntries = [];
165
+ headers.forEach((value, key) => {
166
+ if (SKIP_HEADER_KEYS.has(key.toLowerCase())) return;
167
+ headerEntries.push(`${key.toLowerCase()}:${value}`);
168
+ });
169
+ headerEntries.sort();
170
+ return `${method}:${url}:${headerEntries.join("|")}`;
171
+ }
172
+ /**
173
+ * 从 init 中提取 Data Cache 选项。返回 `null` 表示该请求不应被缓存。
174
+ *
175
+ * 规则:
176
+ * - 无 `next` 选项 → 不缓存(默认行为不变)
177
+ * - `next.noStore: true` → 不缓存
178
+ * - `next.revalidate: 0` → 不缓存
179
+ * - `next.revalidate > 0` 或 `next.tags` 非空 → 缓存
180
+ * - 仅 `next: {}`(无 revalidate/tags) → 永久缓存(对齐 Next.js 行为)
181
+ */
182
+ function extractCacheOptions(init) {
183
+ if (!init?.next) return null;
184
+ const { revalidate, tags, noStore } = init.next;
185
+ if (noStore) return null;
186
+ if (revalidate === 0) return null;
187
+ return {
188
+ revalidate,
189
+ tags,
190
+ noStore
191
+ };
192
+ }
193
+ /** 序列化 Response 为可存储格式(消费 body 一次)。 */
194
+ async function serializeResponse(res) {
195
+ const body = await res.arrayBuffer();
196
+ const headers = {};
197
+ res.headers.forEach((value, key) => {
198
+ headers[key] = value;
199
+ });
200
+ return {
201
+ status: res.status,
202
+ statusText: res.statusText,
203
+ headers,
204
+ body
205
+ };
206
+ }
207
+ /** 从序列化数据重建 Response(每次返回新的可消费实例)。 */
208
+ function deserializeResponse(serialized) {
209
+ return new Response(serialized.body.slice(0), {
210
+ status: serialized.status,
211
+ statusText: serialized.statusText,
212
+ headers: new Headers(serialized.headers)
213
+ });
214
+ }
215
+ /**
216
+ * 创建 fetch Data Cache 中间件(Task 4)。
217
+ *
218
+ * 在请求作用域内包装 `globalThis.fetch`,对带 `next: { revalidate, tags }` 选项的
219
+ * GET/HEAD 请求按 TTL 跨请求缓存响应。无 `next` 选项的请求走原始路径(默认行为不变)。
220
+ *
221
+ * 行为:
222
+ * - 仅缓存 2xx 响应(4xx/5xx 不缓存,允许后续重试)
223
+ * - dev 模式默认 no-cache(可通过 `forceDevCache` 强制启用,用于测试)
224
+ * - 与 `revalidateTag` / `revalidatePath` 集成,失效对应缓存条目
225
+ *
226
+ * @example
227
+ * ```ts
228
+ * import { createDataCacheMiddleware } from '@ubean/server';
229
+ * app.use('*', createDataCacheMiddleware());
230
+ *
231
+ * // 路由处理器中:
232
+ * const res = await fetch('https://api.example.com/data', {
233
+ * next: { revalidate: 60, tags: ['data'] }
234
+ * });
235
+ * ```
236
+ */
237
+ function createDataCacheMiddleware(options = {}) {
238
+ const skipCache = (options.dev ?? isDevMode()) && !options.forceDevCache;
239
+ const { exclude } = options;
240
+ return async function dataCacheMiddleware(c, next) {
241
+ const originalFetch = globalThis.fetch;
242
+ const patchedFetch = async (input, init) => {
243
+ const cacheOpts = extractCacheOptions(init);
244
+ if (!cacheOpts || skipCache) return originalFetch(input, init);
245
+ const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
246
+ if (method !== "GET" && method !== "HEAD") return originalFetch(input, init);
247
+ if (exclude && exclude(typeof input === "string" ? input : input.toString())) return originalFetch(input, init);
248
+ const key = dataCacheKey(input, init);
249
+ const now = Date.now();
250
+ const existing = dataCacheStore.get(key);
251
+ if (existing && now <= existing.expiresAt) return deserializeResponse(existing.response);
252
+ if (existing) {
253
+ dataCacheRemoveFromTagIndex(key, existing.tags);
254
+ dataCacheStore.delete(key);
255
+ }
256
+ const res = await originalFetch(input, init);
257
+ if (!res.ok) return res;
258
+ const ttl = cacheOpts.revalidate === void 0 ? Infinity : cacheOpts.revalidate;
259
+ const tags = cacheOpts.tags ?? [];
260
+ const serialized = await serializeResponse(res);
261
+ dataCacheStore.set(key, {
262
+ response: serialized,
263
+ tags,
264
+ createdAt: now,
265
+ expiresAt: ttl === Infinity ? Number.MAX_SAFE_INTEGER : now + ttl * 1e3
266
+ });
267
+ dataCacheAddToTagIndex(key, tags);
268
+ dataCacheEvict();
269
+ return deserializeResponse(serialized);
270
+ };
271
+ const prevFetch = globalThis.fetch;
272
+ globalThis.fetch = patchedFetch;
273
+ try {
274
+ await next();
275
+ } finally {
276
+ globalThis.fetch = prevFetch;
277
+ }
278
+ };
279
+ }
280
+ /**
281
+ * 失效所有带指定标签的 Data Cache 条目。返回删除数量。
282
+ *
283
+ * 用户通常无需直接调用 —— `cache-directive.ts` 的 `revalidateTag` 会自动调用此函数。
284
+ */
285
+ async function revalidateDataCacheTag(tag) {
286
+ const set = dataCacheTagIndex.get(tag);
287
+ if (!set) return 0;
288
+ const keys = Array.from(set);
289
+ for (const key of keys) {
290
+ const entry = dataCacheStore.get(key);
291
+ if (entry) {
292
+ dataCacheRemoveFromTagIndex(key, entry.tags);
293
+ dataCacheStore.delete(key);
294
+ }
295
+ }
296
+ return keys.length;
297
+ }
298
+ /**
299
+ * 失效缓存键匹配指定模式的 Data Cache 条目。返回删除数量。
300
+ *
301
+ * 用户通常无需直接调用 —— `cache-directive.ts` 的 `revalidatePath` 会自动调用此函数。
302
+ */
303
+ async function revalidateDataCachePath(pattern) {
304
+ const regex = pattern instanceof RegExp ? pattern : dataCacheGlobToRegex(pattern);
305
+ let deleted = 0;
306
+ for (const key of Array.from(dataCacheStore.keys())) if (regex.test(key)) {
307
+ const entry = dataCacheStore.get(key);
308
+ if (entry) {
309
+ dataCacheRemoveFromTagIndex(key, entry.tags);
310
+ dataCacheStore.delete(key);
311
+ deleted++;
312
+ }
313
+ }
314
+ return deleted;
315
+ }
316
+ /** 清空所有 fetch Data Cache 条目(主要用于测试)。 */
317
+ function clearFetchDataCache() {
318
+ dataCacheStore.clear();
319
+ dataCacheTagIndex.clear();
320
+ }
321
+ /** 返回 Data Cache 当前条目数(主要用于测试)。 */
322
+ function getDataCacheSize() {
323
+ return dataCacheStore.size;
324
+ }
325
+ /** 将 glob 模式转换为正则表达式(与 cache-directive.ts 保持一致)。 */
326
+ function dataCacheGlobToRegex(pattern) {
327
+ const DOUBLE = "__DWC__";
328
+ const SINGLE = "__SWC__";
329
+ let s = pattern;
330
+ s = s.replace(/\/\*\*/g, `/${DOUBLE}`);
331
+ s = s.replace(/\*/g, SINGLE);
332
+ s = s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
333
+ s = s.replace(new RegExp(SINGLE, "g"), "[^/]*");
334
+ s = s.replace(new RegExp(`/${DOUBLE}`, "g"), "(?:/.*)?");
335
+ return new RegExp(`^${s}$`);
336
+ }
337
+ //#endregion
338
+ export { getDataCacheSize as a, createMemoizedFetch as i, createDataCacheMiddleware as n, revalidateDataCachePath as o, createFetchMemoizationMiddleware as r, revalidateDataCacheTag as s, clearFetchDataCache as t };