@foxford/den 3.0.0 → 3.1.1

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 (57) hide show
  1. package/README.mdx +266 -2
  2. package/adapter.cjs +1 -1
  3. package/adapter.d.cts +1 -1
  4. package/adapter.d.ts +1 -1
  5. package/adapter.js +1 -1
  6. package/bin/den.cjs +88 -0
  7. package/bin/den.d.cts +2 -0
  8. package/bin/den.d.ts +2 -0
  9. package/bin/den.js +88 -0
  10. package/builder/index.cjs +12 -0
  11. package/builder/index.d.cts +120 -0
  12. package/builder/index.d.ts +120 -0
  13. package/builder/index.js +12 -0
  14. package/chunk-3RX2OSUL.js +21 -0
  15. package/{chunk-HJO26HIQ.js → chunk-6FNC3XMI.js} +4 -0
  16. package/chunk-6QV4L6R2.cjs +21 -0
  17. package/{chunk-WRQC6BVJ.js → chunk-MCN4GFEQ.js} +4 -19
  18. package/chunk-OKRO4G4L.cjs +354 -0
  19. package/chunk-REFU7UMG.js +354 -0
  20. package/{chunk-XHYR3SGG.cjs → chunk-T5QVCXVB.cjs} +5 -1
  21. package/chunk-TPHJUDHG.js +230 -0
  22. package/{chunk-5DJZZML5.cjs → chunk-Z5BL4DJA.cjs} +10 -25
  23. package/chunk-ZOU6W6ZQ.cjs +230 -0
  24. package/{define-repository-MDEWHqM7.d.cts → define-repository-CwAXed2X.d.cts} +1 -1
  25. package/{define-repository-Ca62_YCy.d.ts → define-repository-q_T4hqeP.d.ts} +1 -1
  26. package/{define-slot-Dil8Kr1A.d.ts → define-slot-Cngzae6i.d.ts} +1 -1
  27. package/{define-slot-BKvArA02.d.cts → define-slot-DYNCLDJY.d.cts} +1 -1
  28. package/define.cjs +6 -4
  29. package/define.d.cts +3 -3
  30. package/define.d.ts +3 -3
  31. package/define.js +5 -3
  32. package/den.js +60 -0
  33. package/descriptor-DQ6Gi7A_.d.ts +98 -0
  34. package/descriptor-Dujg_1on.d.cts +98 -0
  35. package/index.cjs +25 -23
  36. package/index.d.cts +7 -5
  37. package/index.d.ts +7 -5
  38. package/index.js +12 -10
  39. package/island/index.cjs +12 -157
  40. package/island/index.d.cts +40 -30
  41. package/island/index.d.ts +40 -30
  42. package/island/index.js +19 -164
  43. package/package.json +59 -16
  44. package/routes-0uXJ0fux.d.ts +64 -0
  45. package/routes-B0hDrkiF.d.cts +64 -0
  46. package/serve/index.cjs +137 -0
  47. package/serve/index.d.cts +60 -0
  48. package/serve/index.d.ts +60 -0
  49. package/serve/index.js +137 -0
  50. package/server-render-CocWXQKC.d.cts +48 -0
  51. package/server-render-CocWXQKC.d.ts +48 -0
  52. package/{types-COwVwgzE.d.cts → types-Dx5qGiwk.d.cts} +1 -1
  53. package/{types-COwVwgzE.d.ts → types-Dx5qGiwk.d.ts} +1 -1
  54. package/view-adapter-B4gEb-ms.d.ts +77 -0
  55. package/view-adapter-DJ7yB6Ml.d.cts +77 -0
  56. package/view-adapter-BvDC5O4y.d.cts +0 -188
  57. package/view-adapter-DtNgyjIf.d.ts +0 -188
package/island/index.js CHANGED
@@ -1,169 +1,27 @@
1
+ import {
2
+ activateIslandViewModels,
3
+ collectDocumentContribution,
4
+ collectIslandState,
5
+ createIslandContainer,
6
+ getAppContainer,
7
+ hydrateIslandState,
8
+ isDocumentSource,
9
+ matchRoute,
10
+ normalizeRoutes,
11
+ pathPattern,
12
+ renderApp,
13
+ resolveEagerUnits
14
+ } from "../chunk-TPHJUDHG.js";
1
15
  import {
2
16
  ViewLayerToken,
3
17
  resolveViewAdapter,
4
18
  viewAdapterOf
5
19
  } from "../chunk-E23E2VUC.js";
