@foxford/den 3.7.0 → 4.1.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 (40) hide show
  1. package/README.mdx +58 -7
  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-2GTQK2PZ.js → chunk-7FOG2MJV.js} +9 -6
  11. package/chunk-A2CYL5LY.js +240 -0
  12. package/chunk-HJFBEJT6.cjs +240 -0
  13. package/{chunk-P4J4M2WZ.cjs → chunk-IATA5QZ7.cjs} +9 -6
  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.d.cts +9 -6
  25. package/island/index.d.ts +9 -6
  26. package/ops/index.cjs +17 -0
  27. package/ops/index.d.cts +39 -0
  28. package/ops/index.d.ts +39 -0
  29. package/ops/index.js +17 -0
  30. package/package.json +25 -16
  31. package/{routes-BvV405k4.d.cts → routes-DQ4ud4bu.d.cts} +1 -1
  32. package/{routes-Di3K_0uQ.d.ts → routes-TkIxQ0Uc.d.ts} +1 -1
  33. package/serve/index.cjs +270 -17
  34. package/serve/index.d.cts +21 -6
  35. package/serve/index.d.ts +21 -6
  36. package/serve/index.js +273 -20
  37. package/{types-BxMQ-VOd.d.cts → types-Ccd5bmt1.d.cts} +23 -4
  38. package/{types-BxMQ-VOd.d.ts → types-Ccd5bmt1.d.ts} +23 -4
  39. package/{view-adapter-DMLlp3Iu.d.cts → view-adapter-CrHh7CjB.d.cts} +2 -2
  40. package/{view-adapter-DNLRSFka.d.ts → view-adapter-DPDvv09J.d.ts} +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxford/den",
3
- "version": "3.7.0",
3
+ "version": "4.1.0",
4
4
  "description": "Den — декларативный метафреймворк Foxford (core runtime)",
5
5
  "keywords": [
6
6
  "foxford",
@@ -30,6 +30,7 @@
30
30
  "den": "./den.js"
31
31
  },
32
32
  "dependencies": {
33
+ "@prometheus-io/client": "0.16.1",
33
34
  "fastify": "^5.12.1",
34
35
  "@foxford/ioc": "^1.1.2",
35
36
  "@foxford/logger": "^1.2.2"
@@ -75,6 +76,11 @@
75
76
  "require": "./island/index.cjs",
76
77
  "types": "./island/index.d.ts"
77
78
  },
