@foxford/den 3.6.0 → 4.0.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 (42) hide show
  1. package/README.mdx +31 -6
  2. package/adapter.d.cts +1 -1
  3. package/adapter.d.ts +1 -1
  4. package/bin/den.cjs +3 -3
  5. package/bin/den.js +1 -1
  6. package/builder/index.cjs +2 -2
  7. package/builder/index.d.cts +4 -2
  8. package/builder/index.d.ts +4 -2
  9. package/builder/index.js +1 -1
  10. package/chunk-2P5TAWRW.cjs +227 -0
  11. package/chunk-DUU4MQ4B.js +227 -0
  12. package/{chunk-2GTQK2PZ.js → chunk-FMRORLLE.js} +6 -4
  13. package/{chunk-P4J4M2WZ.cjs → chunk-OC4LQ2CK.cjs} +6 -4
  14. package/{define-repository-AO7JI6Cb.d.ts → define-repository-Ck45La_2.d.ts} +1 -1
  15. package/{define-repository-C0MI-J32.d.cts → define-repository-DAyeXF48.d.cts} +1 -1
  16. package/{define-slot-CdWdFM16.d.ts → define-slot-BTlLQ__T.d.ts} +1 -1
  17. package/{define-slot-BEeHNEph.d.cts → define-slot-C6hrdJS3.d.cts} +1 -1
  18. package/define.d.cts +3 -3
  19. package/define.d.ts +3 -3
  20. package/{descriptor-BSaHM5B4.d.ts → descriptor-CNJFrEbS.d.ts} +2 -2
  21. package/{descriptor-C2j6Zz6c.d.cts → descriptor-DmfJP4jQ.d.cts} +2 -2
  22. package/index.d.cts +6 -6
  23. package/index.d.ts +6 -6
  24. package/island/index.cjs +23 -1
  25. package/island/index.d.cts +17 -7
  26. package/island/index.d.ts +17 -7
  27. package/island/index.js +22 -0
  28. package/ops/index.cjs +17 -0
  29. package/ops/index.d.cts +39 -0
  30. package/ops/index.d.ts +39 -0
  31. package/ops/index.js +17 -0
  32. package/package.json +25 -16
  33. package/{routes-BvV405k4.d.cts → routes-DQ4ud4bu.d.cts} +1 -1
  34. package/{routes-Di3K_0uQ.d.ts → routes-TkIxQ0Uc.d.ts} +1 -1
  35. package/serve/index.cjs +70 -16
  36. package/serve/index.d.cts +10 -5
  37. package/serve/index.d.ts +10 -5
  38. package/serve/index.js +71 -17
  39. package/{types-BxMQ-VOd.d.cts → types-Ccd5bmt1.d.cts} +23 -4
  40. package/{types-BxMQ-VOd.d.ts → types-Ccd5bmt1.d.ts} +23 -4
  41. package/{view-adapter-DMLlp3Iu.d.cts → view-adapter-CrHh7CjB.d.cts} +2 -2
  42. package/{view-adapter-DNLRSFka.d.ts → view-adapter-DPDvv09J.d.ts} +2 -2
package/serve/index.cjs CHANGED
@@ -14,6 +14,13 @@ require('../chunk-6QV4L6R2.cjs');
14
14
  var _chunkA6ZPAM6Zcjs = require('../chunk-A6ZPAM6Z.cjs');
15
15
 
16
16
 
17
+
18
+
19
+
20
+ var _chunk2P5TAWRWcjs = require('../chunk-2P5TAWRW.cjs');
21
+
22
+
23
+
17
24
  var _chunkT5QVCXVBcjs = require('../chunk-T5QVCXVB.cjs');
18
25
 
19
26
  // src/serve/index.ts
@@ -79,7 +86,7 @@ function matchClaim(app, pathname) {
79
86
  }
80
87
  return (_b = (_a = _chunkQTWJTYZWcjs.matchRoute.call(void 0, app.routes, pathname)) == null ? void 0 : _a.params) != null ? _b : null;
81
88
  }
