@tramvai/module-server 7.5.3 → 7.7.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.
package/README.md CHANGED
@@ -236,6 +236,36 @@ HTTP/1.1 200 OK
236
236
 
237
237
  ```
238
238
 
239
+ ### ETag header
240
+
241
+ Tramvai support [ETag header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag) generation for application pages - it can speed up the page loading for repeated visits.
242
+
243
+ :::warning
244
+
245
+ ETag generation is a heavy operation for a big pages and can negatively affect the application performance.
246
+
247
+ :::
248
+
249
+ :::warning
250
+
251
+ ETag generation is incompatible with `@tramvai/module-opentelementry`, because request trace id is injected to the page HTML content in `<meta name="traceparent" />` tag.
252
+
253
+ :::
254
+
255
+ To enable ETag header, provide `ETAG_OPTIONS_TOKEN` token:
256
+
257
+ ```ts
258
+ const provider = provide({
259
+ provide: ETAG_OPTIONS_TOKEN,
260
+ useValue: {
261
+ enabled: true,
262
+ // by default, ETag is generated based on the content of the page, which is not the best option for performance,
263
+ // you can pass `weak: true` parameter to generate weak ETag based on the hash of the initial state of the page, which can be faster
264
+ weak: false,
265
+ },
266
+ });
267
+ ```
268
+
239
269
  ## How to
240
270
 
241
271
  ### Setting `keepAliveTimeout` for the server
@@ -0,0 +1,7 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * @reference https://github.com/fastify/fastify-etag/blob/main/fnv1a.js
4
+ * Work faster than `etag` and `fnv-plus` libraries
5
+ */
6
+ export declare function fnv1a(str: string | Buffer): number;
7
+ //# sourceMappingURL=fnv1a.d.ts.map
@@ -0,0 +1,87 @@
1
+ /* eslint-disable no-bitwise, @typescript-eslint/no-shadow */
2
+ // MIT License
3
+ //
4
+ // Copyright (c) desudesutalk (https://github.com/desudesutalk)
5
+ // Copyright (c) Travis Webb <me@traviswebb.com>
6
+ // Copyright (c) SukkaW <hi@skk.moe> (https://skk.moe)
7
+ //
8
+ // Permission is hereby granted, free of charge, to any person obtaining a
9
+ // copy of this software and associated documentation files (the "Software"),
10
+ // to deal in the Software without restriction, including without limitation
11
+ // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12
+ // and/or sell copies of the Software, and to permit persons to whom the Software
13
+ // is furnished to do so, subject to the following conditions:
14
+ //
15
+ // The above copyright notice and this permission notice shall be included in all
16
+ // copies or substantial portions of the Software.
17
+ //
18
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
19
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ // The fnv1a implementation is created by @desudesutalk (https://github.com/desudesutalk) in
25
+ // https://github.com/tjwebb/fnv-plus/pull/9, and contributed to Travis Webb's
26
+ // "fnv-plus" library. @SukkaW (https://github.com/SukkaW) modifies it to support Buffers w/o
27
+ // sacrificing performance
28
+ /**
29
+ * @reference https://github.com/fastify/fastify-etag/blob/main/fnv1a.js
30
+ * Work faster than `etag` and `fnv-plus` libraries
31
+ */
32
+ // eslint-disable-next-line max-statements
33
+ function fnv1a(str) {
34
+ const l = str.length - 3;
35
+ let i = 0;
36
+ let t0 = 0;
37
+ let v0 = 0x9dc5;
38
+ let t1 = 0;
39
+ let v1 = 0x811c;
40
+ let get;
41
+ if (str instanceof Buffer) {
42
+ get = (i) => str[i];
43
+ }
44
+ else if (typeof str === 'string') {
45
+ get = (i) => str.charCodeAt(i);
46
+ }
47
+ else {
48
+ throw new TypeError('input must be a string or a buffer');
49
+ }
50
+ while (i < l) {
51
+ v0 ^= get(i++);
52
+ t0 = v0 * 403;
53
+ t1 = v1 * 403;
54
+ t1 += v0 << 8;
55
+ v1 = (t1 + (t0 >>> 16)) & 65535;
56
+ v0 = t0 & 65535;
57
+ v0 ^= get(i++);
58
+ t0 = v0 * 403;
59
+ t1 = v1 * 403;
60
+ t1 += v0 << 8;
61
+ v1 = (t1 + (t0 >>> 16)) & 65535;
62
+ v0 = t0 & 65535;
63
+ v0 ^= get(i++);
64
+ t0 = v0 * 403;
65
+ t1 = v1 * 403;
66
+ t1 += v0 << 8;
67
+ v1 = (t1 + (t0 >>> 16)) & 65535;
68
+ v0 = t0 & 65535;
69
+ v0 ^= get(i++);
70
+ t0 = v0 * 403;
71
+ t1 = v1 * 403;
72
+ t1 += v0 << 8;
73
+ v1 = (t1 + (t0 >>> 16)) & 65535;
74
+ v0 = t0 & 65535;
75
+ }
76
+ while (i < l + 3) {
77
+ v0 ^= get(i++);
78
+ t0 = v0 * 403;
79
+ t1 = v1 * 403;
80
+ t1 += v0 << 8;
81
+ v1 = (t1 + (t0 >>> 16)) & 65535;
82
+ v0 = t0 & 65535;
83
+ }
84
+ return ((v1 << 16) >>> 0) + v0;
85
+ }
86
+
87
+ export { fnv1a };
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /* eslint-disable no-bitwise, @typescript-eslint/no-shadow */
6
+ // MIT License
7
+ //
8
+ // Copyright (c) desudesutalk (https://github.com/desudesutalk)
9
+ // Copyright (c) Travis Webb <me@traviswebb.com>
10
+ // Copyright (c) SukkaW <hi@skk.moe> (https://skk.moe)
11
+ //
12
+ // Permission is hereby granted, free of charge, to any person obtaining a
13
+ // copy of this software and associated documentation files (the "Software"),
14
+ // to deal in the Software without restriction, including without limitation
15
+ // the rights to use, copy, modify, merge, publish, distribute, sublicense,
16
+ // and/or sell copies of the Software, and to permit persons to whom the Software
17
+ // is furnished to do so, subject to the following conditions:
18
+ //
19
+ // The above copyright notice and this permission notice shall be included in all
20
+ // copies or substantial portions of the Software.
21
+ //
22
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
23
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
24
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
25
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+ // The fnv1a implementation is created by @desudesutalk (https://github.com/desudesutalk) in
29
+ // https://github.com/tjwebb/fnv-plus/pull/9, and contributed to Travis Webb's
30
+ // "fnv-plus" library. @SukkaW (https://github.com/SukkaW) modifies it to support Buffers w/o
31
+ // sacrificing performance
32
+ /**
33
+ * @reference https://github.com/fastify/fastify-etag/blob/main/fnv1a.js
34
+ * Work faster than `etag` and `fnv-plus` libraries
35
+ */
36
+ // eslint-disable-next-line max-statements
37
+ function fnv1a(str) {
38
+ const l = str.length - 3;
39
+ let i = 0;
40
+ let t0 = 0;
41
+ let v0 = 0x9dc5;
42
+ let t1 = 0;
43
+ let v1 = 0x811c;
44
+ let get;
45
+ if (str instanceof Buffer) {
46
+ get = (i) => str[i];
47
+ }
48
+ else if (typeof str === 'string') {
49
+ get = (i) => str.charCodeAt(i);
50
+ }
51
+ else {
52
+ throw new TypeError('input must be a string or a buffer');
53
+ }
54
+ while (i < l) {
55
+ v0 ^= get(i++);
56
+ t0 = v0 * 403;
57
+ t1 = v1 * 403;
58
+ t1 += v0 << 8;
59
+ v1 = (t1 + (t0 >>> 16)) & 65535;
60
+ v0 = t0 & 65535;
61
+ v0 ^= get(i++);
62
+ t0 = v0 * 403;
63
+ t1 = v1 * 403;
64
+ t1 += v0 << 8;
65
+ v1 = (t1 + (t0 >>> 16)) & 65535;
66
+ v0 = t0 & 65535;
67
+ v0 ^= get(i++);
68
+ t0 = v0 * 403;
69
+ t1 = v1 * 403;
70
+ t1 += v0 << 8;
71
+ v1 = (t1 + (t0 >>> 16)) & 65535;
72
+ v0 = t0 & 65535;
73
+ v0 ^= get(i++);
74
+ t0 = v0 * 403;
75
+ t1 = v1 * 403;
76
+ t1 += v0 << 8;
77
+ v1 = (t1 + (t0 >>> 16)) & 65535;
78
+ v0 = t0 & 65535;
79
+ }
80
+ while (i < l + 3) {
81
+ v0 ^= get(i++);
82
+ t0 = v0 * 403;
83
+ t1 = v1 * 403;
84
+ t1 += v0 << 8;
85
+ v1 = (t1 + (t0 >>> 16)) & 65535;
86
+ v0 = t0 & 65535;
87
+ }
88
+ return ((v1 << 16) >>> 0) + v0;
89
+ }
90
+
91
+ exports.fnv1a = fnv1a;
@@ -0,0 +1,19 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ export declare const providers: (import("@tinkoff/dippy").Provider<{
4
+ etagOptions: {
5
+ enabled: boolean;
6
+ weak?: boolean;
7
+ } & {
8
+ __type?: "base token";
9
+ };
10
+ storage: import("async_hooks").AsyncLocalStorage<import("@tramvai/tokens-common").AsyncLocalStorageState> & {
11
+ __type?: "base token";
12
+ };
13
+ }, (app: import("fastify").FastifyInstance<import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>) => void> | import("@tinkoff/dippy").Provider<unknown, {
14
+ enabled: boolean;
15
+ weak?: boolean;
16
+ } & {
17
+ __type?: "base token";
18
+ }>)[];
19
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { provide } from '@tinkoff/dippy';
2
+ import { REQUEST_MANAGER_TOKEN, RESPONSE_MANAGER_TOKEN, CONTEXT_TOKEN, ASYNC_LOCAL_STORAGE_TOKEN } from '@tramvai/tokens-common';
3
+ import { ETAG_OPTIONS_TOKEN } from '@tramvai/tokens-server';
4
+ import { WEB_FASTIFY_APP_INIT_TOKEN } from '@tramvai/tokens-server-private';
5
+ import { fnv1a } from './fnv1a.es.js';
6
+
7
+ const providers = [
8
+ provide({
9
+ provide: WEB_FASTIFY_APP_INIT_TOKEN,
10
+ useFactory: ({ etagOptions, storage }) => {
11
+ return (app) => {
12
+ if (!etagOptions.enabled) {
13
+ return;
14
+ }
15
+ app.addHook('onSend', function (req, reply, payload, done) {
16
+ const di = storage.getStore()?.tramvaiRequestDi;
17
+ if (di) {
18
+ const requestManager = di.get(REQUEST_MANAGER_TOKEN);
19
+ const responseManager = di.get(RESPONSE_MANAGER_TOKEN);
20
+ const context = di.get(CONTEXT_TOKEN);
21
+ const currentEtag = responseManager.getHeader('etag');
22
+ if (!currentEtag) {
23
+ const prefix = etagOptions.weak ? 'W/"' : '"';
24
+ const etag = `${prefix +
25
+ fnv1a(etagOptions.weak
26
+ ? // TODO: customize weak strategy
27
+ JSON.stringify(context.dehydrate().dispatcher)
28
+ : responseManager.getBody()).toString(36)}"`;
29
+ responseManager.setHeader('etag', etag);
30
+ reply.header('etag', etag);
31
+ }
32
+ const etag = responseManager.getHeader('etag');
33
+ const ifNoneMatch = requestManager.getHeader('if-none-match');
34
+ if (ifNoneMatch === etag ||
35
+ ifNoneMatch === `W/${etag}` ||
36
+ `W/${ifNoneMatch}` === etag) {
37
+ reply.code(304);
38
+ done(null, '');
39
+ }
40
+ else {
41
+ done(null, payload);
42
+ }
43
+ }
44
+ else {
45
+ done(null, payload);
46
+ }
47
+ });
48
+ };
49
+ },
50
+ deps: {
51
+ etagOptions: ETAG_OPTIONS_TOKEN,
52
+ storage: ASYNC_LOCAL_STORAGE_TOKEN,
53
+ },
54
+ }),
55
+ provide({
56
+ provide: ETAG_OPTIONS_TOKEN,
57
+ useValue: {
58
+ enabled: false,
59
+ },
60
+ }),
61
+ ];
62
+
63
+ export { providers };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var dippy = require('@tinkoff/dippy');
6
+ var tokensCommon = require('@tramvai/tokens-common');
7
+ var tokensServer = require('@tramvai/tokens-server');
8
+ var tokensServerPrivate = require('@tramvai/tokens-server-private');
9
+ var fnv1a = require('./fnv1a.js');
10
+
11
+ const providers = [
12
+ dippy.provide({
13
+ provide: tokensServerPrivate.WEB_FASTIFY_APP_INIT_TOKEN,
14
+ useFactory: ({ etagOptions, storage }) => {
15
+ return (app) => {
16
+ if (!etagOptions.enabled) {
17
+ return;
18
+ }
19
+ app.addHook('onSend', function (req, reply, payload, done) {
20
+ const di = storage.getStore()?.tramvaiRequestDi;
21
+ if (di) {
22
+ const requestManager = di.get(tokensCommon.REQUEST_MANAGER_TOKEN);
23
+ const responseManager = di.get(tokensCommon.RESPONSE_MANAGER_TOKEN);
24
+ const context = di.get(tokensCommon.CONTEXT_TOKEN);
25
+ const currentEtag = responseManager.getHeader('etag');
26
+ if (!currentEtag) {
27
+ const prefix = etagOptions.weak ? 'W/"' : '"';
28
+ const etag = `${prefix +
29
+ fnv1a.fnv1a(etagOptions.weak
30
+ ? // TODO: customize weak strategy
31
+ JSON.stringify(context.dehydrate().dispatcher)
32
+ : responseManager.getBody()).toString(36)}"`;
33
+ responseManager.setHeader('etag', etag);
34
+ reply.header('etag', etag);
35
+ }
36
+ const etag = responseManager.getHeader('etag');
37
+ const ifNoneMatch = requestManager.getHeader('if-none-match');
38
+ if (ifNoneMatch === etag ||
39
+ ifNoneMatch === `W/${etag}` ||
40
+ `W/${ifNoneMatch}` === etag) {
41
+ reply.code(304);
42
+ done(null, '');
43
+ }
44
+ else {
45
+ done(null, payload);
46
+ }
47
+ }
48
+ else {
49
+ done(null, payload);
50
+ }
51
+ });
52
+ };
53
+ },
54
+ deps: {
55
+ etagOptions: tokensServer.ETAG_OPTIONS_TOKEN,
56
+ storage: tokensCommon.ASYNC_LOCAL_STORAGE_TOKEN,
57
+ },
58
+ }),
59
+ dippy.provide({
60
+ provide: tokensServer.ETAG_OPTIONS_TOKEN,
61
+ useValue: {
62
+ enabled: false,
63
+ },
64
+ }),
65
+ ];
66
+
67
+ exports.providers = providers;
package/lib/server.es.js CHANGED
@@ -28,6 +28,7 @@ import { KeepAliveModule } from './modules/keepAlive.es.js';
28
28
  import { ServerTimingModule } from './modules/serverTiming.es.js';
