@bleedingdev/modern-js-server-core 3.9.0-ultramodern.9 → 3.9.3-ultramodern.2

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 (35) hide show
  1. package/dist/cjs/adapters/node/hono.js +25 -16
  2. package/dist/cjs/adapters/node/node.js +3 -1
  3. package/dist/cjs/adapters/node/plugins/static.js +1 -1
  4. package/dist/cjs/helper.js +1 -1
  5. package/dist/cjs/plugins/log.js +2 -1
  6. package/dist/cjs/plugins/render/render.js +4 -4
  7. package/dist/cjs/plugins/render/ssrCache.js +96 -52
  8. package/dist/cjs/serverBase.js +2 -2
  9. package/dist/cjs/utils/transformStream.js +1 -1
  10. package/dist/esm/adapters/node/hono.mjs +25 -16
  11. package/dist/esm/adapters/node/node.mjs +3 -1
  12. package/dist/esm/adapters/node/plugins/static.mjs +1 -1
  13. package/dist/esm/helper.mjs +1 -1
  14. package/dist/esm/plugins/log.mjs +2 -1
  15. package/dist/esm/plugins/render/render.mjs +5 -11
  16. package/dist/esm/plugins/render/ssrCache.mjs +96 -52
  17. package/dist/esm/serverBase.mjs +2 -2
  18. package/dist/esm/utils/transformStream.mjs +1 -1
  19. package/dist/esm-node/adapters/node/hono.mjs +25 -16
  20. package/dist/esm-node/adapters/node/node.mjs +3 -1
  21. package/dist/esm-node/adapters/node/plugins/static.mjs +1 -1
  22. package/dist/esm-node/helper.mjs +1 -1
  23. package/dist/esm-node/plugins/log.mjs +2 -1
  24. package/dist/esm-node/plugins/render/render.mjs +5 -11
  25. package/dist/esm-node/plugins/render/ssrCache.mjs +96 -52
  26. package/dist/esm-node/serverBase.mjs +2 -2
  27. package/dist/esm-node/utils/transformStream.mjs +1 -1
  28. package/dist/types/adapters/node/plugins/static.d.ts +2 -2
  29. package/dist/types/types/config/bff.d.ts +1 -1
  30. package/dist/types/types/config/server.d.ts +8 -3
  31. package/dist/types/types/config/source.d.ts +1 -1
  32. package/dist/types/types/server.d.ts +2 -0
  33. package/package.json +22 -14
  34. package/dist/esm/rslib-runtime~1.mjs +0 -18
  35. package/dist/esm-node/rslib-runtime~0.mjs +0 -19
@@ -1,54 +1,88 @@
1
1
  import { createMemoryStorage } from "@modern-js/runtime-utils/storer";
2
2
  import { X_RENDER_CACHE } from "../../constants.mjs";
3
3
  import { createTransformStream, getPathname } from "../../utils/index.mjs";
4
+ const preventsSharedCaching = (headers)=>/(?:^|,)\s*(?:private|no-store|no-cache)\s*(?:[=,]|$)/i.test(headers.get('cache-control') || '');
5
+ const isCacheableResponse = (response)=>200 === response.status && !preventsSharedCaching(response.headers) && !response.headers.has('set-cookie') && !response.headers.get('vary');
4
6
  const removeTailSlash = (s)=>s.replace(/\/+$/, '');
5
7
  const ZERO_RENDER_LEVEL = /"renderLevel":0/;