82
- function browserContext(req) {
89
+ function requestContextOf(req, fromBody) {
83
90
  const headers = {};
84
91
  const forwarded = {
85
92
  "accept-language": req.headers["x-forwarded-accept-language"],
@@ -91,22 +98,32 @@ function browserContext(req) {
91
98
  }
92
99
  }
93
100
  const cookie = req.headers.cookie;
94
- if (typeof cookie === "string" && cookie !== "") {
95
- headers.cookie = cookie;
101
+ const backendHeaders = _chunkT5QVCXVBcjs.__spreadValues.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, typeof cookie === "string" && cookie !== "" ? { cookie } : {}), fromBody);
102
+ return { backendHeaders, cookies: _chunkQTWJTYZWcjs.parseCookieHeader.call(void 0, cookie), headers };
103
+ }
104
+ function backendHeadersOf(value) {
105
+ if (value === void 0) {
106
+ return void 0;
107
+ }
108
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
109
+ return "invalid";
96
110
  }
97
- return { cookies: _chunkQTWJTYZWcjs.parseCookieHeader.call(void 0, cookie), headers };
111
+ const entries = Object.entries(value);
112
+ if (entries.some(([, single]) => typeof single !== "string")) {
113
+ return "invalid";
114
+ }
115
+ return Object.fromEntries(entries.map(([name, single]) => [name.toLowerCase(), single]));
98
116
  }
