@tramvai/module-server 2.134.0 → 2.137.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.
@@ -0,0 +1,9 @@
1
+ import type { ServerResponseTaskManager as IServerResponseTaskManager, ServerResponseTask } from '@tramvai/tokens-server-private';
2
+ export declare class ServerResponseTaskManager implements IServerResponseTaskManager {
3
+ private tasks;
4
+ private processedTasks;
5
+ private status;
6
+ push(task: ServerResponseTask): void;
7
+ processQueue(): void;
8
+ closeQueue(): Promise<any[]>;
9
+ }
@@ -0,0 +1,32 @@
1
+ class ServerResponseTaskManager {
2
+ constructor() {
3
+ this.tasks = [];
4
+ this.processedTasks = [];
5
+ this.status = 'idle';
6
+ }
7
+ push(task) {
8
+ if (this.status === 'closed') {
9
+ return;
10
+ }
11
+ this.tasks.push(task);
12
+ if (this.status === 'running') {
13
+ this.processQueue();
14
+ }
15
+ }
16
+ processQueue() {
17
+ if (this.status === 'closed') {
18
+ return;
19
+ }
20
+ this.status = 'running';
21
+ while (this.tasks.length > 0 && this.status === 'running') {
22
+ const task = this.tasks.shift();
23
+ this.processedTasks.push(task());
24
+ }
25
+ }
26
+ closeQueue() {
27
+ this.status = 'closed';
28
+ return Promise.all(this.processedTasks);
29
+ }
30
+ }
31
+
32
+ export { ServerResponseTaskManager };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ class ServerResponseTaskManager {
6
+ constructor() {
7
+ this.tasks = [];
8
+ this.processedTasks = [];
9
+ this.status = 'idle';
10
+ }
11
+ push(task) {
12
+ if (this.status === 'closed') {
13
+ return;
14
+ }
15
+ this.tasks.push(task);
16
+ if (this.status === 'running') {
17
+ this.processQueue();
18
+ }
19
+ }
20
+ processQueue() {
21
+ if (this.status === 'closed') {
22
+ return;
23
+ }
24
+ this.status = 'running';
25
+ while (this.tasks.length > 0 && this.status === 'running') {
26
+ const task = this.tasks.shift();
27
+ this.processedTasks.push(task());
28
+ }
29
+ }
30
+ closeQueue() {
31
+ this.status = 'closed';
32
+ return Promise.all(this.processedTasks);
33
+ }
34
+ }
35
+
36
+ exports.ServerResponseTaskManager = ServerResponseTaskManager;
@@ -2,7 +2,7 @@ import type { EXECUTION_CONTEXT_MANAGER_TOKEN, LOGGER_TOKEN } from '@tramvai/tok
2
2
  import type { COMMAND_LINE_RUNNER_TOKEN } from '@tramvai/core';
3
3
  import type { SERVER_TOKEN } from '@tramvai/tokens-server';
4
4
  import type { WEB_FASTIFY_APP_TOKEN, WEB_FASTIFY_APP_AFTER_INIT_TOKEN, WEB_FASTIFY_APP_BEFORE_INIT_TOKEN, WEB_FASTIFY_APP_INIT_TOKEN, WEB_FASTIFY_APP_LIMITER_TOKEN, WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN, WEB_FASTIFY_APP_AFTER_ERROR_TOKEN, WEB_FASTIFY_APP_METRICS_TOKEN } from '@tramvai/tokens-server-private';
5
- import type { FETCH_WEBPACK_STATS_TOKEN } from '@tramvai/tokens-render';
5
+ import { type FETCH_WEBPACK_STATS_TOKEN } from '@tramvai/tokens-render';
6
6
  import type { ExtractDependencyType } from '@tinkoff/dippy';