79
+ "./ops": {
80
+ "import": "./ops/index.js",
81
+ "require": "./ops/index.cjs",
82
+ "types": "./ops/index.d.ts"
83
+ },
78
84
  "./serve": {
79
85
  "import": "./serve/index.js",
80
86
  "require": "./serve/index.cjs",
@@ -89,17 +95,19 @@
89
95
  "adapter.js",
90
96
  "bin",
91
97
  "builder",
92
- "chunk-2GTQK2PZ.js",
93
98
  "chunk-3RX2OSUL.js",
94
99
  "chunk-6FNC3XMI.js",
95
100
  "chunk-6QV4L6R2.cjs",
101
+ "chunk-7FOG2MJV.js",
102
+ "chunk-A2CYL5LY.js",
96
103
  "chunk-A6ZPAM6Z.cjs",
97
104
  "chunk-C7BM4DGX.cjs",
98
105
  "chunk-E23E2VUC.js",
99
106
  "chunk-E56BKBQZ.js",
100
107
  "chunk-H5DKRHHQ.cjs",
108
+ "chunk-HJFBEJT6.cjs",
109
+ "chunk-IATA5QZ7.cjs",
101
110
  "chunk-KQAB6C6Q.cjs",
102
- "chunk-P4J4M2WZ.cjs",
103
111
  "chunk-QTWJTYZW.cjs",
104
112
  "chunk-RG7ZV5BD.js",
105
113
  "chunk-T5QVCXVB.cjs",
@@ -107,33 +115,34 @@
107
115
  "chunk-WBHHHICS.js",
108
116
  "chunk-XOJ2VWX4.cjs",
109
117
  "chunk-XQ7EHXHG.js",
110
- "define-repository-AO7JI6Cb.d.ts",
111
- "define-repository-C0MI-J32.d.cts",
112
- "define-slot-BEeHNEph.d.cts",
113
- "define-slot-CdWdFM16.d.ts",
118
+ "define-repository-Ck45La_2.d.ts",
119
+ "define-repository-DAyeXF48.d.cts",
120
+ "define-slot-BTlLQ__T.d.ts",
121
+ "define-slot-C6hrdJS3.d.cts",
114
122
  "define.cjs",
115
123
  "define.d.cts",
116
124
  "define.d.ts",
117
125
  "define.js",
118
126
  "den.js",
119
- "descriptor-BSaHM5B4.d.ts",
120
- "descriptor-C2j6Zz6c.d.cts",
127
+ "descriptor-CNJFrEbS.d.ts",
128
+ "descriptor-DmfJP4jQ.d.cts",
121
129
  "index.cjs",
122
130
  "index.d.cts",
123
131
  "index.d.ts",
124
132
  "index.js",
125
133
  "island",
134
+ "ops",
126
135
  "package.json",
127
- "routes-BvV405k4.d.cts",
128
- "routes-Di3K_0uQ.d.ts",
136
+ "routes-DQ4ud4bu.d.cts",
137
+ "routes-TkIxQ0Uc.d.ts",
129
138
  "serve",
130
139
  "server-render-CocWXQKC.d.cts",
131
140
  "server-render-CocWXQKC.d.ts",
132
- "types-BxMQ-VOd.d.cts",
133
- "types-BxMQ-VOd.d.ts",
134
- "view-adapter-DMLlp3Iu.d.cts",
135
- "view-adapter-DNLRSFka.d.ts"
141
+ "types-Ccd5bmt1.d.cts",
142
+ "types-Ccd5bmt1.d.ts",
143
+ "view-adapter-CrHh7CjB.d.cts",
144
+ "view-adapter-DPDvv09J.d.ts"
136
145
  ],
137
- "sha": "c642307",
146
+ "sha": "c941b51",
138
147
  "scripts": {}
139
148
  }
@@ -1,4 +1,4 @@
1
- import { G as GuardDefinition } from './types-BxMQ-VOd.cjs';
1
+ import { G as GuardDefinition } from './types-Ccd5bmt1.cjs';
2
2
 
3
3
  /** Маршрут с политикой доступа. */
4
4
  interface RouteDeclaration {
@@ -1,4 +1,4 @@
1
- import { G as GuardDefinition } from './types-BxMQ-VOd.js';
1
+ import { G as GuardDefinition } from './types-Ccd5bmt1.js';
2
2
 
3
3
  /** Маршрут с политикой доступа. */
4
4
  interface RouteDeclaration {
package/serve/index.cjs CHANGED
@@ -14,6 +14,15 @@ require('../chunk-6QV4L6R2.cjs');
14
14
  var _chunkA6ZPAM6Zcjs = require('../chunk-A6ZPAM6Z.cjs');
15
15
 
16
16
 
17
+
18
+
19
+
20
+
21
+ var _chunkHJFBEJT6cjs = require('../chunk-HJFBEJT6.cjs');
22
+
23
+
24
+
25
+
17
26
  var _chunkT5QVCXVBcjs = require('../chunk-T5QVCXVB.cjs');
18
27
 
19
28
  // src/serve/index.ts
@@ -79,7 +88,7 @@ function matchClaim(app, pathname) {
79
88
  }
80
89
  return (_b = (_a = _chunkQTWJTYZWcjs.matchRoute.call(void 0, app.routes, pathname)) == null ? void 0 : _a.params) != null ? _b : null;
81
90
  }
82
- function browserContext(req) {
91
+ function requestContextOf(req, fromBody) {
83
92
  const headers = {};
84
93
  const forwarded = {
85
94
  "accept-language": req.headers["x-forwarded-accept-language"],
@@ -91,22 +100,32 @@ function browserContext(req) {
91
100
  }
92
101
  }
93
102
  const cookie = req.headers.cookie;
94
- if (typeof cookie === "string" && cookie !== "") {
95
- headers.cookie = cookie;
103
+ const backendHeaders = _chunkT5QVCXVBcjs.__spreadValues.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, typeof cookie === "string" && cookie !== "" ? { cookie } : {}), fromBody);
104
+ return { backendHeaders, cookies: _chunkQTWJTYZWcjs.parseCookieHeader.call(void 0, cookie), headers };
105
+ }
106
+ function backendHeadersOf(value) {
107
+ if (value === void 0) {
108
+ return void 0;
109
+ }
110
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
111
+ return "invalid";
112
+ }
113
+ const entries = Object.entries(value);
114
+ if (entries.some(([, single]) => typeof single !== "string")) {
115
+ return "invalid";
96
116
  }
97
- return { cookies: _chunkQTWJTYZWcjs.parseCookieHeader.call(void 0, cookie), headers };
117
+ return Object.fromEntries(entries.map(([name, single]) => [name.toLowerCase(), single]));
98
118
  }