29
29
  import { EarlyHintsModule } from './modules/earlyHints/index.es.js';
30
30
  import { ServerResponseTaskManager } from './server/taskManager.es.js';
31
+ import { providers } from './server/etag/providers.es.js';
31
32
 
32
33
  if (typeof setDefaultResultOrder === 'function') {
33
34
  setDefaultResultOrder('ipv4first');
@@ -57,6 +58,7 @@ ServerModule = __decorate([
57
58
  process.env.NODE_ENV !== 'production' && DebugHttpRequestsModule,
58
59
  ].filter(Boolean),
59
60
  providers: [
61
+ ...providers,
60
62
  provide({
61
63
  provide: SERVER_FACTORY_TOKEN,
62
64
  scope: Scope.SINGLETON,
package/lib/server.js CHANGED
@@ -31,6 +31,7 @@ var keepAlive = require('./modules/keepAlive.js');
31
31
  var serverTiming = require('./modules/serverTiming.js');
32
32
  var index = require('./modules/earlyHints/index.js');
33
33
  var taskManager = require('./server/taskManager.js');
34
+ var providers = require('./server/etag/providers.js');
34
35
 
35
36
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
36
37
 
@@ -64,6 +65,7 @@ exports.ServerModule = tslib.__decorate([
64
65
  process.env.NODE_ENV !== 'production' && debugRequests.DebugHttpRequestsModule,
65
66
  ].filter(Boolean),
66
67
  providers: [
68
+ ...providers.providers,
67
69
  core.provide({
68
70
  provide: tokensServerPrivate.SERVER_FACTORY_TOKEN,
69
71
  scope: core.Scope.SINGLETON,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-server",
3
- "version": "7.5.3",
3
+ "version": "7.7.0",
4
4
  "description": "",
5
5
  "browser": "lib/browser.js",
6
6
  "main": "lib/server.js",
@@ -30,13 +30,13 @@
30
30
  "@tinkoff/monkeypatch": "7.0.1",
31
31
  "@tinkoff/terminus": "0.6.1",
32
32
  "@tinkoff/url": "0.13.1",
33
- "@tramvai/module-cache-warmup": "7.5.3",
34
- "@tramvai/module-metrics": "7.5.3",
35
- "@tramvai/papi": "7.5.3",
33
+ "@tramvai/module-cache-warmup": "7.7.0",
34
+ "@tramvai/module-metrics": "7.7.0",
35
+ "@tramvai/papi": "7.7.0",
36
36
  "@tramvai/safe-strings": "0.10.1",
37
- "@tramvai/tokens-router": "7.5.3",
38
- "@tramvai/tokens-server": "7.5.3",
39
- "@tramvai/tokens-server-private": "7.5.3",
37
+ "@tramvai/tokens-router": "7.7.0",
38
+ "@tramvai/tokens-server": "7.7.0",
39
+ "@tramvai/tokens-server-private": "7.7.0",
40
40
  "afterframe": "^1.0.2",
41
41
  "fastify": "^5.6.2",
42
42
  "http-proxy-middleware": "^2.0.2",
@@ -45,14 +45,14 @@
45
45
  "peerDependencies": {
46
46
  "@tinkoff/dippy": "0.13.2",
47
47
  "@tinkoff/utils": "^2.1.2",
48
- "@tramvai/cli": "7.5.3",
49
- "@tramvai/core": "7.5.3",
50
- "@tramvai/module-common": "7.5.3",
51
- "@tramvai/module-environment": "7.5.3",
52
- "@tramvai/react": "7.5.3",
53
- "@tramvai/tokens-common": "7.5.3",
54
- "@tramvai/tokens-core-private": "7.5.3",
55
- "@tramvai/tokens-render": "7.5.3",
48
+ "@tramvai/cli": "7.7.0",
49
+ "@tramvai/core": "7.7.0",
50
+ "@tramvai/module-common": "7.7.0",
51
+ "@tramvai/module-environment": "7.7.0",
52
+ "@tramvai/react": "7.7.0",
53
+ "@tramvai/tokens-common": "7.7.0",
54
+ "@tramvai/tokens-core-private": "7.7.0",
55
+ "@tramvai/tokens-render": "7.7.0",
56
56
  "react": ">=16.14.0",
57
57
  "react-dom": ">=16.14.0",
58
58
  "tslib": "^2.4.0"