7
7
  export declare const webAppFactory: ({ server }: {
8
8
  server: typeof SERVER_TOKEN;
@@ -3,8 +3,9 @@ import { fastifyCookie } from '@fastify/cookie';
3
3
  import fastifyFormBody from '@fastify/formbody';
4
4
  import { ROOT_EXECUTION_CONTEXT_TOKEN, RESPONSE_MANAGER_TOKEN } from '@tramvai/tokens-common';
5
5
  import { Scope } from '@tramvai/core';
6
- import { FASTIFY_REQUEST, FASTIFY_RESPONSE } from '@tramvai/tokens-server-private';
7
- import { provide } from '@tinkoff/dippy';
6
+ import { FASTIFY_REQUEST, FASTIFY_RESPONSE, SERVER_RESPONSE_STREAM, SERVER_RESPONSE_TASK_MANAGER } from '@tramvai/tokens-server-private';
7
+ import { REACT_SERVER_RENDER_MODE } from '@tramvai/tokens-render';
8
+ import { provide, optional } from '@tinkoff/dippy';
8
9
  import { errorHandler } from './error.es.js';
9
10
 
10
11
  const webAppFactory = ({ server }) => {
@@ -65,7 +66,11 @@ const webAppInitCommand = ({ app, logger, commandLineRunner, executionContextMan
65
66
  useValue: reply,
66
67
  },
67
68
  ]);
69
+ const serverResponseStream = di.get(SERVER_RESPONSE_STREAM);
70
+ const serverResponseTaskManager = di.get(SERVER_RESPONSE_TASK_MANAGER);
68
71
  const responseManager = di.get(RESPONSE_MANAGER_TOKEN);
72
+ // @todo incorrect type inference for optional provider
73
+ const renderMode = di.get(optional(REACT_SERVER_RENDER_MODE));
69
74
  if (reply.sent) {
70
75
  log.debug({
71
76
  event: 'response-ended',
@@ -77,8 +82,28 @@ const webAppInitCommand = ({ app, logger, commandLineRunner, executionContextMan
77
82
  reply
78
83
  .header('content-type', 'text/html')
79
84
  .headers(responseManager.getHeaders())
80
- .status(responseManager.getStatus())
81
- .send(responseManager.getBody());
85
+ .status(responseManager.getStatus());
86
+ if (renderMode === 'streaming') {
87
+ const appHtmlsAttrs = di.get('tramvai app html attributes');
88
+ // split HTML for sequential React HTML chunks streaming inside .application tag
89
+ const [headAndBodyStart, bodyEnd] = responseManager.getBody().split(`<div ${appHtmlsAttrs}></div>`);
90
+ // https://fastify.dev/docs/latest/Reference/Reply/#streams
91
+ reply.send(serverResponseStream);
92
+ // send head and start of body tags immediately
93
+ serverResponseStream.push(`${headAndBodyStart}<div ${appHtmlsAttrs}>`);
94
+ // run all tasks, chunks for renderToPipeableStream will be sent here
95
+ // in future, it can be done earlier, e.g. as Early Hints module
96
+ serverResponseTaskManager.processQueue();
97
+ // wait all tasks
98
+ await serverResponseTaskManager.closeQueue();
99
+ // writing the end of body
100
+ serverResponseStream.push(`</div>${bodyEnd}`);
101
+ // end response
102
+ serverResponseStream.push(null);
103
+ }
104
+ else {
105
+ reply.send(responseManager.getBody());
106
+ }
82
107
  }
83
108
  });
84
109
  }
@@ -8,6 +8,7 @@ var fastifyFormBody = require('@fastify/formbody');
8
8
  var tokensCommon = require('@tramvai/tokens-common');
9
9
  var core = require('@tramvai/core');
10
10
  var tokensServerPrivate = require('@tramvai/tokens-server-private');
11
+ var tokensRender = require('@tramvai/tokens-render');
11
12
  var dippy = require('@tinkoff/dippy');
12
13
  var error = require('./error.js');
13
14
 
@@ -74,7 +75,11 @@ const webAppInitCommand = ({ app, logger, commandLineRunner, executionContextMan
74
75
  useValue: reply,
75
76
  },
76
77
  ]);
78
+ const serverResponseStream = di.get(tokensServerPrivate.SERVER_RESPONSE_STREAM);
79
+ const serverResponseTaskManager = di.get(tokensServerPrivate.SERVER_RESPONSE_TASK_MANAGER);
77
80
  const responseManager = di.get(tokensCommon.RESPONSE_MANAGER_TOKEN);
81
+ // @todo incorrect type inference for optional provider
82
+ const renderMode = di.get(dippy.optional(tokensRender.REACT_SERVER_RENDER_MODE));
78
83
  if (reply.sent) {
79
84
  log.debug({
80
85
  event: 'response-ended',
@@ -86,8 +91,28 @@ const webAppInitCommand = ({ app, logger, commandLineRunner, executionContextMan
86
91
  reply
87
92
  .header('content-type', 'text/html')
88
93
  .headers(responseManager.getHeaders())
89
- .status(responseManager.getStatus())
90
- .send(responseManager.getBody());
94
+ .status(responseManager.getStatus());
95
+ if (renderMode === 'streaming') {
96
+ const appHtmlsAttrs = di.get('tramvai app html attributes');
97
+ // split HTML for sequential React HTML chunks streaming inside .application tag
98
+ const [headAndBodyStart, bodyEnd] = responseManager.getBody().split(`<div ${appHtmlsAttrs}></div>`);
99
+ // https://fastify.dev/docs/latest/Reference/Reply/#streams
100
+ reply.send(serverResponseStream);
101
+ // send head and start of body tags immediately
102
+ serverResponseStream.push(`${headAndBodyStart}<div ${appHtmlsAttrs}>`);
103
+ // run all tasks, chunks for renderToPipeableStream will be sent here
104
+ // in future, it can be done earlier, e.g. as Early Hints module
105
+ serverResponseTaskManager.processQueue();
106
+ // wait all tasks
107
+ await serverResponseTaskManager.closeQueue();
108
+ // writing the end of body
109
+ serverResponseStream.push(`</div>${bodyEnd}`);
110
+ // end response
111
+ serverResponseStream.push(null);
112
+ }
113
+ else {
114
+ reply.send(responseManager.getBody());
115
+ }
91
116
  }
92
117
  });
93
118
  }
package/lib/server.es.js CHANGED
@@ -5,11 +5,12 @@ import { Module, provide, Scope, commandLineListTokens, COMMAND_LINE_RUNNER_TOKE
5
5
  import { SERVER_TOKEN } from '@tramvai/tokens-server';
6
6
  export * from '@tramvai/tokens-server';
7
7
  import { FETCH_WEBPACK_STATS_TOKEN } from '@tramvai/tokens-render';
8
- import { SERVER_FACTORY_TOKEN, WEB_FASTIFY_APP_FACTORY_TOKEN, WEB_FASTIFY_APP_TOKEN, WEB_FASTIFY_APP_BEFORE_INIT_TOKEN, WEB_FASTIFY_APP_INIT_TOKEN, WEB_FASTIFY_APP_AFTER_INIT_TOKEN, WEB_FASTIFY_APP_METRICS_TOKEN, WEB_FASTIFY_APP_LIMITER_TOKEN, WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN, WEB_FASTIFY_APP_AFTER_ERROR_TOKEN } from '@tramvai/tokens-server-private';
8
+ import { SERVER_FACTORY_TOKEN, WEB_FASTIFY_APP_FACTORY_TOKEN, WEB_FASTIFY_APP_TOKEN, WEB_FASTIFY_APP_BEFORE_INIT_TOKEN, WEB_FASTIFY_APP_INIT_TOKEN, WEB_FASTIFY_APP_AFTER_INIT_TOKEN, WEB_FASTIFY_APP_METRICS_TOKEN, WEB_FASTIFY_APP_LIMITER_TOKEN, WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN, WEB_FASTIFY_APP_AFTER_ERROR_TOKEN, SERVER_RESPONSE_STREAM, SERVER_RESPONSE_TASK_MANAGER } from '@tramvai/tokens-server-private';
9
9
  import { LOGGER_TOKEN, EXECUTION_CONTEXT_MANAGER_TOKEN, ENV_MANAGER_TOKEN, ENV_USED_TOKEN } from '@tramvai/tokens-common';
10
10
  import { MetricsModule } from '@tramvai/module-metrics';
11
11
  import { CacheWarmupModule } from '@tramvai/module-cache-warmup';
12
12
  import { ROOT_ERROR_BOUNDARY_COMPONENT_TOKEN } from '@tramvai/react';
13
+ import { PassThrough } from 'stream';
13
14
  import { serverFactory, serverListenCommand } from './server/server.es.js';
14
15
  import { webAppFactory, webAppInitCommand } from './server/webApp.es.js';
15
16
  import { staticAppCommand } from './server/static.es.js';
@@ -25,6 +26,7 @@ import { UtilityServerModule } from './modules/utilityServer.es.js';
25
26
  import { KeepAliveModule } from './modules/keepAlive.es.js';
26
27
  import { ServerTimingModule } from './modules/serverTiming.es.js';
27
28
  import { EarlyHintsModule } from './modules/earlyHints/index.es.js';
29
+ import { ServerResponseTaskManager } from './server/taskManager.es.js';
28
30
 
29
31
  if (typeof setDefaultResultOrder === 'function') {
30
32
  setDefaultResultOrder('ipv4first');
@@ -165,6 +167,16 @@ ServerModule = __decorate([
165
167
  appInfo: APP_INFO_TOKEN,
166
168
  },
167
169
  },
170
+ provide({
171
+ provide: SERVER_RESPONSE_STREAM,
172
+ scope: Scope.REQUEST,
173
+ useFactory: () => new PassThrough(),
174
+ }),
175
+ provide({
176
+ provide: SERVER_RESPONSE_TASK_MANAGER,
177
+ scope: Scope.REQUEST,
178
+ useClass: ServerResponseTaskManager,
179
+ }),
168
180
  ],
169
181
  })
170
182
  ], ServerModule);