20
+ import "../chunk-3RX2OSUL.js";
21
+ import "../chunk-TPBI6TOU.js";
6
22
  import {
7
- LoggerToken,
8
- logger,
9
- unitLogger
10
- } from "../chunk-TPBI6TOU.js";
11
- import {
12
- __async,
13
23
  __spreadValues
14
- } from "../chunk-HJO26HIQ.js";
15
-
16
- // src/island/document.ts
17
- function isDocumentSource(value) {
18
- return typeof value === "object" && value !== null && typeof value.describeDocument === "function";
19
- }
20
-
21
- // src/island/lifecycle.ts
22
- import { log } from "@foxford/logger";
23
-
24
- // src/island/runtime.ts
25
- import { Container } from "@foxford/ioc";
26
- var appContainer = null;
27
- function getAppContainer() {
28
- var _a;
29
- if (appContainer === null) {
30
- appContainer = new Container();
31
- ((_a = logger.getLogger("island:runtime")) != null ? _a : logger).debug("app-\u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440 \u0441\u043E\u0437\u0434\u0430\u043D");
32
- }
33
- return appContainer;
34
- }
35
-
36
- // src/island/lifecycle.ts
37
- var EMPTY_ROUTE = { params: {}, pathname: "", search: "" };
38
- var REDIRECT_FOUND = 302;
39
- function createIslandContainer(descriptor, options = {}) {
40
- var _a, _b, _c, _d, _e;
41
- const { parent = getAppContainer() } = options;
42
- const appLog = (_b = log.getLogger((_a = descriptor.name) != null ? _a : "app")) != null ? _b : logger;
43
- const islandLog = (_c = appLog.getLogger("client")) != null ? _c : appLog;
44
- try {
45
- const container = parent.createChild();
46
- container.bind(LoggerToken).toValue(islandLog);
47
- descriptor.viewLayer.register(container);
48
- const viewModels = (_d = descriptor.viewModels) != null ? _d : [];
49
- const slots = Object.entries((_e = descriptor.slots) != null ? _e : {});
50
- islandLog.info(
51
- `\u043E\u0441\u0442\u0440\u043E\u0432: \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440 \u0441\u043E\u0437\u0434\u0430\u043D (units=${descriptor.units.length}, vms=${viewModels.length}, slots=${slots.length})`
52
- );
53
- for (const unit of descriptor.units) {
54
- unit.register(container);
55
- }
56
- for (const vm of viewModels) {
57
- vm.register(container);
58
- }
59
- for (const [name, slot] of slots) {
60
- slot.register(container, name);
61
- }
62
- return container;
63
- } catch (error) {
64
- islandLog.error("\u043E\u0441\u0442\u0440\u043E\u0432: \u0441\u0431\u043E\u0440\u043A\u0430 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440\u0430 \u0443\u043F\u0430\u043B\u0430", error);
65
- return null;
66
- }
67
- }
68
- function hydrateIslandState(container, descriptor, denState) {
69
- var _a, _b;
70
- for (const vm of (_a = descriptor.viewModels) != null ? _a : []) {
71
- const instance = container.resolve(vm.token);
72
- const saved = denState[vm.token.description];
73
- if (saved !== void 0 && typeof ((_b = instance.state) == null ? void 0 : _b.set) === "function") {
74
- unitLogger(container, vm.token.description).debug("\u0433\u0438\u0434\u0440\u0430\u0446\u0438\u044F VM \u0438\u0437 denState");
75
- instance.state.set(saved);
76
- }
77
- }
78
- }
79
- function activateIslandViewModels(_0, _1) {
80
- return __async(this, arguments, function* (container, descriptor, route = EMPTY_ROUTE) {
81
- var _a;
82
- yield Promise.all(
83
- ((_a = descriptor.viewModels) != null ? _a : []).map((vm) => __async(null, null, function* () {
84
- var _a2;
85
- const instance = container.resolve(vm.token);
86
- unitLogger(container, vm.token.description).debug(`\u0430\u043A\u0442\u0438\u0432\u0430\u0446\u0438\u044F VM (${route.pathname || "\u2014"})`);
87
- yield (_a2 = instance.activate) == null ? void 0 : _a2.call(instance, route);
88
- }))
89
- );
90
- });
91
- }
92
- function collectIslandState(container, descriptor) {
93
- var _a, _b;
94
- const state = {};
95
- for (const vm of (_a = descriptor.viewModels) != null ? _a : []) {
96
- const instance = container.resolve(vm.token);
97
- if (typeof ((_b = instance.state) == null ? void 0 : _b.get) === "function") {
98
- state[vm.token.description] = instance.state.get();
99
- }
100
- }
101
- return state;
102
- }
103
- function collectDocumentContribution(container, descriptor) {
104
- var _a, _b;
105
- const contribution = {};
106
- for (const vm of (_a = descriptor.viewModels) != null ? _a : []) {
107
- const instance = container.resolve(vm.token);
108
- if (!isDocumentSource(instance)) {
109
- continue;
110
- }
111
- const part = instance.describeDocument();
112
- if (part.head) {
113
- contribution.head = __spreadValues(__spreadValues({}, contribution.head), part.head);
114
- }
115
- const declared = part.status !== void 0 || part.redirect !== void 0;
116
- if (declared && contribution.status === void 0) {
117
- contribution.status = (_b = part.status) != null ? _b : REDIRECT_FOUND;
118
- contribution.redirect = part.redirect;
119
- }
120
- }
121
- return contribution;
122
- }
123
- function resolveEagerUnits(container, descriptor) {
124
- var _a;
125
- for (const token of (_a = descriptor.eager) != null ? _a : []) {
126
- const instance = container.resolve(token);
127
- unitLogger(container, token.description).debug("eager: \u0435\u0434\u0438\u043D\u0438\u0446\u0430 \u043F\u043E\u0434\u043D\u044F\u0442\u0430 \u043F\u0440\u0438 \u043C\u043E\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0438");
128
- if (instance instanceof Promise) {
129
- instance.catch(
130
- (error) => unitLogger(container, token.description).error("eager: \u0435\u0434\u0438\u043D\u0438\u0446\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C", error)
131
- );
132
- }
133
- }
134
- }
135
-
136
- // src/island/render.ts
137
- var STATUS_OK = 200;
138
- function renderApp(descriptor, request) {
139
- return __async(this, null, function* () {
140
- var _a, _b;
141
- const route = {
142
- params: (_a = request.params) != null ? _a : {},
143
- pathname: request.pathname,
144
- search: (_b = request.search) != null ? _b : ""
145
- };
146
- const container = createIslandContainer(descriptor, { parent: request.parent });
147
- if (container === null) {
148
- throw new Error(`den/island: \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \xAB${descriptor.name}\xBB \u043D\u0435 \u0441\u043E\u0431\u0440\u0430\u043B\u043E\u0441\u044C \u2014 \u0440\u0435\u043D\u0434\u0435\u0440 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u0435\u043D`);
149
- }
150
- try {
151
- yield activateIslandViewModels(container, descriptor, route);
152
- const { head, redirect, status = STATUS_OK } = collectDocumentContribution(container, descriptor);
153
- if (status !== STATUS_OK) {
154
- return { head, html: "", redirect, state: {}, status };
155
- }
156
- const state = collectIslandState(container, descriptor);
157
- const rendered = resolveViewAdapter(container).renderIsland(descriptor, {
158
- container,
159
- slots: request.slots
160
- });
161
- return { head, headHtml: rendered.head, html: rendered.html, state, status };
162
- } finally {
163
- yield container.dispose();
164
- }
165
- });
166
- }
24
+ } from "../chunk-6FNC3XMI.js";
167
25
 