99
117
  var appPlugin = (instance, options) => {
100
118
  var _a, _b;
101
119
  const { app, slowRenderMs = DEFAULT_SLOW_RENDER_MS } = options;
102
120
  const appLog = (_a = _logger.log.getLogger(app.name)) != null ? _a : _chunkA6ZPAM6Zcjs.logger;
103
121
  const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
104
- instance.get("/_health", () => "ok");
105
122
  instance.get("/_routes", (_req, reply) => {
106
123
  void reply.header("cache-control", "no-store");
107
124
  return claim(app);
108
125
  });
109
- function handleRender(req, reply, target, inputs) {
126
+ function handleRender(req, reply, target, inputs, rawBackendHeaders) {
110
127
  return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
111
128
  if (typeof target !== "string" || !target.startsWith("/")) {
112
129
  return reply.code(400).send({ error: "\u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u0438\u043B\u0438 \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 url" });
@@ -114,6 +131,10 @@ var appPlugin = (instance, options) => {
114
131
  if (inputs !== void 0 && (typeof inputs !== "object" || inputs === null || Array.isArray(inputs))) {
115
132
  return reply.code(400).send({ error: "inputs \u0434\u043E\u043B\u0436\u043D\u044B \u0431\u044B\u0442\u044C \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \u043F\u043E \u043A\u043B\u044E\u0447\u0443 token.description" });
116
133
  }
134
+ const backendHeaders = backendHeadersOf(rawBackendHeaders);
135
+ if (backendHeaders === "invalid") {
136
+ return reply.code(400).send({ error: "backendHeaders \u0434\u043E\u043B\u0436\u043D\u044B \u0431\u044B\u0442\u044C \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \xAB\u0438\u043C\u044F \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0430 \u2192 \u0441\u0442\u0440\u043E\u043A\u0430\xBB" });
137
+ }
117
138
  const { pathname, search } = new URL(target, "http://localhost");
118
139
  const params = matchClaim(app, pathname);
119
140
  if (params === null) {
@@ -123,7 +144,7 @@ var appPlugin = (instance, options) => {
123
144
  const startedAt = Date.now();
124
145
  try {
125
146
  const result = yield _chunkQTWJTYZWcjs.renderApp.call(void 0, app, {
126
- context: browserContext(req),
147
+ context: requestContextOf(req, backendHeaders),
127
148
  inputs,
128
149
  params,
129
150
  pathname,
@@ -140,12 +161,15 @@ var appPlugin = (instance, options) => {
140
161
  }
141
162
  });
142
163
  }
143
- instance.get("/_render", (req, reply) => handleRender(req, reply, req.query.url, void 0));
164
+ instance.get(
165
+ "/_render",
166
+ (req, reply) => handleRender(req, reply, req.query.url, void 0, void 0)
167
+ );
144
168
  instance.route({
145
169
  handler: (req, reply) => {
146
170
  var _a2;
147
171
  const body = (_a2 = req.body) != null ? _a2 : {};
148
- return handleRender(req, reply, body.url, body.inputs);
172
+ return handleRender(req, reply, body.url, body.inputs, body.backendHeaders);
149
173
  },
150
174
  method: "QUERY",
151
175
  url: "/_render"
@@ -157,10 +181,35 @@ var appPlugin = (instance, options) => {
157
181
  var SHUTDOWN_SIGNALS = ["SIGTERM", "SIGINT"];
158
182
  function serveApp(_0) {
159
183
  return _chunkT5QVCXVBcjs.__async.call(void 0, this, arguments, function* (app, options = {}) {
160
- var _a, _b, _c, _d;
184
+ var _a, _b, _c, _d, _e;
161
185
  const appLog = (_a = _logger.log.getLogger(app.name)) != null ? _a : _chunkA6ZPAM6Zcjs.logger;
162
186
  const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
163
187
  const instance = _fastify2.default.call(void 0, { logger: false });
188
+ const metrics = _chunk2P5TAWRWcjs.getProcessMetrics.call(void 0, );
189
+ metrics.startRuntime(app.name);
190
+ let ops;
191
+ const shutdown = () => {
192
+ void instance.close();
193
+ };
194
+ instance.addHook("onRequest", (req, reply, done) => {
195
+ if (_chunk2P5TAWRWcjs.isOpsPath.call(void 0, req.url)) {
196
+ void reply.code(404).send();
197
+ return;
198
+ }
199
+ metrics.trackHttp(req.raw, reply.raw, () => {
200
+ var _a2;
201
+ return (_a2 = req.routeOptions.url) != null ? _a2 : "unmatched";
202
+ });
203
+ done();
204
+ });
205
+ instance.addHook("onClose", () => _chunkT5QVCXVBcjs.__async.call(void 0, null, null, function* () {
206
+ for (const signal of SHUTDOWN_SIGNALS) {
207
+ process.removeListener(signal, shutdown);
208
+ }
209
+ if (ops) {
210
+ yield _chunk2P5TAWRWcjs.closeHttpServer.call(void 0, ops);
211
+ }
212
+ }));
164
213
  yield instance.register(appPlugin, { app, slowRenderMs: options.slowRenderMs });
165
214
  if (options.assets) {
166
215
  registerAssets(instance, options.assets);
@@ -174,14 +223,19 @@ function serveApp(_0) {
174
223
  serveLog.info(`\u2190 ${String(reply.statusCode)} ${req.method} ${req.url} ${elapsed}\u043C\u0441`);
175
224
  done();
176
225
  });
177
- yield (_c = options.configure) == null ? void 0 : _c.call(options, instance);
178
- yield instance.listen({ host: options.host, port: (_d = options.port) != null ? _d : 0 });
226
+ try {
227
+ yield (_c = options.configure) == null ? void 0 : _c.call(options, instance);
228
+ if (options.ops !== false) {
229
+ ops = yield _chunk2P5TAWRWcjs.startOpsServer.call(void 0, options.ops);
230
+ }
231
+ yield instance.listen({ host: (_d = options.host) != null ? _d : "0.0.0.0", port: (_e = options.port) != null ? _e : 8080 });
232
+ } catch (error) {
233
+ yield instance.close();
234
+ throw error;
235
+ }
179
236
  if (options.gracefulShutdown !== false) {
180
237
  for (const signal of SHUTDOWN_SIGNALS) {
181
- process.once(signal, () => {
182
- serveLog.info(`${signal}: \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u0438\u043D\u0441\u0442\u0430\u043D\u0441`);
183
- void instance.close();
184
- });
238
+ process.once(signal, shutdown);
185
239
  }
186
240
  }
187
241
  serveLog.info(
package/serve/index.d.cts CHANGED
@@ -1,9 +1,12 @@
1
1
  import { FastifyInstance, FastifyPluginAsync } from 'fastify';
2
- import { I as IslandDescriptor } from '../descriptor-C2j6Zz6c.cjs';
3
- import { A as AppRoute } from '../routes-BvV405k4.cjs';
4
- import '../define-slot-BEeHNEph.cjs';
5
- import '../types-BxMQ-VOd.cjs';
2
+ import { I as IslandDescriptor } from '../descriptor-DmfJP4jQ.cjs';
3
+ import { A as AppRoute } from '../routes-DQ4ud4bu.cjs';
4
+ import { OpsOptions } from '../ops/index.cjs';
5
+ import '../define-slot-C6hrdJS3.cjs';
6
+ import '../types-Ccd5bmt1.cjs';
6
7
  import '@foxford/ioc';
8
+ import '@prometheus-io/client';
9
+ import 'node:http';
7
10
 
8
11
  /** Что раздаётся и по какому префиксу. */
9
12
  interface AppAssetsSource {
@@ -31,7 +34,7 @@ interface AppPluginOptions {
31
34
  slowRenderMs?: number;
32
35
  }
33
36
  /**
34
- * Регистрирует ручки приложения на инстансе fastify: `/_render`, `/_routes`, `/_health`.
37
+ * Регистрирует ручки приложения на инстансе fastify: `/_render`, `/_routes`.
35
38
  *
36
39
  * Плагином, поэтому монтируется и в свой сервер, и в чужой — под префиксом, рядом с ручками
37
40
  * потребителя, вместе с его плагинами.
@@ -54,6 +57,8 @@ declare function loadAppEnv(root: string): void;
54
57
  interface ServeAppOptions {
55
58
  /** Порт; `0` — свободный, фактический смотри в `server.addresses()`. */
56
59
  port?: number;
60
+ /** Второй HTTP-сервер для probes и метрик; false — подключается потребителем. */
61
+ ops?: OpsOptions | false;
57
62
  /** Интерфейс прослушивания. */
58
63
  host?: string;
59
64
  /** Порог, выше которого рендер считается медленным и уходит в `warn`, мс. */
package/serve/index.d.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  import { FastifyInstance, FastifyPluginAsync } from 'fastify';
2
- import { I as IslandDescriptor } from '../descriptor-BSaHM5B4.js';
3
- import { A as AppRoute } from '../routes-Di3K_0uQ.js';
4
- import '../define-slot-CdWdFM16.js';
5
- import '../types-BxMQ-VOd.js';
2
+ import { I as IslandDescriptor } from '../descriptor-CNJFrEbS.js';
3
+ import { A as AppRoute } from '../routes-TkIxQ0Uc.js';
4
+ import { OpsOptions } from '../ops/index.js';
5
+ import '../define-slot-BTlLQ__T.js';
6
+ import '../types-Ccd5bmt1.js';
6
7
  import '@foxford/ioc';
8
+ import '@prometheus-io/client';
9
+ import 'node:http';
7
10
 
8
11
  /** Что раздаётся и по какому префиксу. */
9
12
  interface AppAssetsSource {
@@ -31,7 +34,7 @@ interface AppPluginOptions {
31
34
  slowRenderMs?: number;
32
35
  }
33
36
  /**
34
- * Регистрирует ручки приложения на инстансе fastify: `/_render`, `/_routes`, `/_health`.
37
+ * Регистрирует ручки приложения на инстансе fastify: `/_render`, `/_routes`.
35
38
  *
36
39
  * Плагином, поэтому монтируется и в свой сервер, и в чужой — под префиксом, рядом с ручками
37
40
  * потребителя, вместе с его плагинами.
@@ -54,6 +57,8 @@ declare function loadAppEnv(root: string): void;
54
57
  interface ServeAppOptions {
55
58
  /** Порт; `0` — свободный, фактический смотри в `server.addresses()`. */
56
59
  port?: number;
60
+ /** Второй HTTP-сервер для probes и метрик; false — подключается потребителем. */
61
+ ops?: OpsOptions | false;
57
62
  /** Интерфейс прослушивания. */
58
63
  host?: string;
59
64
  /** Порог, выше которого рендер считается медленным и уходит в `warn`, мс. */
package/serve/index.js CHANGED
@@ -13,7 +13,14 @@ import {
13
13
  logger
14
14
  } from "../chunk-TPBI6TOU.js";
15
15
  import {
16
- __async
16
+ closeHttpServer,
17
+ getProcessMetrics,
18
+ isOpsPath,
19
+ startOpsServer
20
+ } from "../chunk-DUU4MQ4B.js";
21
+ import {
22
+ __async,
23
+ __spreadValues
17
24
  } from "../chunk-6FNC3XMI.js";
18
25
 
19
26
  // src/serve/index.ts
@@ -79,7 +86,7 @@ function matchClaim(app, pathname) {
79
86
  }
80
87
  return (_b = (_a = matchRoute(app.routes, pathname)) == null ? void 0 : _a.params) != null ? _b : null;
81
88
  }
82
- function browserContext(req) {
89
+ function requestContextOf(req, fromBody) {
83
90
  const headers = {};
84
91
  const forwarded = {
85
92
  "accept-language": req.headers["x-forwarded-accept-language"],
@@ -91,22 +98,32 @@ function browserContext(req) {
91
98
  }
92
99
  }
93
100
  const cookie = req.headers.cookie;
94
- if (typeof cookie === "string" && cookie !== "") {
95
- headers.cookie = cookie;
101
+ const backendHeaders = __spreadValues(__spreadValues({}, typeof cookie === "string" && cookie !== "" ? { cookie } : {}), fromBody);
102
+ return { backendHeaders, cookies: parseCookieHeader(cookie), headers };
103
+ }
104
+ function backendHeadersOf(value) {
105
+ if (value === void 0) {
106
+ return void 0;
107
+ }
108
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
109
+ return "invalid";
110
+ }
111
+ const entries = Object.entries(value);
112
+ if (entries.some(([, single]) => typeof single !== "string")) {
113
+ return "invalid";
96
114
  }
97
- return { cookies: parseCookieHeader(cookie), headers };
115
+ return Object.fromEntries(entries.map(([name, single]) => [name.toLowerCase(), single]));
98
116
  }
99
117
  var appPlugin = (instance, options) => {
100
118
  var _a, _b;
101
119
  const { app, slowRenderMs = DEFAULT_SLOW_RENDER_MS } = options;
102
120
  const appLog = (_a = log.getLogger(app.name)) != null ? _a : logger;
103
121
  const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
104
- instance.get("/_health", () => "ok");
105
122
  instance.get("/_routes", (_req, reply) => {
106
123
  void reply.header("cache-control", "no-store");
107
124
  return claim(app);
108
125
  });
109
- function handleRender(req, reply, target, inputs) {
126
+ function handleRender(req, reply, target, inputs, rawBackendHeaders) {
110
127
  return __async(this, null, function* () {
111
128
  if (typeof target !== "string" || !target.startsWith("/")) {
112
129
  return reply.code(400).send({ error: "\u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u0438\u043B\u0438 \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 url" });
@@ -114,6 +131,10 @@ var appPlugin = (instance, options) => {
114
131
  if (inputs !== void 0 && (typeof inputs !== "object" || inputs === null || Array.isArray(inputs))) {
115
132
  return reply.code(400).send({ error: "inputs \u0434\u043E\u043B\u0436\u043D\u044B \u0431\u044B\u0442\u044C \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \u043F\u043E \u043A\u043B\u044E\u0447\u0443 token.description" });
116
133
  }
134
+ const backendHeaders = backendHeadersOf(rawBackendHeaders);
135
+ if (backendHeaders === "invalid") {
136
+ return reply.code(400).send({ error: "backendHeaders \u0434\u043E\u043B\u0436\u043D\u044B \u0431\u044B\u0442\u044C \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \xAB\u0438\u043C\u044F \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0430 \u2192 \u0441\u0442\u0440\u043E\u043A\u0430\xBB" });
137
+ }
117
138
  const { pathname, search } = new URL(target, "http://localhost");
118
139
  const params = matchClaim(app, pathname);
119
140
  if (params === null) {
@@ -123,7 +144,7 @@ var appPlugin = (instance, options) => {
123
144
  const startedAt = Date.now();
124
145
  try {
125
146
  const result = yield renderApp(app, {
126
- context: browserContext(req),
147
+ context: requestContextOf(req, backendHeaders),
127
148
  inputs,
128
149
  params,
129
150
  pathname,
@@ -140,12 +161,15 @@ var appPlugin = (instance, options) => {
140
161
  }
141
162
  });
142
163
  }
143
- instance.get("/_render", (req, reply) => handleRender(req, reply, req.query.url, void 0));
164
+ instance.get(
165
+ "/_render",
166
+ (req, reply) => handleRender(req, reply, req.query.url, void 0, void 0)
167
+ );
144
168
  instance.route({
145
169
  handler: (req, reply) => {
146
170
  var _a2;
147
171
  const body = (_a2 = req.body) != null ? _a2 : {};
148
- return handleRender(req, reply, body.url, body.inputs);
172
+ return handleRender(req, reply, body.url, body.inputs, body.backendHeaders);
149
173
  },
150
174
  method: "QUERY",
151
175
  url: "/_render"
@@ -157,10 +181,35 @@ var appPlugin = (instance, options) => {
157
181
  var SHUTDOWN_SIGNALS = ["SIGTERM", "SIGINT"];
158
182
  function serveApp(_0) {
159
183
  return __async(this, arguments, function* (app, options = {}) {
160
- var _a, _b, _c, _d;
184
+ var _a, _b, _c, _d, _e;
161
185
  const appLog = (_a = log2.getLogger(app.name)) != null ? _a : logger;
162
186
  const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
163
187
  const instance = Fastify({ logger: false });
188
+ const metrics = getProcessMetrics();
189
+ metrics.startRuntime(app.name);
190
+ let ops;
191
+ const shutdown = () => {
192
+ void instance.close();
193
+ };
194
+ instance.addHook("onRequest", (req, reply, done) => {
195
+ if (isOpsPath(req.url)) {
196
+ void reply.code(404).send();
197
+ return;
198
+ }
199
+ metrics.trackHttp(req.raw, reply.raw, () => {
200
+ var _a2;
201
+ return (_a2 = req.routeOptions.url) != null ? _a2 : "unmatched";
202
+ });
203
+ done();
204
+ });
205
+ instance.addHook("onClose", () => __async(null, null, function* () {
206
+ for (const signal of SHUTDOWN_SIGNALS) {
207
+ process.removeListener(signal, shutdown);
208
+ }
209
+ if (ops) {
210
+ yield closeHttpServer(ops);
211
+ }
212
+ }));
164
213
  yield instance.register(appPlugin, { app, slowRenderMs: options.slowRenderMs });
165
214
  if (options.assets) {
166
215
  registerAssets(instance, options.assets);
@@ -174,14 +223,19 @@ function serveApp(_0) {
174
223
  serveLog.info(`\u2190 ${String(reply.statusCode)} ${req.method} ${req.url} ${elapsed}\u043C\u0441`);
175
224
  done();
176
225
  });
177
- yield (_c = options.configure) == null ? void 0 : _c.call(options, instance);
178
- yield instance.listen({ host: options.host, port: (_d = options.port) != null ? _d : 0 });
226
+ try {
227
+ yield (_c = options.configure) == null ? void 0 : _c.call(options, instance);
228
+ if (options.ops !== false) {
229
+ ops = yield startOpsServer(options.ops);
230
+ }
231
+ yield instance.listen({ host: (_d = options.host) != null ? _d : "0.0.0.0", port: (_e = options.port) != null ? _e : 8080 });
232
+ } catch (error) {
233
+ yield instance.close();
234
+ throw error;
235
+ }
179
236
  if (options.gracefulShutdown !== false) {
180
237
  for (const signal of SHUTDOWN_SIGNALS) {
181
- process.once(signal, () => {
182
- serveLog.info(`${signal}: \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u0438\u043D\u0441\u0442\u0430\u043D\u0441`);
183
- void instance.close();
184
- });
238
+ process.once(signal, shutdown);
185
239
  }
186
240
  }
187
241
  serveLog.info(
@@ -477,14 +477,33 @@ declare class StateRegistry {
477
477
  }
478
478
 
479
479
  /**
480
- * Контекст HTTP-запроса — заголовки и cookies, переданные сервером.
481
- * На клиенте содержит пустые объекты.
480
+ * Реквизиты HTTP-запроса. Три поля отвечают на ТРИ РАЗНЫХ вопроса, и путать их нельзя:
481
+ * что прислал браузер, чему равна отдельная кука и что предъявлять бэкенду.
482
+ *
483
+ * На клиенте заголовков нет, куки берутся из документа, а реквизиты бэкенда подставляет
484
+ * сам браузер.
482
485
  */
483
486
  interface RequestContext {
484
- /** HTTP-заголовки запроса (в нижнем регистре) */
487
+ /**
488
+ * Что прислал БРАУЗЕР: `host`, `accept-language` (в нижнем регистре).
489
+ *
490
+ * Читается для решений о рендере — собрать абсолютную ссылку, выбрать язык. Заголовка
491
+ * `cookie` здесь НЕТ намеренно: собери кто-нибудь по нему запрос — он отправил бы браузерную
492
+ * куку вместо реквизитов этого запроса, и на первом визите это был бы другой посетитель.
493
+ * Нужно значение куки — есть `cookies`; нужно сходить в бэкенд — есть `backendHeaders`.
494
+ */
485
495
  headers: Record<string, string>;
486
- /** Распарсенные cookies из заголовка Cookie */
496
+ /** Разобранные куки — чтобы прочитать значение конкретной куки. */
487
497
  cookies: Record<string, string>;
498
+ /**
499
+ * Что предъявлять БЭКЕНДУ в каждом запросе: `cookie` и то, что бэкенд выдал хосту на
500
+ * бутстрапе своими заголовками (идентичность посетителя и прочее).
501
+ *
502
+ * Один набор на все походы приложения, и собирает его хост — до рендера и один раз.
503
+ * Навешивает на клиент расширение рантайма `@foxford/den-sdk`, поэтому прикладной код
504
+ * заголовков не касается вовсе.
505
+ */
506
+ backendHeaders?: Record<string, string>;
488
507
  }
489
508
  /**
490
509
  * Результат проверки guard.
@@ -477,14 +477,33 @@ declare class StateRegistry {
477
477
  }
478
478
 
479
479
  /**
480
- * Контекст HTTP-запроса — заголовки и cookies, переданные сервером.
481
- * На клиенте содержит пустые объекты.
480
+ * Реквизиты HTTP-запроса. Три поля отвечают на ТРИ РАЗНЫХ вопроса, и путать их нельзя:
481
+ * что прислал браузер, чему равна отдельная кука и что предъявлять бэкенду.
482
+ *
483
+ * На клиенте заголовков нет, куки берутся из документа, а реквизиты бэкенда подставляет
484
+ * сам браузер.
482
485
  */
483
486
  interface RequestContext {
484
- /** HTTP-заголовки запроса (в нижнем регистре) */
487
+ /**
488
+ * Что прислал БРАУЗЕР: `host`, `accept-language` (в нижнем регистре).
489
+ *
490
+ * Читается для решений о рендере — собрать абсолютную ссылку, выбрать язык. Заголовка
491
+ * `cookie` здесь НЕТ намеренно: собери кто-нибудь по нему запрос — он отправил бы браузерную
492
+ * куку вместо реквизитов этого запроса, и на первом визите это был бы другой посетитель.
493
+ * Нужно значение куки — есть `cookies`; нужно сходить в бэкенд — есть `backendHeaders`.
494
+ */
485
495
  headers: Record<string, string>;
486
- /** Распарсенные cookies из заголовка Cookie */
496
+ /** Разобранные куки — чтобы прочитать значение конкретной куки. */
487
497
  cookies: Record<string, string>;
498
+ /**
499
+ * Что предъявлять БЭКЕНДУ в каждом запросе: `cookie` и то, что бэкенд выдал хосту на
500
+ * бутстрапе своими заголовками (идентичность посетителя и прочее).
501
+ *
502
+ * Один набор на все походы приложения, и собирает его хост — до рендера и один раз.
503
+ * Навешивает на клиент расширение рантайма `@foxford/den-sdk`, поэтому прикладной код
504
+ * заголовков не касается вовсе.
505
+ */
506
+ backendHeaders?: Record<string, string>;
488
507
  }
489
508
  /**
490
509
  * Результат проверки guard.
@@ -1,6 +1,6 @@
1
- import { I as IslandDescriptor } from './descriptor-C2j6Zz6c.cjs';
1
+ import { I as IslandDescriptor } from './descriptor-DmfJP4jQ.cjs';
2
2
  import { S as ServerRenderResult } from './server-render-CocWXQKC.cjs';
3
- import { L as LayerDefinition, h as LayerConfig } from './types-BxMQ-VOd.cjs';
3
+ import { L as LayerDefinition, h as LayerConfig } from './types-Ccd5bmt1.cjs';
4
4
  import { Container, Token } from '@foxford/ioc';
5
5
 
6
6
  /** Что нужно адаптеру, чтобы отрисовать остров на сервере. */
@@ -1,6 +1,6 @@
1
- import { I as IslandDescriptor } from './descriptor-BSaHM5B4.js';
1
+ import { I as IslandDescriptor } from './descriptor-CNJFrEbS.js';
2
2
  import { S as ServerRenderResult } from './server-render-CocWXQKC.js';
3
- import { L as LayerDefinition, h as LayerConfig } from './types-BxMQ-VOd.js';
3
+ import { L as LayerDefinition, h as LayerConfig } from './types-Ccd5bmt1.js';
4
4
  import { Container, Token } from '@foxford/ioc';
5
5
 
6
6
  /** Что нужно адаптеру, чтобы отрисовать остров на сервере. */