package/lib/server.js CHANGED
@@ -13,6 +13,7 @@ var tokensCommon = require('@tramvai/tokens-common');
13
13
  var moduleMetrics = require('@tramvai/module-metrics');
14
14
  var moduleCacheWarmup = require('@tramvai/module-cache-warmup');
15
15
  var react = require('@tramvai/react');
16
+ var stream = require('stream');
16
17
  var server = require('./server/server.js');
17
18
  var webApp = require('./server/webApp.js');
18
19
  var _static = require('./server/static.js');
@@ -28,6 +29,7 @@ var utilityServer = require('./modules/utilityServer.js');
28
29
  var keepAlive = require('./modules/keepAlive.js');
29
30
  var serverTiming = require('./modules/serverTiming.js');
30
31
  var index = require('./modules/earlyHints/index.js');
32
+ var taskManager = require('./server/taskManager.js');
31
33
 
32
34
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
33
35
 
@@ -172,6 +174,16 @@ exports.ServerModule = tslib.__decorate([
172
174
  appInfo: core.APP_INFO_TOKEN,
173
175
  },
174
176
  },
177
+ core.provide({
178
+ provide: tokensServerPrivate.SERVER_RESPONSE_STREAM,
179
+ scope: core.Scope.REQUEST,
180
+ useFactory: () => new stream.PassThrough(),
181
+ }),
182
+ core.provide({
183
+ provide: tokensServerPrivate.SERVER_RESPONSE_TASK_MANAGER,
184
+ scope: core.Scope.REQUEST,
185
+ useClass: taskManager.ServerResponseTaskManager,
186
+ }),
175
187
  ],