99
119
  var appPlugin = (instance, options) => {
100
120
  var _a, _b;
101
121
  const { app, slowRenderMs = DEFAULT_SLOW_RENDER_MS } = options;
102
122
  const appLog = (_a = _logger.log.getLogger(app.name)) != null ? _a : _chunkA6ZPAM6Zcjs.logger;
103
123
  const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
104
- instance.get("/_health", () => "ok");
105
124
  instance.get("/_routes", (_req, reply) => {
106
125
  void reply.header("cache-control", "no-store");
107
126
  return claim(app);
108
127
  });
109
- function handleRender(req, reply, target, inputs) {
128
+ function handleRender(req, reply, target, inputs, rawBackendHeaders) {
110
129
  return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
111
130
  if (typeof target !== "string" || !target.startsWith("/")) {
112
131
  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 +133,10 @@ var appPlugin = (instance, options) => {
114
133
  if (inputs !== void 0 && (typeof inputs !== "object" || inputs === null || Array.isArray(inputs))) {
115
134
  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
135
  }
136
+ const backendHeaders = backendHeadersOf(rawBackendHeaders);
137
+ if (backendHeaders === "invalid") {
138
+ 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" });
139
+ }
117
140
  const { pathname, search } = new URL(target, "http://localhost");
118
141
  const params = matchClaim(app, pathname);
119
142
  if (params === null) {
@@ -123,7 +146,7 @@ var appPlugin = (instance, options) => {
123
146
  const startedAt = Date.now();
124
147
  try {
125
148
  const result = yield _chunkQTWJTYZWcjs.renderApp.call(void 0, app, {
126
- context: browserContext(req),
149
+ context: requestContextOf(req, backendHeaders),
127
150
  inputs,
128
151
  params,
129
152
  pathname,
@@ -140,12 +163,15 @@ var appPlugin = (instance, options) => {
140
163
  }
141
164
  });
142
165
  }
143
- instance.get("/_render", (req, reply) => handleRender(req, reply, req.query.url, void 0));
166
+ instance.get(
167
+ "/_render",
168
+ (req, reply) => handleRender(req, reply, req.query.url, void 0, void 0)
169
+ );
144
170
  instance.route({
145
171
  handler: (req, reply) => {
146
172
  var _a2;
147
173
  const body = (_a2 = req.body) != null ? _a2 : {};
148
- return handleRender(req, reply, body.url, body.inputs);
174
+ return handleRender(req, reply, body.url, body.inputs, body.backendHeaders);
149
175
  },
150
176
  method: "QUERY",
151
177
  url: "/_render"
@@ -153,14 +179,235 @@ var appPlugin = (instance, options) => {
153
179
  return Promise.resolve();
154
180
  };
155
181
 
182
+ // src/serve/process.ts
183
+ var _cluster = require('cluster'); var _cluster2 = _interopRequireDefault(_cluster);
184
+
185
+
186
+ // src/serve/worker.ts
187
+
188
+ var ALIVE = "den:app-worker-alive";
189
+ var SIGNALS = ["SIGTERM", "SIGINT"];
190
+ var SHUTDOWN_MS = 1e4;
191
+ function startWorker(appName, start) {
192
+ return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
193
+ const aggregation = _chunkHJFBEJT6cjs.clusterMetrics.call(void 0, );
194
+ const logger2 = _logger.log.getLogger(appName).getLogger("cluster");
195
+ let timer;
196
+ let closing;
197
+ const starting = start();
198
+ const close = () => {
199
+ closing != null ? closing : closing = (() => _chunkT5QVCXVBcjs.__async.call(void 0, null, null, function* () {
200
+ clearInterval(timer);
201
+ try {
202
+ const instance = yield starting;
203
+ yield instance.close();
204
+ } finally {
205
+ try {
206
+ if (process.connected) yield aggregation.shutdown();
207
+ } finally {
208
+ for (const signal of SIGNALS) process.removeListener(signal, shutdown);
209
+ process.removeListener("disconnect", shutdown);
210
+ if (process.connected) process.disconnect();
211
+ }
212
+ }
213
+ }))();
214
+ return closing;
215
+ };
216
+ const shutdown = () => {
217
+ const deadline = setTimeout(() => process.exit(1), SHUTDOWN_MS);
218
+ deadline.unref();
219
+ void close().then(() => process.exit(0)).catch((error) => {
220
+ logger2.error("\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0432\u043E\u0440\u043A\u0435\u0440\u0430 \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C:", error);
221
+ process.exit(1);
222
+ });
223
+ };
224
+ for (const signal of SIGNALS) process.on(signal, shutdown);
225
+ process.once("disconnect", shutdown);
226
+ try {
227
+ yield starting;
228
+ if (!closing) {
229
+ const notify = () => {
230
+ var _a;
231
+ if (process.connected) (_a = process.send) == null ? void 0 : _a.call(process, { type: ALIVE });
232
+ };
233
+ timer = setInterval(notify, 2e3);
234
+ timer.unref();
235
+ notify();
236
+ }
237
+ } catch (error) {
238
+ yield close().catch(() => void 0);
239
+ throw error;
240
+ }
241
+ return { close };
242
+ });
243
+ }
244
+
245
+ // src/serve/process.ts
246
+ var STARTUP_MS = 6e4;
247
+ function startAppProcess(appName, options, start) {
248
+ return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
249
+ var _a;
250
+ const workers = (_a = options.workers) != null ? _a : 1;
251
+ if (!Number.isSafeInteger(workers) || workers < 1) {
252
+ throw new Error("WORKERS must be a positive integer");
253
+ }
254
+ if (workers === 1) {
255
+ const instance = yield start(options);
256
+ return { close: () => instance.close() };
257
+ }
258
+ const metrics = _chunkHJFBEJT6cjs.getProcessMetrics.call(void 0, );
259
+ metrics.startRuntime(appName);
260
+ if (_cluster2.default.isWorker) {
261
+ return startWorker(appName, () => start(_chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, options), { gracefulShutdown: false, ops: false })));
262
+ }
263
+ return startPrimary(appName, options, workers);
264
+ });
265
+ }
266
+ function startPrimary(appName, options, workers) {
267
+ return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
268
+ const aggregation = _chunkHJFBEJT6cjs.clusterMetrics.call(void 0, );
269
+ const logger2 = _logger.log.getLogger(appName).getLogger("cluster");
270
+ const children = /* @__PURE__ */ new Set();
271
+ const alive = /* @__PURE__ */ new Map();
272
+ let ops;
273
+ let closing;
274
+ let started = false;
275
+ let resolveReady = () => void 0;
276
+ let rejectReady = () => void 0;
277
+ const ready = new Promise((resolve, reject) => {
278
+ resolveReady = resolve;
279
+ rejectReady = reject;
280
+ });
281
+ void ready.catch(() => void 0);
282
+ const timeout = setTimeout(() => rejectReady(new Error("Den workers startup timeout")), STARTUP_MS);
283
+ const onMessage = (worker, message) => {
284
+ if (children.has(worker) && typeof message === "object" && message !== null && "type" in message && message.type === ALIVE) {
285
+ alive.set(worker.id, Date.now());
286
+ if (alive.size === workers) resolveReady();
287
+ }
288
+ };
289
+ const close = () => {
290
+ closing != null ? closing : closing = (() => _chunkT5QVCXVBcjs.__async.call(void 0, null, null, function* () {
291
+ clearTimeout(timeout);
292
+ resolveReady();
293
+ _cluster2.default.removeListener("message", onMessage);
294
+ yield Promise.all([...children].map(stopWorker));
295
+ if (ops) yield _chunkHJFBEJT6cjs.closeHttpServer.call(void 0, ops);
296
+ yield aggregation.shutdown();
297
+ for (const signal of SIGNALS) process.removeListener(signal, shutdown);
298
+ }))();
299
+ return closing;
300
+ };
301
+ const shutdown = () => {
302
+ void close().then(() => process.exit()).catch((error) => {
303
+ logger2.error("\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043A\u043B\u0430\u0441\u0442\u0435\u0440\u0430 \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C:", error);
304
+ process.exit(1);
305
+ });
306
+ };
307
+ const failed = (error) => {
308
+ if (closing) return;
309
+ if (!started) {
310
+ rejectReady(error);
311
+ } else {
312
+ logger2.error(error);
313
+ process.exitCode = 1;
314
+ shutdown();
315
+ }
316
+ };
317
+ _cluster2.default.on("message", onMessage);
318
+ if (options.gracefulShutdown !== false) {
319
+ for (const signal of SIGNALS) process.on(signal, shutdown);
320
+ }
321
+ try {
322
+ if (options.ops !== false) {
323
+ const supplied = options.ops;
324
+ ops = yield _chunkHJFBEJT6cjs.startOpsServer.call(void 0, _chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, supplied), {
325
+ collect: aggregation.collect,
326
+ contentType: aggregation.contentType,
327
+ isLive: () => {
328
+ var _a;
329
+ return [...children].some((worker) => {
330
+ var _a2;
331
+ return worker.isConnected() && Date.now() - ((_a2 = alive.get(worker.id)) != null ? _a2 : 0) < 1e4;
332
+ }) && ((_a = supplied == null ? void 0 : supplied.isLive) == null ? void 0 : _a.call(supplied)) !== false;
333
+ }
334
+ }));
335
+ }
336
+ if (closing) {
337
+ if (ops) yield _chunkHJFBEJT6cjs.closeHttpServer.call(void 0, ops);
338
+ yield closing;
339
+ return { close };
340
+ }
341
+ for (let i = 0; i < workers; i++) {
342
+ const worker = _cluster2.default.fork();
343
+ children.add(worker);
344
+ worker.once("error", failed);
345
+ worker.once("exit", (code, signal) => {
346
+ alive.delete(worker.id);
347
+ failed(new Error(`Den worker ${String(worker.process.pid)} exited (${String(code != null ? code : signal)})`));
348
+ });
349
+ }
350
+ yield ready;
351
+ started = true;
352
+ clearTimeout(timeout);
353
+ if (!closing) logger2.info(`\u0437\u0430\u043F\u0443\u0449\u0435\u043D\u043E \u0432\u043E\u0440\u043A\u0435\u0440\u043E\u0432: ${String(workers)}`);
354
+ return { close };
355
+ } catch (error) {
356
+ yield close();
357
+ throw error;
358
+ }
359
+ });
360
+ }
361
+ function stopWorker(worker) {
362
+ return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
363
+ if (worker.isDead()) return;
364
+ yield new Promise((resolve) => {
365
+ const timeout = setTimeout(() => worker.kill("SIGKILL"), SHUTDOWN_MS);
366
+ worker.once("exit", () => {
367
+ clearTimeout(timeout);
368
+ resolve();
369
+ });
370
+ worker.kill("SIGTERM");
371
+ });
372
+ });
373
+ }
374
+
156
375
  // src/serve/index.ts