168
26
  // src/island/slot-address.ts
169
27
  function slotAddress(app, slot) {
@@ -209,11 +67,6 @@ function describeIsland(descriptor, denState) {
209
67
  viewModels: ((_b = descriptor.viewModels) != null ? _b : []).map((vm) => vm.token.description)
210
68
  };
211
69
  }
212
-
213
- // src/island/routes.ts
214
- function normalizeRoutes(routes = []) {
215
- return routes.map((route) => typeof route === "string" ? { source: route } : route);
216
- }
217
70
  export {
218
71
  ViewLayerToken,
219
72
  activateIslandViewModels,
@@ -225,7 +78,9 @@ export {
225
78
  getIslandsSnapshot,
226
79
  hydrateIslandState,
227
80
  isDocumentSource,
81
+ matchRoute,
228
82
  normalizeRoutes,
83
+ pathPattern,
229
84
  registerIsland,
230
85
  renderApp,
231
86
  resolveEagerUnits,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxford/den",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "description": "Den — декларативный метафреймворк Foxford (core runtime)",
5
5
  "keywords": [
6
6
  "foxford",
@@ -19,9 +19,21 @@
19
19
  }
20
20
  ],
21
21
  "type": "module",
22
+ "bin": {
23
+ "den": "./den.js"
24
+ },
22
25
  "dependencies": {
23
- "@foxford/ioc": "^1.1.2",
24
- "@foxford/logger": "^1.2.2"
26
+ "fastify": "^5.11.2",
27
+ "@foxford/logger": "^1.2.2",
28
+ "@foxford/ioc": "^1.1.2"
29
+ },
30
+ "peerDependencies": {
31
+ "vite": ">=5.0.0"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "vite": {
35
+ "optional": true
36
+ }
25
37
  },
26
38
  "main": "./index.cjs",
27
39
  "module": "./index.js",
@@ -36,6 +48,16 @@
36
48
  "require": "./adapter.cjs",
37
49
  "types": "./adapter.d.ts"
38
50
  },