6
8
  const NO_SSR_CACHE = /<meta\s+[^>]*name=["']no-ssr-cache["'][^>]*>/i;
7
9
  async function processCache({ request, key, requestHandler, requestHandlerOptions, ttl, container, cacheStatus }) {
8
10
  const response = await requestHandler(request, requestHandlerOptions);
9
11
  const { onError } = requestHandlerOptions;
10
- const nonCacheableStatusCodes = [
11
- 204,
12
- 305,
13
- 404,
14
- 405,
15
- 500,
16
- 501,
17
- 502,
18
- 503,
19
- 504
20
- ];
21
- if (nonCacheableStatusCodes.includes(response.status)) return response;
12
+ const deleteCache = async ()=>{
13
+ try {
14
+ await container.delete(key);
15
+ } catch {
16
+ (onError || console.error)('[render-cache] delete cache failed');
17
+ }
18
+ };
19
+ if (!isCacheableResponse(response) || !response.body) {
20
+ await deleteCache();
21
+ return response;
22
+ }
23
+ const headers = Object.fromEntries(response.headers);
22
24
  const decoder = new TextDecoder();
23
25
  if (response.body) {
24
26
  const stream = createTransformStream();
25
27
  const reader = response.body.getReader();
26
28
  const writer = stream.writable.getWriter();
27
29
  let html = '';
28
- const push = ()=>reader.read().then(({ done, value })=>{
29
- if (done) {
30
- const match = ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html);
31
- if (match) return void writer.close();
32
- const current = Date.now();
33
- const cache = {
34
- val: html,
35
- cursor: current
36
- };
37
- container.set(key, JSON.stringify(cache), {
30
+ let cancelled = false;
31
+ const cancel = async (reason)=>{
32
+ cancelled = true;
33
+ await reader.cancel(reason);
34
+ };
35
+ writer.closed.catch(cancel).catch(()=>{});
36
+ const onAbort = ()=>{
37
+ Promise.allSettled([
38
+ cancel(request.signal.reason),
39
+ writer.abort(request.signal.reason)
40
+ ]);
41
+ };
42
+ request.signal.addEventListener('abort', onAbort, {
43
+ once: true
44
+ });
45
+ if (request.signal.aborted) onAbort();
46
+ const pumping = (async ()=>{
47
+ try {
48
+ for(;;){
49
+ const { done, value } = await reader.read();
50
+ if (cancelled) return;
51
+ if (done) break;
52
+ html += decoder.decode(value, {
53
+ stream: true
54
+ });
55
+ await writer.write(value);
56
+ }
57
+ html += decoder.decode();
58
+ await writer.close();
59
+ if (cancelled) return;
60
+ if (ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html)) return void await deleteCache();
61
+ const cache = {
62
+ val: html,
63
+ cursor: Date.now(),
64
+ headers
65
+ };
66
+ try {
67
+ await container.set(key, JSON.stringify(cache), {
38
68
  ttl
39
- }).catch(()=>{
40
- if (onError) onError(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
41
- else console.error(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
42
69
  });
43
- writer.close();
44
- return;
70
+ } catch (error) {
71
+ (onError || console.error)('[render-cache] set cache failed');
45
72
  }
46
- const content = decoder.decode(value);
47
- html += content;
48
- writer.write(value);
49
- push();
50
- });
51
- push();
73
+ } catch (error) {
74
+ await Promise.allSettled([
75
+ cancel(error),
76
+ writer.abort(error)
77
+ ]);
78
+ if (cacheStatus) try {
79
+ (onError || console.error)('[render-cache] response stream failed');
80
+ } catch {}
81
+ } finally{
82
+ request.signal.removeEventListener('abort', onAbort);
83
+ }
84
+ })();
85
+ pumping.catch(()=>{});
52
86
  cacheStatus && response.headers.set(X_RENDER_CACHE, cacheStatus);
53
87
  return new Response(stream.readable, {
54
88
  status: response.status,
@@ -63,9 +97,12 @@ function computedKey(req, cacheControl) {
63
97
  const pathname = getPathname(req);
64
98
  const { customKey } = cacheControl;
65
99
  const defaultKey = '/' === pathname ? pathname : removeTailSlash(pathname);
66
- if (!customKey) return defaultKey;
67
- if ('string' == typeof customKey) return customKey;
68
- return customKey(defaultKey);
100
+ if (customKey) if ('string' == typeof customKey) return customKey;
101
+ else return customKey(defaultKey);
102
+ else {
103
+ const url = new URL(req.url);
104
+ return `${url.origin}${defaultKey}${url.search}`;
105
+ }
69
106
  }
70
107
  function shouldUseCache(request) {
71
108
  const url = new URL(request.url);
@@ -76,7 +113,7 @@ function shouldUseCache(request) {
76
113
  }
77
114
  function matchCacheControl(cacheOption, req) {
78
115
  if (!cacheOption || !req) return;
79
- {
116
+ else {
80
117
  if (isCacheControl(cacheOption)) return cacheOption;
81
118
  if (isCacheOptionProvider(cacheOption)) return cacheOption(req);
82
119
  const url = req.url;
@@ -95,13 +132,15 @@ function matchCacheControl(cacheOption, req) {
95
132
  async function getCacheResult(request, options) {
96
133
  const { cacheControl, container = storage, requestHandler, requestHandlerOptions } = options;
97
134
  const { onError } = requestHandlerOptions;
98
- const key = computedKey(request, cacheControl);
135
+ const hasCredentials = request.headers.has('cookie') || request.headers.has('authorization');
136
+ if ('GET' !== request.method || !shouldUseCache(request) || preventsSharedCaching(request.headers) || hasCredentials && !cacheControl.customKey) return requestHandler(request, requestHandlerOptions);
137
+ const key = `${CACHE_NAMESPACE}:v2:${computedKey(request, cacheControl)}`;
99
138
  let value;
100
139
  try {
101
140
  value = await container.get(key);
102
141
  } catch (_) {
103
- if (onError) onError(`[render-cache] get cache failed, key: ${key}`);
104
- else console.error(`[render-cache] get cache failed, key: ${key}`);
142
+ if (onError) onError('[render-cache] get cache failed');
143
+ else console.error('[render-cache] get cache failed');
105
144
  value = void 0;
106
145
  }
107
146
  const { maxAge, staleWhileRevalidate } = cacheControl;
@@ -115,27 +154,27 @@ async function getCacheResult(request, options) {
115
154
  container,
116
155
  cacheStatus: 'miss'
117
156
  });
118
- {
157
+ else {
119
158
  const cache = JSON.parse(value);
120
159
  const interval = Date.now() - cache.cursor;
121
160
  if (interval <= maxAge) {
122
161
  const cacheStatus = 'hit';
123
162
  return new Response(cache.val, {
124
163
  headers: {
164
+ ...cache.headers,
125
165
  [X_RENDER_CACHE]: cacheStatus
126
166
  }
127
167
  });
128
- }
129
- if (!(interval <= staleWhileRevalidate + maxAge)) return processCache({
130
- key,
131
- request,
132
- requestHandler,
133
- requestHandlerOptions,
134
- ttl,
135
- container,
136
- cacheStatus: 'expired'
137
- });
138
- {
168
+ } else {
169
+ if (!(interval <= staleWhileRevalidate + maxAge)) return processCache({
170
+ key,
171
+ request,
172
+ requestHandler,
173
+ requestHandlerOptions,
174
+ ttl,
175
+ container,
176
+ cacheStatus: 'expired'
177
+ });
139
178
  processCache({
140
179
  key,
141
180
  request,
@@ -145,10 +184,15 @@ async function getCacheResult(request, options) {
145
184
  container
146
185
  }).then(async (response)=>{
147
186
  await response.text();
187
+ }).catch((error)=>{
188
+ try {
189
+ (onError || console.error)('[render-cache] revalidation failed');
190
+ } catch {}
148
191
  });
149
192
  const cacheStatus = 'stale';
150
193
  return new Response(cache.val, {
151
194
  headers: {
195
+ ...cache.headers,
152
196
  [X_RENDER_CACHE]: cacheStatus
153
197
  }
154
198
  });
@@ -146,7 +146,7 @@ function applyMiddlewares() {
146
146
  if (middleware.before) {
147
147
  const targetIndex = finalMiddlewares.findIndex((item)=>{
148
148
  if (middleware.before?.includes(item.name)) return true;
149
- return false;
149
+ else return false;
150
150
  });
151
151
  if (-1 !== targetIndex) finalMiddlewares.splice(targetIndex, 0, middleware);
152
152
  else finalMiddlewares.push(middleware);
@@ -167,7 +167,7 @@ function applyMiddlewares() {
167
167
  }
168
168
  function handler2Handlers(handler) {
169
169
  if (Array.isArray(handler)) return handler;
170
- return [
170
+ else return [
171
171
  handler
172
172
  ];
173
173
  }
@@ -14,7 +14,7 @@ function transformResponse(response, transformCb) {
14
14
  if (response.body) {
15
15
  const stream = createTransformStream(async (before)=>{
16
16
  if (Array.isArray(transformCb)) return transformCb.reduce(async (before, cb)=>cb(await before), Promise.resolve(before));
17
- return transformCb(before);
17
+ else return transformCb(before);
18
18
  });
19
19
  response.body.pipeThrough(stream);
20
20
  readable = stream.readable;
@@ -30,27 +30,36 @@ const httpCallBack2HonoMid = (handler)=>async (context, next)=>{
30
30
  } else await next();
31
31
  };
32
32
  const noop = ()=>{};
33
- const connectMid2HonoMid = (handler)=>async (context, next)=>new Promise((resolve, reject)=>{
34
- const { req, res } = context.env.node;
35
- if (handler.length < 3) resolve(handler(req, res, noop));
36
- else handler(req, res, (err)=>{
33
+ const connectMid2HonoMid = (handler)=>async (context, next)=>{
34
+ const { req, res: rawRes } = context.env.node;
35
+ const res = rawRes;
36
+ if (handler.length < 3) return handler(req, res, noop);
37
+ const continueChain = await new Promise((resolve, reject)=>{
38
+ const onFinish = ()=>{
39
+ context.res;
40
+ context.finalized = true;
41
+ resolve(false);
42
+ };
43
+ res.once?.('finish', onFinish);
44
+ handler(req, res, (err)=>{
45
+ res.removeListener?.('finish', onFinish);
37
46
  if (err) reject(err);
38
- else resolve(next());
47
+ else resolve(true);
39
48
  });
40
49
  });
41
- const connectMockMid2HonoMid = (handler)=>async (context, next)=>new Promise((resolve, reject)=>{
50
+ if (continueChain) await next();
51
+ };
52
+ const connectMockMid2HonoMid = (handler)=>{
53
+ if (handler.length >= 3) return connectMid2HonoMid(handler);
54
+ return async (context, _next)=>new Promise((resolve)=>{
42
55
  const { req, res: rawRes } = context.env.node;
43
56
  const res = rawRes;
44
- if (handler.length < 3) {
45
- res.once?.('finish', ()=>{
46
- context.res;
47
- context.finalized = true;
48
- resolve();
49
- });
50
- handler(req, res, noop);
51
- } else handler(req, res, (err)=>{
52
- if (err) reject(err);
53
- else resolve(next());
57
+ res.once?.('finish', ()=>{
58
+ context.res;
59
+ context.finalized = true;
60
+ resolve();
54
61
  });
62
+ handler(req, res, noop);
55
63
  });
64
+ };
56
65
  export { connectMid2HonoMid, connectMockMid2HonoMid, httpCallBack2HonoMid };
@@ -25,7 +25,9 @@ const createWebRequest = (req, res, body)=>{
25
25
  headers: headerRecord,
26
26
  signal: controller.signal
27
27
  };
28
- res.on('close', ()=>controller.abort('res closed'));
28
+ res.on('close', ()=>{
29
+ if (!res.writableFinished || req.aborted) controller.abort('res closed');
30
+ });
29
31
  const protocol = 'encrypted' in req.socket && req.socket.encrypted ? 'https' : 'http';
30
32
  const authority = req.headers[':authority'] ?? req.headers.host;
31
33
  const url = `${protocol}://${authority}${req.url}`;
@@ -155,7 +155,7 @@ function createStaticMiddleware(options) {
155
155
  respondAsset: options.respondAsset,
156
156
  respondPublicFallback: options.respondPublicFallback
157
157
  })(c, next);
158
- {
158
+ else {
159
159
  const filepath = path.join(pwd, pathname.replace(pathPrefix, ()=>''));
160
160
  if (!isPathInside(filepath, pwd)) return next();
161
161
  if (!await fs.pathExists(filepath)) return next();
@@ -3,7 +3,7 @@ const LOADER_CONTEXT = 'loaderContext';
3
3
  function getLoaderCtx(c) {
4
4
  const loaderContext = c.get(LOADER_CONTEXT);
5
5
  if (loaderContext) return loaderContext;
6
- {
6
+ else {
7
7
  const loaderContext = new Map();
8
8
  c.set(LOADER_CONTEXT, loaderContext);
9
9
  return loaderContext;
@@ -41,7 +41,8 @@ function logHandler() {
41
41
  log(logFn, "<--", method, path);
42
42
  const start = Date.now();
43
43
  await next();
44
- log(logFn, "-->", method, path, c?.env?.node?.res?.statusCode ?? c.res.status, time(start));
44
+ const nodeRes = c?.env?.node?.res;
45
+ log(logFn, "-->", method, path, nodeRes?.headersSent ? nodeRes.statusCode : c.res.status, time(start));
45
46
  };
46
47
  }
47
48
  const logPlugin = ()=>({
@@ -8,14 +8,8 @@ import { dataHandler } from "./dataHandler.mjs";
8
8
  import { renderRscHandler } from "./renderRscHandler.mjs";
9
9
  import { serverActionHandler } from "./serverActionHandler.mjs";
10
10
  import { ssrRender } from "./ssrRender.mjs";
11
- import { __webpack_require__ } from "../../rslib-runtime~0.mjs";
12
11
  import { createRequire as __rspack_createRequire } from "node:module";
13
12
  const __rspack_createRequire_require = __rspack_createRequire(import.meta.url);
14
- __webpack_require__.add({
15
- "@modern-js/runtime-utils/router" (module) {
16
- module.exports = __rspack_createRequire_require("@modern-js/runtime-utils/router");
17
- }
18
- });
19
13
  const DYNAMIC_ROUTE_REG = /\/:./;
20
14
  function getRouter(routes) {
21
15
  const dynamicRoutes = [];
@@ -48,8 +42,7 @@ function matchRoute(router, pathname, entryName) {
48
42
  void 0,
49
43
  {}
50
44
  ];
51
- }
52
- {
45
+ } else {
53
46
  const result = matchedRoutes[0];
54
47
  if (!result) return [
55
48
  void 0,
@@ -74,6 +67,7 @@ async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig
74
67
  return async (req, { monitors, nodeReq, templates, serverManifest, rscClientManifest, rscSSRManifest, rscServerManifest, locals, matchEntryName, matchPathname, loaderContext, serverContext, contextForceCSR, reporter })=>{
75
68
  const forMatchpathname = matchPathname ?? getPathname(req);
76
69
  const [routeInfo, params] = matchRoute(router, forMatchpathname, matchEntryName);
70
+ serverContext?.set('renderRoute', routeInfo);
77
71
  const framework = cutNameByHyphen(metaName || 'modern-js');
78
72
  const fallbackHeader = `x-${framework}-ssr-fallback`;
79
73
  let fallbackReason = null;
@@ -166,7 +160,7 @@ async function renderHandler(request, options, mode, fallbackWrapper, framework)
166
160
  const { nestedRoutesJson } = serverManifest;
167
161
  const routes = nestedRoutesJson?.[options.routeInfo.entryName];
168
162
  if (routes) {
169
- const { matchRoutes } = __webpack_require__("@modern-js/runtime-utils/router");
163
+ const { matchRoutes } = __rspack_createRequire_require("@modern-js/runtime-utils/router");
170
164
  const url = new URL(request.url);
171
165
  const matchedRoutes = matchRoutes(routes, url.pathname, options.routeInfo.urlPath);
172
166
  if (matchedRoutes) {
@@ -204,7 +198,7 @@ async function getRenderMode(req, fallbackHeader, isSSR, forceCSR, nodeReq, onFa
204
198
  if (req.headers.get('x-rsc-action')) return 'rsc-action';
205
199
  if (req.headers.get('x-rsc-tree')) return 'rsc-tree';
206
200
  if (!isSSR) return 'csr';
207
- {
201
+ else {
208
202
  if (query.__loader) return 'data';
209
203
  const fallbackHeaderValue = req.headers.get(fallbackHeader) || nodeReq?.headers[fallbackHeader];
210
204
  if (forceCSR && (query.csr || fallbackHeaderValue || contextForceCSR)) {
@@ -233,6 +227,6 @@ async function csrRender(request, options) {
233
227
  [X_MODERNJS_RENDER]: 'client'
234
228
  })
235
229
  });
236
- return csrRscRender(request, options);
230
+ else return csrRscRender(request, options);
237
231
  }
238
232
  export { createRender, matchRoute };
@@ -2,54 +2,88 @@ import "node:module";
2
2
  import { createMemoryStorage } from "@modern-js/runtime-utils/storer";
3
3
  import { X_RENDER_CACHE } from "../../constants.mjs";
4
4
  import { createTransformStream, getPathname } from "../../utils/index.mjs";
5
+ const preventsSharedCaching = (headers)=>/(?:^|,)\s*(?:private|no-store|no-cache)\s*(?:[=,]|$)/i.test(headers.get('cache-control') || '');
6
+ const isCacheableResponse = (response)=>200 === response.status && !preventsSharedCaching(response.headers) && !response.headers.has('set-cookie') && !response.headers.get('vary');
5
7
  const removeTailSlash = (s)=>s.replace(/\/+$/, '');
6
8
  const ZERO_RENDER_LEVEL = /"renderLevel":0/;
7
9
  const NO_SSR_CACHE = /<meta\s+[^>]*name=["']no-ssr-cache["'][^>]*>/i;
8
10
  async function processCache({ request, key, requestHandler, requestHandlerOptions, ttl, container, cacheStatus }) {
9
11
  const response = await requestHandler(request, requestHandlerOptions);
10
12
  const { onError } = requestHandlerOptions;
11
- const nonCacheableStatusCodes = [
12
- 204,
13
- 305,
14
- 404,
15
- 405,
16
- 500,
17
- 501,
18
- 502,
19
- 503,
20
- 504
21
- ];
22
- if (nonCacheableStatusCodes.includes(response.status)) return response;
13
+ const deleteCache = async ()=>{
14
+ try {
15
+ await container.delete(key);
16
+ } catch {
17
+ (onError || console.error)('[render-cache] delete cache failed');
18
+ }
19
+ };
20
+ if (!isCacheableResponse(response) || !response.body) {
21
+ await deleteCache();
22
+ return response;
23
+ }
24
+ const headers = Object.fromEntries(response.headers);
23
25
  const decoder = new TextDecoder();
24
26
  if (response.body) {
25
27
  const stream = createTransformStream();
26
28
  const reader = response.body.getReader();
27
29
  const writer = stream.writable.getWriter();
28
30
  let html = '';
29
- const push = ()=>reader.read().then(({ done, value })=>{
30
- if (done) {
31
- const match = ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html);
32
- if (match) return void writer.close();
33
- const current = Date.now();
34
- const cache = {
35
- val: html,
36
- cursor: current
37
- };
38
- container.set(key, JSON.stringify(cache), {
31
+ let cancelled = false;
32
+ const cancel = async (reason)=>{
33
+ cancelled = true;
34
+ await reader.cancel(reason);
35
+ };
36
+ writer.closed.catch(cancel).catch(()=>{});
37
+ const onAbort = ()=>{
38
+ Promise.allSettled([
39
+ cancel(request.signal.reason),
40
+ writer.abort(request.signal.reason)
41
+ ]);
42
+ };
43
+ request.signal.addEventListener('abort', onAbort, {
44
+ once: true
45
+ });
46
+ if (request.signal.aborted) onAbort();
47
+ const pumping = (async ()=>{
48
+ try {
49
+ for(;;){
50
+ const { done, value } = await reader.read();
51
+ if (cancelled) return;
52
+ if (done) break;
53
+ html += decoder.decode(value, {
54
+ stream: true
55
+ });
56
+ await writer.write(value);
57
+ }
58
+ html += decoder.decode();
59
+ await writer.close();
60
+ if (cancelled) return;
61
+ if (ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html)) return void await deleteCache();
62
+ const cache = {
63
+ val: html,
64
+ cursor: Date.now(),
65
+ headers
66
+ };
67
+ try {
68
+ await container.set(key, JSON.stringify(cache), {
39
69
  ttl
40
- }).catch(()=>{
41
- if (onError) onError(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
42
- else console.error(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
43
70
  });
44
- writer.close();
45
- return;
71
+ } catch (error) {
72
+ (onError || console.error)('[render-cache] set cache failed');
46
73
  }
47
- const content = decoder.decode(value);
48
- html += content;
49
- writer.write(value);
50
- push();
51
- });
52
- push();
74
+ } catch (error) {
75
+ await Promise.allSettled([
76
+ cancel(error),
77
+ writer.abort(error)
78
+ ]);
79
+ if (cacheStatus) try {
80
+ (onError || console.error)('[render-cache] response stream failed');
81
+ } catch {}
82
+ } finally{
83
+ request.signal.removeEventListener('abort', onAbort);
84
+ }
85
+ })();
86
+ pumping.catch(()=>{});
53
87
  cacheStatus && response.headers.set(X_RENDER_CACHE, cacheStatus);
54
88
  return new Response(stream.readable, {
55
89
  status: response.status,
@@ -64,9 +98,12 @@ function computedKey(req, cacheControl) {
64
98
  const pathname = getPathname(req);
65
99
  const { customKey } = cacheControl;
66
100
  const defaultKey = '/' === pathname ? pathname : removeTailSlash(pathname);
67
- if (!customKey) return defaultKey;
68
- if ('string' == typeof customKey) return customKey;
69
- return customKey(defaultKey);
101
+ if (customKey) if ('string' == typeof customKey) return customKey;
102
+ else return customKey(defaultKey);
103
+ else {
104
+ const url = new URL(req.url);
105
+ return `${url.origin}${defaultKey}${url.search}`;
106
+ }
70
107
  }
71
108
  function shouldUseCache(request) {
72
109
  const url = new URL(request.url);
@@ -77,7 +114,7 @@ function shouldUseCache(request) {
77
114
  }
78
115
  function matchCacheControl(cacheOption, req) {
79
116
  if (!cacheOption || !req) return;
80
- {
117
+ else {
81
118
  if (isCacheControl(cacheOption)) return cacheOption;
82
119
  if (isCacheOptionProvider(cacheOption)) return cacheOption(req);
83
120
  const url = req.url;
@@ -96,13 +133,15 @@ function matchCacheControl(cacheOption, req) {
96
133
  async function getCacheResult(request, options) {
97
134
  const { cacheControl, container = storage, requestHandler, requestHandlerOptions } = options;
98
135
  const { onError } = requestHandlerOptions;
99
- const key = computedKey(request, cacheControl);
136
+ const hasCredentials = request.headers.has('cookie') || request.headers.has('authorization');
137
+ if ('GET' !== request.method || !shouldUseCache(request) || preventsSharedCaching(request.headers) || hasCredentials && !cacheControl.customKey) return requestHandler(request, requestHandlerOptions);
138
+ const key = `${CACHE_NAMESPACE}:v2:${computedKey(request, cacheControl)}`;
100
139
  let value;
101
140
  try {
102
141
  value = await container.get(key);
103
142
  } catch (_) {
104
- if (onError) onError(`[render-cache] get cache failed, key: ${key}`);
105
- else console.error(`[render-cache] get cache failed, key: ${key}`);
143
+ if (onError) onError('[render-cache] get cache failed');
144
+ else console.error('[render-cache] get cache failed');
106
145
  value = void 0;
107
146
  }
108
147
  const { maxAge, staleWhileRevalidate } = cacheControl;
@@ -116,27 +155,27 @@ async function getCacheResult(request, options) {
116
155
  container,
117
156
  cacheStatus: 'miss'
118
157
  });
119
- {
158
+ else {
120
159
  const cache = JSON.parse(value);
121
160
  const interval = Date.now() - cache.cursor;
122
161
  if (interval <= maxAge) {
123
162
  const cacheStatus = 'hit';
124
163
  return new Response(cache.val, {
125
164
  headers: {
165
+ ...cache.headers,
126
166
  [X_RENDER_CACHE]: cacheStatus
127
167
  }
128
168
  });
129
- }
130
- if (!(interval <= staleWhileRevalidate + maxAge)) return processCache({
131
- key,
132
- request,
133
- requestHandler,
134
- requestHandlerOptions,
135
- ttl,
136
- container,
137
- cacheStatus: 'expired'
138
- });
139
- {
169
+ } else {
170
+ if (!(interval <= staleWhileRevalidate + maxAge)) return processCache({
171
+ key,
172
+ request,
173
+ requestHandler,
174
+ requestHandlerOptions,
175
+ ttl,
176
+ container,
177
+ cacheStatus: 'expired'
178
+ });
140
179
  processCache({
141
180
  key,
142
181
  request,
@@ -146,10 +185,15 @@ async function getCacheResult(request, options) {
146
185
  container
147
186
  }).then(async (response)=>{
148
187
  await response.text();
188
+ }).catch((error)=>{
189
+ try {
190
+ (onError || console.error)('[render-cache] revalidation failed');
191
+ } catch {}
149
192
  });
150
193
  const cacheStatus = 'stale';
151
194
  return new Response(cache.val, {
152
195
  headers: {
196
+ ...cache.headers,
153
197
  [X_RENDER_CACHE]: cacheStatus
154
198
  }
155
199
  });
@@ -147,7 +147,7 @@ function applyMiddlewares() {
147
147
  if (middleware.before) {
148
148
  const targetIndex = finalMiddlewares.findIndex((item)=>{
149
149
  if (middleware.before?.includes(item.name)) return true;
150
- return false;
150
+ else return false;
151
151
  });
152
152
  if (-1 !== targetIndex) finalMiddlewares.splice(targetIndex, 0, middleware);
153
153
  else finalMiddlewares.push(middleware);
@@ -168,7 +168,7 @@ function applyMiddlewares() {
168
168
  }
169
169
  function handler2Handlers(handler) {
170
170
  if (Array.isArray(handler)) return handler;
171
- return [
171
+ else return [
172
172
  handler
173
173
  ];
174
174
  }
@@ -15,7 +15,7 @@ function transformResponse(response, transformCb) {
15
15
  if (response.body) {
16
16
  const stream = createTransformStream(async (before)=>{
17
17
  if (Array.isArray(transformCb)) return transformCb.reduce(async (before, cb)=>cb(await before), Promise.resolve(before));
18
- return transformCb(before);
18
+ else return transformCb(before);
19
19
  });
20
20
  response.body.pipeThrough(stream);
21
21
  readable = stream.readable;