176
188
  })
177
189
  ], exports.ServerModule);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-server",
3
- "version": "2.134.0",
3
+ "version": "2.137.0",
4
4
  "description": "",
5
5
  "browser": "lib/browser.js",
6
6
  "main": "lib/server.js",
@@ -25,11 +25,11 @@
25
25
  "@tinkoff/monkeypatch": "2.0.5",
26
26
  "@tinkoff/terminus": "0.1.9",
27
27
  "@tinkoff/url": "0.8.6",
28
- "@tramvai/module-cache-warmup": "2.134.0",
29
- "@tramvai/module-metrics": "2.134.0",
30
- "@tramvai/papi": "2.134.0",
31
- "@tramvai/tokens-server": "2.134.0",
32
- "@tramvai/tokens-server-private": "2.134.0",
28
+ "@tramvai/module-cache-warmup": "2.137.0",
29
+ "@tramvai/module-metrics": "2.137.0",
30
+ "@tramvai/papi": "2.137.0",
31
+ "@tramvai/tokens-server": "2.137.0",
32
+ "@tramvai/tokens-server-private": "2.137.0",
33
33
  "@tramvai/safe-strings": "0.5.11",
34
34
  "fastify": "^4.14.1",
35
35
  "@fastify/cookie": "^8.3.0",
@@ -42,14 +42,14 @@
42
42
  "peerDependencies": {
43
43
  "@tinkoff/dippy": "0.8.15",
44
44
  "@tinkoff/utils": "^2.1.2",
45
- "@tramvai/cli": "2.134.0",
46
- "@tramvai/core": "2.134.0",
47
- "@tramvai/react": "2.134.0",
48
- "@tramvai/module-common": "2.134.0",
49
- "@tramvai/module-environment": "2.134.0",
50
- "@tramvai/tokens-common": "2.134.0",
51
- "@tramvai/tokens-core-private": "2.134.0",
52
- "@tramvai/tokens-render": "2.134.0",
45
+ "@tramvai/cli": "2.137.0",
46
+ "@tramvai/core": "2.137.0",
47
+ "@tramvai/react": "2.137.0",
48
+ "@tramvai/module-common": "2.137.0",
49
+ "@tramvai/module-environment": "2.137.0",
50
+ "@tramvai/tokens-common": "2.137.0",
51
+ "@tramvai/tokens-core-private": "2.137.0",
52
+ "@tramvai/tokens-render": "2.137.0",
53
53
  "react": ">=16.14.0",
54
54
  "react-dom": ">=16.14.0",
55
55
  "tslib": "^2.4.0"