51
+ "./bin": {
52
+ "import": "./bin/den.js",
53
+ "require": "./bin/den.cjs",
54
+ "types": "./bin/den.d.ts"
55
+ },
56
+ "./build": {
57
+ "import": "./builder/index.js",
58
+ "require": "./builder/index.cjs",
59
+ "types": "./builder/index.d.ts"
60
+ },
39
61
  "./define": {
40
62
  "import": "./define.js",
41
63
  "require": "./define.cjs",
@@ -45,6 +67,11 @@
45
67
  "import": "./island/index.js",
46
68
  "require": "./island/index.cjs",
47
69
  "types": "./island/index.d.ts"
70
+ },
71
+ "./serve": {
72
+ "import": "./serve/index.js",
73
+ "require": "./serve/index.cjs",
74
+ "types": "./serve/index.d.ts"
48
75
  }
49
76
  },
50
77
  "files": [
@@ -53,35 +80,51 @@
53
80
  "adapter.d.cts",
54
81
  "adapter.d.ts",
55
82
  "adapter.js",
56
- "chunk-5DJZZML5.cjs",
83
+ "bin",
84
+ "builder",
85
+ "chunk-3RX2OSUL.js",
86
+ "chunk-6FNC3XMI.js",
87
+ "chunk-6QV4L6R2.cjs",
57
88
  "chunk-A6ZPAM6Z.cjs",
58
89
  "chunk-C7BM4DGX.cjs",
59
90
  "chunk-E23E2VUC.js",
60
- "chunk-HJO26HIQ.js",
91
+ "chunk-MCN4GFEQ.js",
92
+ "chunk-OKRO4G4L.cjs",
93
+ "chunk-REFU7UMG.js",
94
+ "chunk-T5QVCXVB.cjs",
61
95
  "chunk-TPBI6TOU.js",
96
+ "chunk-TPHJUDHG.js",
62
97
  "chunk-WBHHHICS.js",
63
- "chunk-WRQC6BVJ.js",
64
- "chunk-XHYR3SGG.cjs",
65
98
  "chunk-XOJ2VWX4.cjs",
66
- "define-repository-Ca62_YCy.d.ts",
67
- "define-repository-MDEWHqM7.d.cts",
68
- "define-slot-BKvArA02.d.cts",
69
- "define-slot-Dil8Kr1A.d.ts",
99
+ "chunk-Z5BL4DJA.cjs",
100
+ "chunk-ZOU6W6ZQ.cjs",
101
+ "define-repository-CwAXed2X.d.cts",
102
+ "define-repository-q_T4hqeP.d.ts",
103
+ "define-slot-Cngzae6i.d.ts",
104
+ "define-slot-DYNCLDJY.d.cts",
70
105
  "define.cjs",
71
106
  "define.d.cts",
72
107
  "define.d.ts",
73
108
  "define.js",
109
+ "den.js",
110
+ "descriptor-DQ6Gi7A_.d.ts",
111
+ "descriptor-Dujg_1on.d.cts",
74
112
  "index.cjs",
75
113
  "index.d.cts",
76
114
  "index.d.ts",
77
115
  "index.js",
78
116
  "island",
79
117
  "package.json",
80
- "types-COwVwgzE.d.cts",
81
- "types-COwVwgzE.d.ts",
82
- "view-adapter-BvDC5O4y.d.cts",
83
- "view-adapter-DtNgyjIf.d.ts"
118
+ "routes-0uXJ0fux.d.ts",
119
+ "routes-B0hDrkiF.d.cts",
120
+ "serve",
121
+ "server-render-CocWXQKC.d.cts",
122
+ "server-render-CocWXQKC.d.ts",
123
+ "types-Dx5qGiwk.d.cts",
124
+ "types-Dx5qGiwk.d.ts",
125
+ "view-adapter-B4gEb-ms.d.ts",
126
+ "view-adapter-DJ7yB6Ml.d.cts"
84
127
  ],
85
- "sha": "ba6123d",
128
+ "sha": "dd4b7ca",
86
129
  "scripts": {}