376
+ function serveAppProcess(app, options = {}) {
377
+ return startAppProcess(app.name, options, (serveOptions) => serveApp(app, serveOptions));
378
+ }
157
379
  var SHUTDOWN_SIGNALS = ["SIGTERM", "SIGINT"];
158
380
  function serveApp(_0) {
159
381
  return _chunkT5QVCXVBcjs.__async.call(void 0, this, arguments, function* (app, options = {}) {
160
- var _a, _b, _c, _d;
382
+ var _a, _b, _c, _d, _e;
161
383
  const appLog = (_a = _logger.log.getLogger(app.name)) != null ? _a : _chunkA6ZPAM6Zcjs.logger;
162
384
  const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
163
385
  const instance = _fastify2.default.call(void 0, { logger: false });
386
+ const metrics = _chunkHJFBEJT6cjs.getProcessMetrics.call(void 0, );
387
+ metrics.startRuntime(app.name);
388
+ let ops;
389
+ const shutdown = () => {
390
+ void instance.close();
391
+ };
392
+ instance.addHook("onRequest", (req, reply, done) => {
393
+ if (_chunkHJFBEJT6cjs.isOpsPath.call(void 0, req.url)) {
394
+ void reply.code(404).send();
395
+ return;
396
+ }
397
+ metrics.trackHttp(req.raw, reply.raw, () => {
398
+ var _a2;
399
+ return (_a2 = req.routeOptions.url) != null ? _a2 : "unmatched";
400
+ });
401
+ done();
402
+ });
403
+ instance.addHook("onClose", () => _chunkT5QVCXVBcjs.__async.call(void 0, null, null, function* () {
404
+ for (const signal of SHUTDOWN_SIGNALS) {
405
+ process.removeListener(signal, shutdown);
406
+ }
407
+ if (ops) {
408
+ yield _chunkHJFBEJT6cjs.closeHttpServer.call(void 0, ops);
409
+ }
410
+ }));
164
411
  yield instance.register(appPlugin, { app, slowRenderMs: options.slowRenderMs });
165
412
  if (options.assets) {
166
413
  registerAssets(instance, options.assets);
@@ -174,14 +421,19 @@ function serveApp(_0) {
174
421
  serveLog.info(`\u2190 ${String(reply.statusCode)} ${req.method} ${req.url} ${elapsed}\u043C\u0441`);
175
422
  done();
176
423
  });
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 });
424
+ try {
425
+ yield (_c = options.configure) == null ? void 0 : _c.call(options, instance);
426
+ if (options.ops !== false) {
427
+ ops = yield _chunkHJFBEJT6cjs.startOpsServer.call(void 0, options.ops);
428
+ }
429
+ yield instance.listen({ host: (_d = options.host) != null ? _d : "0.0.0.0", port: (_e = options.port) != null ? _e : 8080 });
430
+ } catch (error) {
431
+ yield instance.close();
432
+ throw error;
433
+ }
179
434
  if (options.gracefulShutdown !== false) {
180
435
  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
- });
436
+ process.once(signal, shutdown);
185
437
  }
