@bleedingdev/modern-js-server-core 3.9.0-ultramodern.10 → 3.9.0-ultramodern.11

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.
@@ -35,54 +35,67 @@ __webpack_require__.d(__webpack_exports__, {
35
35
  const storer_namespaceObject = require("@modern-js/runtime-utils/storer");
36
36
  const external_constants_js_namespaceObject = require("../../constants.js");
37
37
  const index_js_namespaceObject = require("../../utils/index.js");
38
+ const preventsSharedCaching = (headers)=>/(?:^|,)\s*(?:private|no-store|no-cache)\s*(?:[=,]|$)/i.test(headers.get('cache-control') || '');
39
+ const isCacheableResponse = (response)=>200 === response.status && !preventsSharedCaching(response.headers) && !response.headers.has('set-cookie') && !response.headers.get('vary');
38
40
  const removeTailSlash = (s)=>s.replace(/\/+$/, '');
39
41
  const ZERO_RENDER_LEVEL = /"renderLevel":0/;
40
42
  const NO_SSR_CACHE = /<meta\s+[^>]*name=["']no-ssr-cache["'][^>]*>/i;
41
43
  async function processCache({ request, key, requestHandler, requestHandlerOptions, ttl, container, cacheStatus }) {
42
44
  const response = await requestHandler(request, requestHandlerOptions);
43
45
  const { onError } = requestHandlerOptions;
44
- const nonCacheableStatusCodes = [
45
- 204,
46
- 305,
47
- 404,
48
- 405,
49
- 500,
50
- 501,
51
- 502,
52
- 503,
53
- 504
54
- ];
55
- if (nonCacheableStatusCodes.includes(response.status)) return response;
46
+ const deleteCache = async ()=>{
47
+ try {
48
+ await container.delete(key);
49
+ } catch {
50
+ (onError || console.error)('[render-cache] delete cache failed');
51
+ }
52
+ };
53
+ if (!isCacheableResponse(response) || !response.body) {
54
+ await deleteCache();
55
+ return response;
56
+ }
57
+ const headers = Object.fromEntries(response.headers);
56
58
  const decoder = new TextDecoder();
57
59
  if (response.body) {
58
60
  const stream = (0, index_js_namespaceObject.createTransformStream)();
59
61
  const reader = response.body.getReader();
60
62
  const writer = stream.writable.getWriter();
61
63
  let html = '';
62
- const push = ()=>reader.read().then(({ done, value })=>{
64
+ const push = ()=>reader.read().then(async ({ done, value })=>{
63
65
  if (done) {
66
+ html += decoder.decode();
64
67
  const match = ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html);
65
- if (match) return void writer.close();
68
+ if (match) {
69
+ await deleteCache();
70
+ return writer.close();
71
+ }
66
72
  const current = Date.now();
67
73
  const cache = {
68
74
  val: html,
69
- cursor: current
75
+ cursor: current,
76
+ headers
70
77
  };
71
78
  container.set(key, JSON.stringify(cache), {
72
79
  ttl
73
80
  }).catch(()=>{
74
- if (onError) onError(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
75
- else console.error(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
81
+ (onError || console.error)('[render-cache] set cache failed');
76
82
  });
77
- writer.close();
78
- return;
83
+ return writer.close();
79
84
  }
80
- const content = decoder.decode(value);
85
+ const content = decoder.decode(value, {
86
+ stream: true
87
+ });
81
88
  html += content;
82
- writer.write(value);
83
- push();
89
+ await writer.write(value);
90
+ return push();
84
91
  });
85
- push();
92
+ push().catch(async (error)=>{
93
+ await Promise.allSettled([
94
+ writer.abort(error),
95
+ reader.cancel(error)
96
+ ]);
97
+ (onError || console.error)('[render-cache] response stream failed');
98
+ });
86
99
  cacheStatus && response.headers.set(external_constants_js_namespaceObject.X_RENDER_CACHE, cacheStatus);
87
100
  return new Response(stream.readable, {
88
101
  status: response.status,
@@ -97,9 +110,12 @@ function computedKey(req, cacheControl) {
97
110
  const pathname = (0, index_js_namespaceObject.getPathname)(req);
98
111
  const { customKey } = cacheControl;
99
112
  const defaultKey = '/' === pathname ? pathname : removeTailSlash(pathname);
100
- if (!customKey) return defaultKey;
101
- if ('string' == typeof customKey) return customKey;
102
- return customKey(defaultKey);
113
+ if (customKey) if ('string' == typeof customKey) return customKey;
114
+ else return customKey(defaultKey);
115
+ {
116
+ const url = new URL(req.url);
117
+ return `${url.origin}${defaultKey}${url.search}`;
118
+ }
103
119
  }
104
120
  function shouldUseCache(request) {
105
121
  const url = new URL(request.url);
@@ -129,13 +145,15 @@ function matchCacheControl(cacheOption, req) {
129
145
  async function getCacheResult(request, options) {
130
146
  const { cacheControl, container = storage, requestHandler, requestHandlerOptions } = options;
131
147
  const { onError } = requestHandlerOptions;
132
- const key = computedKey(request, cacheControl);
148
+ const hasCredentials = request.headers.has('cookie') || request.headers.has('authorization');
149
+ if ('GET' !== request.method || !shouldUseCache(request) || preventsSharedCaching(request.headers) || hasCredentials && !cacheControl.customKey) return requestHandler(request, requestHandlerOptions);
150
+ const key = `${CACHE_NAMESPACE}:v2:${computedKey(request, cacheControl)}`;
133
151
  let value;
134
152
  try {
135
153
  value = await container.get(key);
136
154
  } catch (_) {
137
- if (onError) onError(`[render-cache] get cache failed, key: ${key}`);
138
- else console.error(`[render-cache] get cache failed, key: ${key}`);
155
+ if (onError) onError('[render-cache] get cache failed');
156
+ else console.error('[render-cache] get cache failed');
139
157
  value = void 0;
140
158
  }
141
159
  const { maxAge, staleWhileRevalidate } = cacheControl;
@@ -156,6 +174,7 @@ async function getCacheResult(request, options) {
156
174
  const cacheStatus = 'hit';
157
175
  return new Response(cache.val, {
158
176
  headers: {
177
+ ...cache.headers,
159
178
  [external_constants_js_namespaceObject.X_RENDER_CACHE]: cacheStatus
160
179
  }
161
180
  });
@@ -179,10 +198,13 @@ async function getCacheResult(request, options) {
179
198
  container
180
199
  }).then(async (response)=>{
181
200
  await response.text();
201
+ }).catch(()=>{
202
+ (onError || console.error)('[render-cache] revalidation failed');
182
203
  });
183
204
  const cacheStatus = 'stale';
184
205
  return new Response(cache.val, {
185
206
  headers: {
207
+ ...cache.headers,
186
208
  [external_constants_js_namespaceObject.X_RENDER_CACHE]: cacheStatus
187
209
  }
188
210
  });
@@ -1,54 +1,67 @@
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 })=>{
30
+ const push = ()=>reader.read().then(async ({ done, value })=>{
29
31
  if (done) {
32
+ html += decoder.decode();
30
33
  const match = ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html);
31
- if (match) return void writer.close();
34
+ if (match) {
35
+ await deleteCache();
36
+ return writer.close();
37
+ }
32
38
  const current = Date.now();
33
39
  const cache = {
34
40
  val: html,
35
- cursor: current
41
+ cursor: current,
42
+ headers
36
43
  };
37
44
  container.set(key, JSON.stringify(cache), {
38
45
  ttl
39
46
  }).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)}`);
47
+ (onError || console.error)('[render-cache] set cache failed');
42
48
  });
43
- writer.close();
44
- return;
49
+ return writer.close();
45
50
  }
46
- const content = decoder.decode(value);
51
+ const content = decoder.decode(value, {
52
+ stream: true
53
+ });
47
54
  html += content;
48
- writer.write(value);
49
- push();
55
+ await writer.write(value);
56
+ return push();
50
57
  });
51
- push();
58
+ push().catch(async (error)=>{
59
+ await Promise.allSettled([
60
+ writer.abort(error),
61
+ reader.cancel(error)
62
+ ]);
63
+ (onError || console.error)('[render-cache] response stream failed');
64
+ });
52
65
  cacheStatus && response.headers.set(X_RENDER_CACHE, cacheStatus);
53
66
  return new Response(stream.readable, {
54
67
  status: response.status,
@@ -63,9 +76,12 @@ function computedKey(req, cacheControl) {
63
76
  const pathname = getPathname(req);
64
77
  const { customKey } = cacheControl;
65
78
  const defaultKey = '/' === pathname ? pathname : removeTailSlash(pathname);
66
- if (!customKey) return defaultKey;
67
- if ('string' == typeof customKey) return customKey;
68
- return customKey(defaultKey);
79
+ if (customKey) if ('string' == typeof customKey) return customKey;
80
+ else return customKey(defaultKey);
81
+ {
82
+ const url = new URL(req.url);
83
+ return `${url.origin}${defaultKey}${url.search}`;
84
+ }
69
85
  }
70
86
  function shouldUseCache(request) {
71
87
  const url = new URL(request.url);
@@ -95,13 +111,15 @@ function matchCacheControl(cacheOption, req) {
95
111
  async function getCacheResult(request, options) {
96
112
  const { cacheControl, container = storage, requestHandler, requestHandlerOptions } = options;
97
113
  const { onError } = requestHandlerOptions;
98
- const key = computedKey(request, cacheControl);
114
+ const hasCredentials = request.headers.has('cookie') || request.headers.has('authorization');
115
+ if ('GET' !== request.method || !shouldUseCache(request) || preventsSharedCaching(request.headers) || hasCredentials && !cacheControl.customKey) return requestHandler(request, requestHandlerOptions);
116
+ const key = `${CACHE_NAMESPACE}:v2:${computedKey(request, cacheControl)}`;
99
117
  let value;
100
118
  try {
101
119
  value = await container.get(key);
102
120
  } catch (_) {
103
- if (onError) onError(`[render-cache] get cache failed, key: ${key}`);
104
- else console.error(`[render-cache] get cache failed, key: ${key}`);
121
+ if (onError) onError('[render-cache] get cache failed');
122
+ else console.error('[render-cache] get cache failed');
105
123
  value = void 0;
106
124
  }
107
125
  const { maxAge, staleWhileRevalidate } = cacheControl;
@@ -122,6 +140,7 @@ async function getCacheResult(request, options) {
122
140
  const cacheStatus = 'hit';
123
141
  return new Response(cache.val, {
124
142
  headers: {
143
+ ...cache.headers,
125
144
  [X_RENDER_CACHE]: cacheStatus
126
145
  }
127
146
  });
@@ -145,10 +164,13 @@ async function getCacheResult(request, options) {
145
164
  container
146
165
  }).then(async (response)=>{
147
166
  await response.text();
167
+ }).catch(()=>{
168
+ (onError || console.error)('[render-cache] revalidation failed');
148
169
  });
149
170
  const cacheStatus = 'stale';
150
171
  return new Response(cache.val, {
151
172
  headers: {
173
+ ...cache.headers,
152
174
  [X_RENDER_CACHE]: cacheStatus
153
175
  }
154
176
  });
@@ -2,54 +2,67 @@ 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 })=>{
31
+ const push = ()=>reader.read().then(async ({ done, value })=>{
30
32
  if (done) {
33
+ html += decoder.decode();
31
34
  const match = ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html);
32
- if (match) return void writer.close();
35
+ if (match) {
36
+ await deleteCache();
37
+ return writer.close();
38
+ }
33
39
  const current = Date.now();
34
40
  const cache = {
35
41
  val: html,
36
- cursor: current
42
+ cursor: current,
43
+ headers
37
44
  };
38
45
  container.set(key, JSON.stringify(cache), {
39
46
  ttl
40
47
  }).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)}`);
48
+ (onError || console.error)('[render-cache] set cache failed');
43
49
  });
44
- writer.close();
45
- return;
50
+ return writer.close();
46
51
  }
47
- const content = decoder.decode(value);
52
+ const content = decoder.decode(value, {
53
+ stream: true
54
+ });
48
55
  html += content;
49
- writer.write(value);
50
- push();
56
+ await writer.write(value);
57
+ return push();
51
58
  });
52
- push();
59
+ push().catch(async (error)=>{
60
+ await Promise.allSettled([
61
+ writer.abort(error),
62
+ reader.cancel(error)
63
+ ]);
64
+ (onError || console.error)('[render-cache] response stream failed');
65
+ });
53
66
  cacheStatus && response.headers.set(X_RENDER_CACHE, cacheStatus);
54
67
  return new Response(stream.readable, {
55
68
  status: response.status,
@@ -64,9 +77,12 @@ function computedKey(req, cacheControl) {
64
77
  const pathname = getPathname(req);
65
78
  const { customKey } = cacheControl;
66
79
  const defaultKey = '/' === pathname ? pathname : removeTailSlash(pathname);
67
- if (!customKey) return defaultKey;
68
- if ('string' == typeof customKey) return customKey;
69
- return customKey(defaultKey);
80
+ if (customKey) if ('string' == typeof customKey) return customKey;
81
+ else return customKey(defaultKey);
82
+ {
83
+ const url = new URL(req.url);
84
+ return `${url.origin}${defaultKey}${url.search}`;
85
+ }
70
86
  }
71
87
  function shouldUseCache(request) {
72
88
  const url = new URL(request.url);
@@ -96,13 +112,15 @@ function matchCacheControl(cacheOption, req) {
96
112
  async function getCacheResult(request, options) {
97
113
  const { cacheControl, container = storage, requestHandler, requestHandlerOptions } = options;
98
114
  const { onError } = requestHandlerOptions;
99
- const key = computedKey(request, cacheControl);
115
+ const hasCredentials = request.headers.has('cookie') || request.headers.has('authorization');
116
+ if ('GET' !== request.method || !shouldUseCache(request) || preventsSharedCaching(request.headers) || hasCredentials && !cacheControl.customKey) return requestHandler(request, requestHandlerOptions);
117
+ const key = `${CACHE_NAMESPACE}:v2:${computedKey(request, cacheControl)}`;
100
118
  let value;
101
119
  try {
102
120
  value = await container.get(key);
103
121
  } catch (_) {
104
- if (onError) onError(`[render-cache] get cache failed, key: ${key}`);
105
- else console.error(`[render-cache] get cache failed, key: ${key}`);
122
+ if (onError) onError('[render-cache] get cache failed');
123
+ else console.error('[render-cache] get cache failed');
106
124
  value = void 0;
107
125
  }
108
126
  const { maxAge, staleWhileRevalidate } = cacheControl;
@@ -123,6 +141,7 @@ async function getCacheResult(request, options) {
123
141
  const cacheStatus = 'hit';
124
142
  return new Response(cache.val, {
125
143
  headers: {
144
+ ...cache.headers,
126
145
  [X_RENDER_CACHE]: cacheStatus
127
146
  }
128
147
  });
@@ -146,10 +165,13 @@ async function getCacheResult(request, options) {
146
165
  container
147
166
  }).then(async (response)=>{
148
167
  await response.text();
168
+ }).catch(()=>{
169
+ (onError || console.error)('[render-cache] revalidation failed');
149
170
  });
150
171
  const cacheStatus = 'stale';
151
172
  return new Response(cache.val, {
152
173
  headers: {
174
+ ...cache.headers,
153
175
  [X_RENDER_CACHE]: cacheStatus
154
176
  }
155
177
  });
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "modern",
18
18
  "modern.js"
19
19
  ],
20
- "version": "3.9.0-ultramodern.10",
20
+ "version": "3.9.0-ultramodern.11",
21
21
  "types": "./dist/types/index.d.ts",
22
22
  "main": "./dist/cjs/index.js",
23
23
  "exports": {
@@ -66,9 +66,9 @@
66
66
  "node": ">=20"
67
67
  },
68
68
  "dependencies": {
69
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.10",
70
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.10",
71
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.10",
69
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.11",
70
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.11",
71
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.11",
72
72
  "@swc/helpers": "^0.5.23",
73
73
  "@web-std/fetch": "^4.2.1",
74
74
  "@web-std/file": "^3.0.3",
@@ -79,7 +79,7 @@
79
79
  "ts-deepmerge": "8.0.0"
80
80
  },
81
81
  "devDependencies": {
82
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.10",
82
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.11",
83
83
  "@rslib/core": "1.0.0",
84
84
  "@scripts/rstest-config": "2.66.0",
85
85
  "@types/cloneable-readable": "^2.0.3",