87
130
  }
@@ -0,0 +1,64 @@
1
+ import { G as GuardDefinition } from './types-Dx5qGiwk.js';
2
+
3
+ /** Маршрут с политикой доступа. */
4
+ interface RouteDeclaration {
5
+ /** Regex-источник адреса (`'^/cabinet/billing'`, `'.*'`). */
6
+ source: string;
7
+ /**
8
+ * Guard-ы этого маршрута — поверх guard-ов приложения и хоста. Пусто — маршрут открыт
9
+ * настолько, насколько открыто само приложение.
10
+ */
11
+ guards?: ReadonlyArray<GuardDefinition>;
12
+ }
13
+ /**
14
+ * Заявка на адрес: голая строка — публичный маршрут, объект — маршрут со своей политикой.
15
+ * Обе формы лежат в одном массиве, и порядок значим так же, как раньше: хост раздаёт путь
16
+ * первому заявившему, поэтому частные записи ставятся выше общих.
17
+ */
18
+ type AppRoute = string | RouteDeclaration;
19
+ /**
20
+ * Приводит заявки к одной форме — чтобы хост и dev-инструменты не разбирали union каждый сам.
21
+ *
22
+ * @param routes - Заявки приложения (строки и/или записи)
23
+ * @returns Записи маршрутов в порядке объявления
24
+ */
25
+ declare function normalizeRoutes(routes?: ReadonlyArray<AppRoute>): RouteDeclaration[];
26
+ /** Совпадение адреса с заявкой: сама запись и выделенные ею параметры. */
27
+ interface RouteMatch {
28
+ route: RouteDeclaration;
29
+ /** Именованные группы совпавшего маршрута; пусто, если их нет. */
30
+ params: Record<string, string>;
31
+ }
32
+ /**
33
+ * Ищет первую заявку, которой принадлежит путь, и выделяет её параметры.
34
+ *
35
+ * Один матчер на всех, потому что совпадений в системе два и они обязаны сходиться: хост решает
36
+ * по нему, кому отдать адрес, а рантайм приложения — своё ли это. Разойдись они, приложение
37
+ * получало бы запросы по путям, которых не заявляло.
38
+ *
39
+ * Параметры — именованные группы регулярки (`(?<slug>[^/]+)`); писать их руками не нужно,
40
+ * для этого есть {@link pathPattern}. Безымянные группы в параметры не попадают: у них нет
41
+ * имени, под которым их ждать.
42
+ *
43
+ * @param routes - Заявки приложения в порядке объявления
44
+ * @param pathname - Путь без search
45
+ * @returns Совпадение либо `null`
46
+ */
47
+ declare function matchRoute(routes: ReadonlyArray<AppRoute> | undefined, pathname: string): RouteMatch | null;
48
+ /**
49
+ * Делает из шаблона адреса источник регулярки с именованными группами:
50
+ * `'/:foo/:bar'` → `'^/(?<foo>[^/]+)/(?<bar>[^/]+)$'`.
51
+ *
52
+ * Отдельным помощником, а не распознаванием `:` прямо в `source`: заявка объявляется
53
+ * регуляркой, и в ней двоеточие — обычный символ. Угадывай мы синтаксис по содержимому
54
+ * строки — законная регулярка однажды молча сменила бы смысл.
55
+ *
56
+ * Шаблон привязан к обоим концам (`^…$`): `'/:slug'` означает один сегмент, а не «путь,
57
+ * который где-то внутри похож на сегмент».
58
+ *
59
+ * @param pattern - Шаблон адреса с сегментами-параметрами
60
+ * @returns Источник для {@link RouteDeclaration.source}
61
+ */
62
+ declare function pathPattern(pattern: string): string;
63
+
64
+ export { type AppRoute as A, type RouteDeclaration as R, type RouteMatch as a, matchRoute as m, normalizeRoutes as n, pathPattern as p };
@@ -0,0 +1,64 @@
1
+ import { G as GuardDefinition } from './types-Dx5qGiwk.cjs';
2
+
3
+ /** Маршрут с политикой доступа. */
4
+ interface RouteDeclaration {
5
+ /** Regex-источник адреса (`'^/cabinet/billing'`, `'.*'`). */
6
+ source: string;
7
+ /**
8
+ * Guard-ы этого маршрута — поверх guard-ов приложения и хоста. Пусто — маршрут открыт
9
+ * настолько, насколько открыто само приложение.
10
+ */
11
+ guards?: ReadonlyArray<GuardDefinition>;
12
+ }
13
+ /**
14
+ * Заявка на адрес: голая строка — публичный маршрут, объект — маршрут со своей политикой.
15
+ * Обе формы лежат в одном массиве, и порядок значим так же, как раньше: хост раздаёт путь
16
+ * первому заявившему, поэтому частные записи ставятся выше общих.
17
+ */
18
+ type AppRoute = string | RouteDeclaration;
19
+ /**
20
+ * Приводит заявки к одной форме — чтобы хост и dev-инструменты не разбирали union каждый сам.
21
+ *
22
+ * @param routes - Заявки приложения (строки и/или записи)
23
+ * @returns Записи маршрутов в порядке объявления
24
+ */
25
+ declare function normalizeRoutes(routes?: ReadonlyArray<AppRoute>): RouteDeclaration[];
26
+ /** Совпадение адреса с заявкой: сама запись и выделенные ею параметры. */
27
+ interface RouteMatch {
28
+ route: RouteDeclaration;
29
+ /** Именованные группы совпавшего маршрута; пусто, если их нет. */
30
+ params: Record<string, string>;
31
+ }
32
+ /**
33
+ * Ищет первую заявку, которой принадлежит путь, и выделяет её параметры.
34
+ *
35
+ * Один матчер на всех, потому что совпадений в системе два и они обязаны сходиться: хост решает
36
+ * по нему, кому отдать адрес, а рантайм приложения — своё ли это. Разойдись они, приложение
37
+ * получало бы запросы по путям, которых не заявляло.
38
+ *
39
+ * Параметры — именованные группы регулярки (`(?<slug>[^/]+)`); писать их руками не нужно,
40
+ * для этого есть {@link pathPattern}. Безымянные группы в параметры не попадают: у них нет
41
+ * имени, под которым их ждать.
42
+ *
43
+ * @param routes - Заявки приложения в порядке объявления
44
+ * @param pathname - Путь без search
45
+ * @returns Совпадение либо `null`
46
+ */
47
+ declare function matchRoute(routes: ReadonlyArray<AppRoute> | undefined, pathname: string): RouteMatch | null;
48
+ /**
49
+ * Делает из шаблона адреса источник регулярки с именованными группами:
50
+ * `'/:foo/:bar'` → `'^/(?<foo>[^/]+)/(?<bar>[^/]+)$'`.
51
+ *
52
+ * Отдельным помощником, а не распознаванием `:` прямо в `source`: заявка объявляется
53
+ * регуляркой, и в ней двоеточие — обычный символ. Угадывай мы синтаксис по содержимому
54
+ * строки — законная регулярка однажды молча сменила бы смысл.
55
+ *
56
+ * Шаблон привязан к обоим концам (`^…$`): `'/:slug'` означает один сегмент, а не «путь,
57
+ * который где-то внутри похож на сегмент».
58
+ *
59
+ * @param pattern - Шаблон адреса с сегментами-параметрами
60
+ * @returns Источник для {@link RouteDeclaration.source}
61
+ */
62
+ declare function pathPattern(pattern: string): string;
63
+
64
+ export { type AppRoute as A, type RouteDeclaration as R, type RouteMatch as a, matchRoute as m, normalizeRoutes as n, pathPattern as p };
@@ -0,0 +1,137 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
2
+
3
+
4
+
5
+ var _chunkZOU6W6ZQcjs = require('../chunk-ZOU6W6ZQ.cjs');
6
+ require('../chunk-XOJ2VWX4.cjs');
7
+ require('../chunk-6QV4L6R2.cjs');
8
+
9
+
10
+ var _chunkA6ZPAM6Zcjs = require('../chunk-A6ZPAM6Z.cjs');
11
+
12
+
13
+ var _chunkT5QVCXVBcjs = require('../chunk-T5QVCXVB.cjs');
14
+
15
+ // src/serve/index.ts
16
+ var _fastify = require('fastify'); var _fastify2 = _interopRequireDefault(_fastify);
17
+ var _logger = require('@foxford/logger');
18
+
19
+ // src/serve/plugin.ts
20
+
21
+ var DEFAULT_SLOW_RENDER_MS = 1e3;
22
+ function claim(app) {
23
+ return _chunkZOU6W6ZQcjs.normalizeRoutes.call(void 0, app.routes).map((route) => route.source);
24
+ }
25
+ function matchClaim(app, pathname) {
26
+ var _a, _b;
27
+ if (claim(app).length === 0) {
28
+ return {};
29
+ }
30
+ return (_b = (_a = _chunkZOU6W6ZQcjs.matchRoute.call(void 0, app.routes, pathname)) == null ? void 0 : _a.params) != null ? _b : null;
31
+ }
32
+ function parseCookies(header) {
33
+ const cookies = {};
34
+ for (const part of (header != null ? header : "").split(";")) {
35
+ const eq = part.indexOf("=");
36
+ if (eq < 1) {
37
+ continue;
38
+ }
39
+ const name = part.slice(0, eq).trim();
40
+ if (name && !(name in cookies)) {
41
+ cookies[name] = part.slice(eq + 1).trim();
42
+ }
43
+ }
44
+ return cookies;
45
+ }
46
+ function browserContext(req) {
47
+ const headers = {};
48
+ const forwarded = {
49
+ "accept-language": req.headers["x-forwarded-accept-language"],
50
+ host: req.headers["x-forwarded-host"]
51
+ };
52
+ for (const [name, value] of Object.entries(forwarded)) {
53
+ if (typeof value === "string" && value !== "") {
54
+ headers[name] = value;
55
+ }
56
+ }
57
+ const cookie = req.headers.cookie;
58
+ if (typeof cookie === "string" && cookie !== "") {
59
+ headers.cookie = cookie;
60
+ }
61
+ return { cookies: parseCookies(cookie), headers };
62
+ }
63
+ var appPlugin = (instance, options) => {
64
+ var _a, _b;
65
+ const { app, slowRenderMs = DEFAULT_SLOW_RENDER_MS } = options;
66
+ const appLog = (_a = _logger.log.getLogger(app.name)) != null ? _a : _chunkA6ZPAM6Zcjs.logger;
67
+ const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
68
+ instance.get("/_health", () => "ok");
69
+ instance.get("/_routes", (_req, reply) => {
70
+ void reply.header("cache-control", "no-store");
71
+ return claim(app);
72
+ });
73
+ instance.get("/_render", (req, reply) => _chunkT5QVCXVBcjs.__async.call(void 0, null, null, function* () {
74
+ const target = req.query.url;
75
+ if (typeof target !== "string" || !target.startsWith("/")) {
76
+ 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" });
77
+ }
78
+ const { pathname, search } = new URL(target, "http://localhost");
79
+ const params = matchClaim(app, pathname);
80
+ if (params === null) {
81
+ serveLog.error(`\u043F\u0443\u0442\u044C \xAB${pathname}\xBB \u0432\u043D\u0435 \u0437\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F`);
82
+ return reply.code(404).send({ error: `\u043F\u0443\u0442\u044C \xAB${pathname}\xBB \u0432\u043D\u0435 \u0437\u0430\u044F\u0432\u043A\u0438 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F` });
83
+ }
84
+ const startedAt = Date.now();
85
+ try {
86
+ const result = yield _chunkZOU6W6ZQcjs.renderApp.call(void 0, app, { context: browserContext(req), params, pathname, search });
87
+ const elapsed = Date.now() - startedAt;
88
+ if (elapsed >= slowRenderMs) {
89
+ serveLog.warn(`\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u044B\u0439 \u0440\u0435\u043D\u0434\u0435\u0440 \xAB${pathname}\xBB: ${String(elapsed)}\u043C\u0441`);
90
+ }
91
+ return result;
92
+ } catch (error) {
93
+ serveLog.error(`\u0440\u0435\u043D\u0434\u0435\u0440 \xAB${pathname}\xBB \u0443\u043F\u0430\u043B \u0437\u0430 ${String(Date.now() - startedAt)}\u043C\u0441`, error);
94
+ return reply.code(500).send({ error: error instanceof Error ? error.message : String(error) });
95
+ }
96
+ }));
97
+ return Promise.resolve();
98
+ };
99
+
100
+ // src/serve/index.ts
101
+ var SHUTDOWN_SIGNALS = ["SIGTERM", "SIGINT"];
102
+ function serveApp(_0) {
103
+ return _chunkT5QVCXVBcjs.__async.call(void 0, this, arguments, function* (app, options = {}) {
104
+ var _a, _b, _c, _d;
105
+ const appLog = (_a = _logger.log.getLogger(app.name)) != null ? _a : _chunkA6ZPAM6Zcjs.logger;
106
+ const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
107
+ const instance = _fastify2.default.call(void 0, { logger: false });
108
+ yield instance.register(appPlugin, { app, slowRenderMs: options.slowRenderMs });
109
+ instance.addHook("onRequest", (req, _reply, done) => {
110
+ serveLog.debug(`\u2192 ${req.method} ${req.url}`);
111
+ done();
112
+ });
113
+ instance.addHook("onResponse", (req, reply, done) => {
114
+ const elapsed = reply.elapsedTime.toFixed(1);
115
+ serveLog.info(`\u2190 ${String(reply.statusCode)} ${req.method} ${req.url} ${elapsed}\u043C\u0441`);
116
+ done();
117
+ });
118
+ yield (_c = options.configure) == null ? void 0 : _c.call(options, instance);
119
+ yield instance.listen({ host: options.host, port: (_d = options.port) != null ? _d : 0 });
120
+ if (options.gracefulShutdown !== false) {
121
+ for (const signal of SHUTDOWN_SIGNALS) {
122
+ process.once(signal, () => {
123
+ serveLog.info(`${signal}: \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u0438\u043D\u0441\u0442\u0430\u043D\u0441`);
124
+ void instance.close();
125
+ });
126
+ }
127
+ }
128
+ serveLog.info(
129
+ `\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \xAB${app.name}\xBB \u0441\u043B\u0443\u0448\u0430\u0435\u0442 ${instance.addresses().map((a) => String(a.port)).join(", ")}`
130
+ );
131
+ return instance;
132
+ });
133
+ }
134
+
135
+
136
+
137
+ exports.appPlugin = appPlugin; exports.serveApp = serveApp;
@@ -0,0 +1,60 @@
1
+ import { I as IslandDescriptor } from '../descriptor-Dujg_1on.cjs';
2
+ import { A as AppRoute } from '../routes-B0hDrkiF.cjs';
3
+ import { FastifyPluginAsync, FastifyInstance } from 'fastify';
4
+ import '../define-slot-DYNCLDJY.cjs';
5
+ import '../types-Dx5qGiwk.cjs';
6
+ import '@foxford/ioc';
7
+
8
+ /** Приложение, которое можно поднять сервисом: остров плюс его заявка на адреса. */
9
+ type ServableApp = IslandDescriptor & {
10
+ routes?: ReadonlyArray<AppRoute>;
11
+ };
12
+ /** Настройки плагина. */
13
+ interface AppPluginOptions {
14
+ app: ServableApp;
15
+ /** Порог, выше которого рендер считается медленным и уходит в `warn`, мс. */
16
+ slowRenderMs?: number;
17
+ }
18
+ /**
19
+ * Регистрирует ручки приложения на инстансе fastify: `/_render`, `/_routes`, `/_health`.
20
+ *
21
+ * Плагином, поэтому монтируется и в свой сервер, и в чужой — под префиксом, рядом с ручками
22
+ * потребителя, вместе с его плагинами.
23
+ *
24
+ * @param instance - Инстанс fastify
25
+ * @param options - Приложение и порог медленного рендера
26
+ */
27
+ declare const appPlugin: FastifyPluginAsync<AppPluginOptions>;
28
+
29
+ /** Настройки инстанса. */
30
+ interface ServeAppOptions {
31
+ /** Порт; `0` — свободный, фактический смотри в `server.addresses()`. */
32
+ port?: number;
33
+ /** Интерфейс прослушивания. */
34
+ host?: string;
35
+ /** Порог, выше которого рендер считается медленным и уходит в `warn`, мс. */
36
+ slowRenderMs?: number;
37
+ /**
38
+ * Расширение инстанса ДО прослушивания: сюда потребитель регистрирует свои плагины.
39
+ * Вызывается после ручек приложения, поэтому хуки видят и их.
40
+ */
41
+ configure?: (instance: FastifyInstance) => void | Promise<void>;
42
+ /**
43
+ * Закрывать инстанс по `SIGTERM`/`SIGINT`. По умолчанию да: сервис живёт в оркестраторе,
44
+ * который именно так и просит его уйти, а незакрытый инстанс рвёт запросы на середине.
45
+ */
46
+ gracefulShutdown?: boolean;
47
+ }
48
+ /**
49
+ * Поднимает приложение отдельным сервисом.
50
+ *
51
+ * Сахар над {@link appPlugin}: если сервер нужен свой (общий порт с чужими ручками, свой
52
+ * порядок плагинов), бери плагин и регистрируй сам.
53
+ *
54
+ * @param app - Дескриптор приложения вместе с его заявкой на адреса
55
+ * @param options - Порт, расширение инстанса, поведение при остановке
56
+ * @returns Слушающий инстанс fastify
57
+ */
58
+ declare function serveApp(app: ServableApp, options?: ServeAppOptions): Promise<FastifyInstance>;
59
+
60
+ export { type AppPluginOptions, type ServableApp, type ServeAppOptions, appPlugin, serveApp };