186
438
  }
187
439
  serveLog.info(
@@ -195,4 +447,5 @@ function serveApp(_0) {
195
447
 
196
448
 
197
449
 
198
- exports.appPlugin = appPlugin; exports.loadAppEnv = _chunkKQAB6C6Qcjs.loadAppEnv; exports.registerAssets = registerAssets; exports.serveApp = serveApp;
450
+
451
+ exports.appPlugin = appPlugin; exports.loadAppEnv = _chunkKQAB6C6Qcjs.loadAppEnv; exports.registerAssets = registerAssets; exports.serveApp = serveApp; exports.serveAppProcess = serveAppProcess;
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
  * потребителя, вместе с его плагинами.
@@ -41,6 +44,14 @@ interface AppPluginOptions {
41
44
  */
42
45
  declare const appPlugin: FastifyPluginAsync<AppPluginOptions>;
43
46
 
47
+ interface AppProcess {
48
+ close: () => Promise<void>;
49
+ }
50
+ interface ServeAppProcessOptions extends ServeAppOptions {
51
+ /** Число процессов приложения; по умолчанию 1. Каждый воркер имеет свой контейнер. */
52
+ workers?: number;
53
+ }
54
+
44
55
  /**
45
56
  * Кладёт умолчания из `<root>/.env` в окружение процесса.
46
57
  *
@@ -50,10 +61,14 @@ declare const appPlugin: FastifyPluginAsync<AppPluginOptions>;
50
61
  */
51
62
  declare function loadAppEnv(root: string): void;
52
63
 
64
+ /** Точка входа самостоятельного процесса: один инстанс либо cluster с общим портом. */
65
+ declare function serveAppProcess(app: ServableApp, options?: ServeAppProcessOptions): Promise<AppProcess>;
53
66
  /** Настройки инстанса. */
54
67
  interface ServeAppOptions {
55
68
  /** Порт; `0` — свободный, фактический смотри в `server.addresses()`. */
56
69
  port?: number;
70
+ /** Второй HTTP-сервер для probes и метрик; false — подключается потребителем. */
71
+ ops?: OpsOptions | false;
57
72
  /** Интерфейс прослушивания. */
58
73
  host?: string;
59
74
  /** Порог, выше которого рендер считается медленным и уходит в `warn`, мс. */
@@ -88,4 +103,4 @@ interface ServeAppOptions {
88
103
  */
89
104
  declare function serveApp(app: ServableApp, options?: ServeAppOptions): Promise<FastifyInstance>;
90
105
 
91
- export { type AppAssetsSource, type AppPluginOptions, type ServableApp, type ServeAppOptions, appPlugin, loadAppEnv, registerAssets, serveApp };
106
+ export { type AppAssetsSource, type AppPluginOptions, type AppProcess, type ServableApp, type ServeAppOptions, type ServeAppProcessOptions, appPlugin, loadAppEnv, registerAssets, serveApp, serveAppProcess };
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
  * потребителя, вместе с его плагинами.
@@ -41,6 +44,14 @@ interface AppPluginOptions {
41
44
  */
42
45
  declare const appPlugin: FastifyPluginAsync<AppPluginOptions>;
43
46
 
47
+ interface AppProcess {
48
+ close: () => Promise<void>;
49
+ }
50
+ interface ServeAppProcessOptions extends ServeAppOptions {
51
+ /** Число процессов приложения; по умолчанию 1. Каждый воркер имеет свой контейнер. */
52
+ workers?: number;
53
+ }
54
+
44
55
  /**
45
56
  * Кладёт умолчания из `<root>/.env` в окружение процесса.
46
57
  *
@@ -50,10 +61,14 @@ declare const appPlugin: FastifyPluginAsync<AppPluginOptions>;
50
61
  */
51
62
  declare function loadAppEnv(root: string): void;
52
63
 
64
+ /** Точка входа самостоятельного процесса: один инстанс либо cluster с общим портом. */
65
+ declare function serveAppProcess(app: ServableApp, options?: ServeAppProcessOptions): Promise<AppProcess>;
53
66
  /** Настройки инстанса. */
54
67
  interface ServeAppOptions {
55
68
  /** Порт; `0` — свободный, фактический смотри в `server.addresses()`. */
56
69
  port?: number;
70
+ /** Второй HTTP-сервер для probes и метрик; false — подключается потребителем. */
71
+ ops?: OpsOptions | false;
57
72
  /** Интерфейс прослушивания. */
58
73
  host?: string;
59
74
  /** Порог, выше которого рендер считается медленным и уходит в `warn`, мс. */
@@ -88,4 +103,4 @@ interface ServeAppOptions {
88
103
  */
89
104
  declare function serveApp(app: ServableApp, options?: ServeAppOptions): Promise<FastifyInstance>;
90
105
 
91
- export { type AppAssetsSource, type AppPluginOptions, type ServableApp, type ServeAppOptions, appPlugin, loadAppEnv, registerAssets, serveApp };
106
+ export { type AppAssetsSource, type AppPluginOptions, type AppProcess, type ServableApp, type ServeAppOptions, type ServeAppProcessOptions, appPlugin, loadAppEnv, registerAssets, serveApp, serveAppProcess };