@foxford/den 2.1.0 → 3.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.
- package/README.mdx +215 -2
- package/chunk-6ZFVV3AV.cjs +214 -0
- package/chunk-7M5OAOQ4.js +214 -0
- package/chunk-CZVYVNLI.cjs +45 -0
- package/{chunk-TCYGDT2A.js → chunk-E23E2VUC.js} +2 -2
- package/chunk-KJ6NDOOL.js +45 -0
- package/{chunk-5DJZZML5.cjs → chunk-P4QJCHMM.cjs} +12 -29
- package/{chunk-WRQC6BVJ.js → chunk-PZVUKTZG.js} +3 -20
- package/{chunk-LJE7K7VD.cjs → chunk-XOJ2VWX4.cjs} +2 -2
- package/define.cjs +4 -4
- package/define.js +3 -3
- package/den-serve.js +146 -0
- package/{view-adapter-CSPKr2xy.d.ts → descriptor-DZDNH7R5.d.ts} +4 -56
- package/{view-adapter-zbHBVVqR.d.cts → descriptor-h0JE9jFx.d.cts} +4 -56
- package/index.cjs +16 -15
- package/index.d.cts +2 -1
- package/index.d.ts +2 -1
- package/index.js +12 -11
- package/island/index.cjs +17 -83
- package/island/index.d.cts +157 -81
- package/island/index.d.ts +157 -81
- package/island/index.js +22 -88
- package/package.json +29 -12
- package/routes-BqIoqbt1.d.cts +64 -0
- package/routes-CWpIqVAM.d.ts +64 -0
- package/serve/index.cjs +136 -0
- package/serve/index.d.cts +60 -0
- package/serve/index.d.ts +60 -0
- package/serve/index.js +136 -0
- package/view-adapter-CPI8056c.d.ts +123 -0
- package/view-adapter-D2597RJZ.d.cts +123 -0
- package/chunk-A6ZPAM6Z.cjs +0 -26
- package/chunk-TPBI6TOU.js +0 -26
package/island/index.d.ts
CHANGED
|
@@ -1,9 +1,83 @@
|
|
|
1
|
-
import { I as IslandDescriptor } from '../
|
|
2
|
-
export { V as ViewAdapter, a as ViewLayerDefinition, b as ViewLayerToken, r as resolveViewAdapter, v as viewAdapterOf } from '../view-adapter-CSPKr2xy.js';
|
|
1
|
+
import { I as IslandDescriptor } from '../descriptor-DZDNH7R5.js';
|
|
3
2
|
import { Container } from '@foxford/ioc';
|
|
4
|
-
import {
|
|
3
|
+
import { R as RequestContext } from '../types-COwVwgzE.js';
|
|
4
|
+
export { A as AppRoute, R as RouteDeclaration, a as RouteMatch, m as matchRoute, n as normalizeRoutes, p as pathPattern } from '../routes-CWpIqVAM.js';
|
|
5
|
+
export { R as RenderIslandOptions, c as RenderTree, S as ServerRender, d as ServerRenderResult, V as ViewAdapter, a as ViewLayerDefinition, b as ViewLayerToken, r as resolveViewAdapter, v as viewAdapterOf } from '../view-adapter-CPI8056c.js';
|
|
5
6
|
import '../define-slot-Dil8Kr1A.js';
|
|
6
7
|
|
|
8
|
+
/** Вклад приложения в `<head>` документа. */
|
|
9
|
+
interface DocumentHead {
|
|
10
|
+
title?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
keywords?: string;
|
|
13
|
+
/** При отсутствии хост берёт `title`. */
|
|
14
|
+
ogTitle?: string;
|
|
15
|
+
/** При отсутствии хост берёт `description`. */
|
|
16
|
+
ogDescription?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Что приложение сообщает хосту про страницу, которую оно нарисовало. */
|
|
19
|
+
interface DocumentContribution {
|
|
20
|
+
head?: DocumentHead;
|
|
21
|
+
/**
|
|
22
|
+
* Исход запроса статусом HTTP; не объявлен — `200`.
|
|
23
|
+
*
|
|
24
|
+
* Статусом, а не набором признаков вроде «страницы нет»: исход у запроса один, а признаки
|
|
25
|
+
* позволяют объявить сразу два взаимоисключающих. Тем же статусом исход едет по сети,
|
|
26
|
+
* поэтому у обоих транспортов дискриминатор общий.
|
|
27
|
+
*
|
|
28
|
+
* Нужен отдельно от исключения: `throw` означает, что приложение упало, и хост рисует
|
|
29
|
+
* плашку вместо него. Приложение, занявшее путь по префиксу (`/legal/*`), обязано уметь
|
|
30
|
+
* сказать `404` про несуществующий адрес внутри своего пространства.
|
|
31
|
+
*/
|
|
32
|
+
status?: number;
|
|
33
|
+
/** Адрес для 3xx. Объявлен без статуса — исход `302`. */
|
|
34
|
+
redirect?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* VM, которой есть что сказать про документ.
|
|
38
|
+
*
|
|
39
|
+
* Метод СИНХРОННЫЙ, и это не ограничение, а разделение обязанностей: ходит в ручки активация,
|
|
40
|
+
* а здесь остаётся проекция уже посчитанного состояния. Приложению, которому «есть ли такая
|
|
41
|
+
* страница» известно только от бэкенда, второй поход не нужен — активация ждётся ДО сбора
|
|
42
|
+
* вклада, поэтому к моменту вызова ответ уже лежит в состоянии VM:
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* async activate(route: IslandRoute) {
|
|
46
|
+
* this.state.set(await loadPage(this.service, route.pathname)) // сюда ходим
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* describeDocument(): DocumentContribution {
|
|
50
|
+
* return this.state.get().page === null ? { status: 404 } : { head: … } // здесь только решаем
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* Разреши мы здесь асинхронность — у рендера появилась бы вторая фаза походов в сеть, а логика
|
|
55
|
+
* загрузки разъехалась бы по двум местам. Ровно от этого избавлялись, убирая `load`.
|
|
56
|
+
*/
|
|
57
|
+
interface DocumentSource {
|
|
58
|
+
describeDocument(): DocumentContribution;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Реализует ли объект {@link DocumentSource}.
|
|
62
|
+
*
|
|
63
|
+
* @param value - Проверяемое значение
|
|
64
|
+
*/
|
|
65
|
+
declare function isDocumentSource(value: unknown): value is DocumentSource;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Адрес, по которому рендерится остров.
|
|
69
|
+
*
|
|
70
|
+
* Приходит АРГУМЕНТОМ активации, а не через контейнер: контейнер живёт и на клиенте, где
|
|
71
|
+
* адреса в нём нет, и юнит, потребовавший его через `requires`, упал бы при резолве.
|
|
72
|
+
*/
|
|
73
|
+
interface IslandRoute {
|
|
74
|
+
/** Путь без search, с ведущим слэшем: `/legal/general`. */
|
|
75
|
+
pathname: string;
|
|
76
|
+
/** Строка запроса без `?`; пустая, если её нет. */
|
|
77
|
+
search: string;
|
|
78
|
+
/** Параметры, выделенные маршрутом хоста. */
|
|
79
|
+
params: Record<string, string>;
|
|
80
|
+
}
|
|
7
81
|
/** Опции сборки контейнера острова. */
|
|
8
82
|
interface CreateIslandContainerOptions {
|
|
9
83
|
/**
|
|
@@ -42,14 +116,36 @@ declare function createIslandContainer(descriptor: IslandDescriptor, options?: C
|
|
|
42
116
|
*/
|
|
43
117
|
declare function hydrateIslandState(container: Container, descriptor: IslandDescriptor, denState: Record<string, unknown>): void;
|
|
44
118
|
/**
|
|
45
|
-
* Активирует VM
|
|
46
|
-
*
|
|
47
|
-
*
|
|
119
|
+
* Активирует VM острова: каждая считает своё состояние по адресу.
|
|
120
|
+
*
|
|
121
|
+
* Параллельно, потому что VM одного острова друг о друге не знают; ждём все — состояние
|
|
122
|
+
* нужно целиком, а не по частям.
|
|
123
|
+
*
|
|
124
|
+
* @param container - Контейнер острова
|
|
125
|
+
* @param descriptor - Дескриптор острова
|
|
126
|
+
* @param route - Адрес, по которому рендерится остров
|
|
127
|
+
*/
|
|
128
|
+
declare function activateIslandViewModels(container: Container, descriptor: IslandDescriptor, route?: IslandRoute): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Снимает состояние VM острова — операция, обратная {@link hydrateIslandState}.
|
|
48
131
|
*
|
|
49
132
|
* @param container - Контейнер острова
|
|
50
133
|
* @param descriptor - Дескриптор острова
|
|
134
|
+
* @returns Состояние по ключам `token.description`
|
|
51
135
|
*/
|
|
52
|
-
declare function
|
|
136
|
+
declare function collectIslandState(container: Container, descriptor: IslandDescriptor): Record<string, unknown>;
|
|
137
|
+
/**
|
|
138
|
+
* Собирает вклад острова в документ по всем его VM.
|
|
139
|
+
*
|
|
140
|
+
* Правила слияния два, и они следуют из смысла полей. `head` — набор полей, поэтому
|
|
141
|
+
* объявленное позже уточняет объявленное раньше. Исход (`status` вместе с `redirect`) —
|
|
142
|
+
* ОДНО решение, а не набор: побеждает первое объявленное, и берётся оно целиком, иначе
|
|
143
|
+
* статус одной VM склеился бы с адресом другой.
|
|
144
|
+
*
|
|
145
|
+
* @param container - Контейнер острова
|
|
146
|
+
* @param descriptor - Дескриптор острова
|
|
147
|
+
*/
|
|
148
|
+
declare function collectDocumentContribution(container: Container, descriptor: IslandDescriptor): DocumentContribution;
|
|
53
149
|
/**
|
|
54
150
|
* Поднимает единицы, объявленные `eager`, — те, что обязаны жить с загрузки, а не с первого
|
|
55
151
|
* резолва.
|
|
@@ -57,14 +153,65 @@ declare function activateIslandViewModels(container: Container, descriptor: Isla
|
|
|
57
153
|
* Отдельным шагом, а не внутри сборки контейнера: контейнер строится и на сервере (в vike —
|
|
58
154
|
* прямо в `renderToString`), а сервер живёт один рендер, и поднимать там WS-подписку или
|
|
59
155
|
* прогрев кэша незачем — открытый сокет на каждый рендер это не «eager», это утечка. Зовёт
|
|
60
|
-
* адаптер в точке монтирования, которой на сервере просто нет;
|
|
61
|
-
*
|
|
156
|
+
* адаптер в точке монтирования, которой на сервере просто нет; серверное состояние приложения
|
|
157
|
+
* считает активация VM.
|
|
62
158
|
*
|
|
63
159
|
* @param container - Контейнер острова
|
|
64
160
|
* @param descriptor - Дескриптор острова
|
|
65
161
|
*/
|
|
66
162
|
declare function resolveEagerUnits(container: Container, descriptor: IslandDescriptor): void;
|
|
67
163
|
|
|
164
|
+
/** Запрос на рендер приложения. */
|
|
165
|
+
interface RenderAppRequest {
|
|
166
|
+
/** Путь без search, с ведущим слэшем: `/legal/general`. */
|
|
167
|
+
pathname: string;
|
|
168
|
+
/** Строка запроса без `?`. */
|
|
169
|
+
search?: string;
|
|
170
|
+
/** Параметры, выделенные маршрутом хоста. */
|
|
171
|
+
params?: Record<string, string>;
|
|
172
|
+
/**
|
|
173
|
+
* Реквизиты запроса браузера — заголовки и куки. Кладутся в контейнер под
|
|
174
|
+
* `RequestContextToken`, поэтому приложение читает их через `requires`, одинаково у обоих
|
|
175
|
+
* транспортов: в процессе хоста их даёт хост, за сетью — сетевой рантайм из `X-Forwarded-*`.
|
|
176
|
+
*/
|
|
177
|
+
context?: RequestContext;
|
|
178
|
+
/**
|
|
179
|
+
* Родитель контейнера приложения: через него приезжает всё, что настроил исполнитель, —
|
|
180
|
+
* логгер, конфиг HTTP, выданные возможности. По умолчанию — app-scope контейнер.
|
|
181
|
+
*/
|
|
182
|
+
parent?: Container;
|
|
183
|
+
/** Внешние заполнители слотов; для ядра непрозрачны. */
|
|
184
|
+
slots?: Record<string, unknown>;
|
|
185
|
+
}
|
|
186
|
+
/** Ответ приложения: всё, что хосту нужно, чтобы собрать документ и ответить. */
|
|
187
|
+
interface RenderAppResult {
|
|
188
|
+
/**
|
|
189
|
+
* Исход запроса статусом HTTP. `200` — вот страница; всё остальное значит, что страницы
|
|
190
|
+
* по этому адресу нет, и тело такого ответа выбирает хост: страница ошибки у портала одна
|
|
191
|
+
* на все приложения.
|
|
192
|
+
*/
|
|
193
|
+
status: number;
|
|
194
|
+
/** Разметка приложения; пустая при любом исходе, кроме `200`. */
|
|
195
|
+
html: string;
|
|
196
|
+
/** Состояние VM по ключам `token.description` — им гидрируется остров в браузере. */
|
|
197
|
+
state: Record<string, unknown>;
|
|
198
|
+
/** Вклад в `<head>` данными. */
|
|
199
|
+
head?: DocumentHead;
|
|
200
|
+
/** Вклад в `<head>` готовой разметкой — стили, собранные во время рендера. */
|
|
201
|
+
headHtml?: string;
|
|
202
|
+
/** Адрес для 3xx. */
|
|
203
|
+
redirect?: string;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Рисует приложение по адресу и отдаёт разметку вместе с состоянием.
|
|
207
|
+
*
|
|
208
|
+
* @param descriptor - Дескриптор приложения
|
|
209
|
+
* @param request - Адрес, родительский контейнер и заполнители слотов
|
|
210
|
+
* @returns Разметка, состояние и вклад в документ
|
|
211
|
+
* @throws Если контейнер приложения не собрался — исполнитель решает, чем это показать
|
|
212
|
+
*/
|
|
213
|
+
declare function renderApp(descriptor: IslandDescriptor, request: RenderAppRequest): Promise<RenderAppResult>;
|
|
214
|
+
|
|
68
215
|
/** Запись живого острова: имя + фактический состав его контейнера. */
|
|
69
216
|
interface IslandRecord {
|
|
70
217
|
/** Уникальный id инстанса острова (островов одного приложения может быть несколько). */
|
|
@@ -122,75 +269,4 @@ declare function getAppContainer(): Container;
|
|
|
122
269
|
*/
|
|
123
270
|
declare function slotAddress(app: string, slot: string): string;
|
|
124
271
|
|
|
125
|
-
|
|
126
|
-
interface RouteDeclaration {
|
|
127
|
-
/** Regex-источник адреса (`'^/cabinet/billing'`, `'.*'`). */
|
|
128
|
-
source: string;
|
|
129
|
-
/**
|
|
130
|
-
* Guard-ы этого маршрута — поверх guard-ов приложения и хоста. Пусто — маршрут открыт
|
|
131
|
-
* настолько, насколько открыто само приложение.
|
|
132
|
-
*/
|
|
133
|
-
guards?: ReadonlyArray<GuardDefinition>;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Заявка на адрес: голая строка — публичный маршрут, объект — маршрут со своей политикой.
|
|
137
|
-
* Обе формы лежат в одном массиве, и порядок значим так же, как раньше: хост раздаёт путь
|
|
138
|
-
* первому заявившему, поэтому частные записи ставятся выше общих.
|
|
139
|
-
*/
|
|
140
|
-
type AppRoute = string | RouteDeclaration;
|
|
141
|
-
/**
|
|
142
|
-
* Приводит заявки к одной форме — чтобы хост и dev-инструменты не разбирали union каждый сам.
|
|
143
|
-
*
|
|
144
|
-
* @param routes - Заявки приложения (строки и/или записи)
|
|
145
|
-
* @returns Записи маршрутов в порядке объявления
|
|
146
|
-
*/
|
|
147
|
-
declare function normalizeRoutes(routes?: ReadonlyArray<AppRoute>): RouteDeclaration[];
|
|
148
|
-
|
|
149
|
-
/** Что приложение отдало серверным рендером. */
|
|
150
|
-
interface ServerRenderResult {
|
|
151
|
-
/** Разметка дерева — результат переданного рендера. */
|
|
152
|
-
html: string;
|
|
153
|
-
/**
|
|
154
|
-
* Вклад в `<head>` ГОТОВОЙ разметкой: то, что рождается только во время рендера и
|
|
155
|
-
* данными не описывается.
|
|
156
|
-
*
|
|
157
|
-
* Таковы стили CSS-in-JS: стороннее представление у них одно — тег со служебными
|
|
158
|
-
* атрибутами, по которым клиентский рантайм узнаёт свои правила. Разобрав его на поля,
|
|
159
|
-
* потеряешь регидратацию, и стили впрыснутся вторым экземпляром. Поэтому вклад в документ
|
|
160
|
-
* ДАННЫМИ (`DocumentHead` у хостов) остаётся отдельным каналом, а это — разметка.
|
|
161
|
-
*
|
|
162
|
-
* Разметка приезжает в документ хоста ДОСЛОВНО, без экранирования — иначе служебные
|
|
163
|
-
* атрибуты не пережили бы вставку. Отвечает за содержимое приложение: у сетевого
|
|
164
|
-
* транспорта строка приходит из чужого процесса, и хост её не разбирает.
|
|
165
|
-
*/
|
|
166
|
-
head?: string;
|
|
167
|
-
}
|
|
168
|
-
/** Рендер дерева в разметку. Его даёт тот, кто серверный рендер исполняет. */
|
|
169
|
-
type RenderTree<Tree = unknown> = (tree: Tree) => string;
|
|
170
|
-
/**
|
|
171
|
-
* Серверный рендер приложения: приложению дают его дерево и рендер, оно возвращает
|
|
172
|
-
* разметку и свой вклад в документ.
|
|
173
|
-
*
|
|
174
|
-
* Рендер приходит АРГУМЕНТОМ, потому что приложение им не владеет: страницу собирает тот,
|
|
175
|
-
* кто держит документ, и приложений на ней может быть несколько. Отдав рендер аргументом,
|
|
176
|
-
* приложение получает обычную функцию — состояние прохода живёт локальной переменной и
|
|
177
|
-
* между запросами не утекает.
|
|
178
|
-
*
|
|
179
|
-
* Подпись одна на оба транспорта, и в этом смысл: у приложения в процессе хоста рендер
|
|
180
|
-
* передаёт хост, у сетевого — его собственный сервер, а `html` и `head` едут ответом.
|
|
181
|
-
* Перевод приложения между транспортами эту часть не задевает.
|
|
182
|
-
*
|
|
183
|
-
* `Tree` — дерево view-фреймворка, для ядра непрозрачное. View-адаптер сужает параметр
|
|
184
|
-
* до своего типа (`ReactServerRender` в `@foxford/den-react`), и приложение пишет проход
|
|
185
|
-
* в терминах СВОЕГО фреймворка.
|
|
186
|
-
*
|
|
187
|
-
* @example
|
|
188
|
-
* serverRender: (tree, render) => {
|
|
189
|
-
* const sheet = new ServerStyleSheet()
|
|
190
|
-
*
|
|
191
|
-
* return { head: sheet.getStyleTags(), html: render(sheet.collectStyles(tree)) }
|
|
192
|
-
* }
|
|
193
|
-
*/
|
|
194
|
-
type ServerRender<Tree = unknown> = (tree: Tree, render: RenderTree<Tree>) => ServerRenderResult;
|
|
195
|
-
|
|
196
|
-
export { type AppRoute, type CreateIslandContainerOptions, IslandDescriptor, type IslandRecord, type RenderTree, type RouteDeclaration, type ServerRender, type ServerRenderResult, activateIslandViewModels, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, normalizeRoutes, registerIsland, resolveEagerUnits, slotAddress, subscribeIslands };
|
|
272
|
+
export { type CreateIslandContainerOptions, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
|
package/island/index.js
CHANGED
|
@@ -1,94 +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-7M5OAOQ4.js";
|
|
1
15
|
import {
|
|
2
16
|
ViewLayerToken,
|
|
3
17
|
resolveViewAdapter,
|
|
4
18
|
viewAdapterOf
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import
|
|
7
|
-
LoggerToken,
|
|
8
|
-
logger,
|
|
9
|
-
unitLogger
|
|
10
|
-
} from "../chunk-TPBI6TOU.js";
|
|
19
|
+
} from "../chunk-E23E2VUC.js";
|
|
20
|
+
import "../chunk-KJ6NDOOL.js";
|
|
11
21
|
import {
|
|
12
22
|
__spreadValues
|
|
13
23
|
} from "../chunk-HJO26HIQ.js";
|
|
14
24
|
|
|
15
|
-
// src/island/lifecycle.ts
|
|
16
|
-
import { log } from "@foxford/logger";
|
|
17
|
-
|
|
18
|
-
// src/island/runtime.ts
|
|
19
|
-
import { Container } from "@foxford/ioc";
|
|
20
|
-
var appContainer = null;
|
|
21
|
-
function getAppContainer() {
|
|
22
|
-
var _a;
|
|
23
|
-
if (appContainer === null) {
|
|
24
|
-
appContainer = new Container();
|
|
25
|
-
((_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");
|
|
26
|
-
}
|
|
27
|
-
return appContainer;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// src/island/lifecycle.ts
|
|
31
|
-
function createIslandContainer(descriptor, options = {}) {
|
|
32
|
-
var _a, _b, _c, _d, _e;
|
|
33
|
-
const { parent = getAppContainer() } = options;
|
|
34
|
-
const appLog = (_b = log.getLogger((_a = descriptor.name) != null ? _a : "app")) != null ? _b : logger;
|
|
35
|
-
const islandLog = (_c = appLog.getLogger("client")) != null ? _c : appLog;
|
|
36
|
-
try {
|
|
37
|
-
const container = parent.createChild();
|
|
38
|
-
container.bind(LoggerToken).toValue(islandLog);
|
|
39
|
-
descriptor.viewLayer.register(container);
|
|
40
|
-
const viewModels = (_d = descriptor.viewModels) != null ? _d : [];
|
|
41
|
-
const slots = Object.entries((_e = descriptor.slots) != null ? _e : {});
|
|
42
|
-
islandLog.info(
|
|
43
|
-
`\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})`
|
|
44
|
-
);
|
|
45
|
-
for (const unit of descriptor.units) {
|
|
46
|
-
unit.register(container);
|
|
47
|
-
}
|
|
48
|
-
for (const vm of viewModels) {
|
|
49
|
-
vm.register(container);
|
|
50
|
-
}
|
|
51
|
-
for (const [name, slot] of slots) {
|
|
52
|
-
slot.register(container, name);
|
|
53
|
-
}
|
|
54
|
-
return container;
|
|
55
|
-
} catch (error) {
|
|
56
|
-
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);
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function hydrateIslandState(container, descriptor, denState) {
|
|
61
|
-
var _a, _b;
|
|
62
|
-
for (const vm of (_a = descriptor.viewModels) != null ? _a : []) {
|
|
63
|
-
const instance = container.resolve(vm.token);
|
|
64
|
-
const saved = denState[vm.token.description];
|
|
65
|
-
if (saved !== void 0 && typeof ((_b = instance.state) == null ? void 0 : _b.set) === "function") {
|
|
66
|
-
unitLogger(container, vm.token.description).debug("\u0433\u0438\u0434\u0440\u0430\u0446\u0438\u044F VM \u0438\u0437 denState");
|
|
67
|
-
instance.state.set(saved);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
function activateIslandViewModels(container, descriptor) {
|
|
72
|
-
var _a, _b;
|
|
73
|
-
for (const vm of (_a = descriptor.viewModels) != null ? _a : []) {
|
|
74
|
-
const instance = container.resolve(vm.token);
|
|
75
|
-
unitLogger(container, vm.token.description).debug("\u0430\u043A\u0442\u0438\u0432\u0430\u0446\u0438\u044F VM (\u0431\u0435\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u043D\u043E\u0433\u043E denState)");
|
|
76
|
-
void ((_b = instance.activate) == null ? void 0 : _b.call(instance));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function resolveEagerUnits(container, descriptor) {
|
|
80
|
-
var _a;
|
|
81
|
-
for (const token of (_a = descriptor.eager) != null ? _a : []) {
|
|
82
|
-
const instance = container.resolve(token);
|
|
83
|
-
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");
|
|
84
|
-
if (instance instanceof Promise) {
|
|
85
|
-
instance.catch(
|
|
86
|
-
(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)
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
25
|
// src/island/slot-address.ts
|
|
93
26
|
function slotAddress(app, slot) {
|
|
94
27
|
return `${app}:${slot}`;
|
|
@@ -133,21 +66,22 @@ function describeIsland(descriptor, denState) {
|
|
|
133
66
|
viewModels: ((_b = descriptor.viewModels) != null ? _b : []).map((vm) => vm.token.description)
|
|
134
67
|
};
|
|
135
68
|
}
|
|
136
|
-
|
|
137
|
-
// src/island/routes.ts
|
|
138
|
-
function normalizeRoutes(routes = []) {
|
|
139
|
-
return routes.map((route) => typeof route === "string" ? { source: route } : route);
|
|
140
|
-
}
|
|
141
69
|
export {
|
|
142
70
|
ViewLayerToken,
|
|
143
71
|
activateIslandViewModels,
|
|
72
|
+
collectDocumentContribution,
|
|
73
|
+
collectIslandState,
|
|
144
74
|
createIslandContainer,
|
|
145
75
|
describeIsland,
|
|
146
76
|
getAppContainer,
|
|
147
77
|
getIslandsSnapshot,
|
|
148
78
|
hydrateIslandState,
|
|
79
|
+
isDocumentSource,
|
|
80
|
+
matchRoute,
|
|
149
81
|
normalizeRoutes,
|
|
82
|
+
pathPattern,
|
|
150
83
|
registerIsland,
|
|
84
|
+
renderApp,
|
|
151
85
|
resolveEagerUnits,
|
|
152
86
|
resolveViewAdapter,
|
|
153
87
|
slotAddress,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foxford/den",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Den — декларативный метафреймворк Foxford (core runtime)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"foxford",
|
|
@@ -19,9 +19,13 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"type": "module",
|
|
22
|
+
"bin": {
|
|
23
|
+
"den-serve": "./den-serve.js"
|
|
24
|
+
},
|
|
22
25
|
"dependencies": {
|
|
23
|
-
"
|
|
24
|
-
"@foxford/logger": "^1.2.2"
|
|
26
|
+
"fastify": "^5.11.2",
|
|
27
|
+
"@foxford/logger": "^1.2.2",
|
|
28
|
+
"@foxford/ioc": "^1.1.2"
|
|
25
29
|
},
|
|
26
30
|
"main": "./index.cjs",
|
|
27
31
|
"module": "./index.js",
|
|
@@ -45,6 +49,11 @@
|
|
|
45
49
|
"import": "./island/index.js",
|
|
46
50
|
"require": "./island/index.cjs",
|
|
47
51
|
"types": "./island/index.d.ts"
|
|
52
|
+
},
|
|
53
|
+
"./serve": {
|
|
54
|
+
"import": "./serve/index.js",
|
|
55
|
+
"require": "./serve/index.cjs",
|
|
56
|
+
"types": "./serve/index.d.ts"
|
|
48
57
|
}
|
|
49
58
|
},
|
|
50
59
|
"files": [
|
|
@@ -53,16 +62,18 @@
|
|
|
53
62
|
"adapter.d.cts",
|
|
54
63
|
"adapter.d.ts",
|
|
55
64
|
"adapter.js",
|
|
56
|
-
"chunk-
|
|
57
|
-
"chunk-
|
|
65
|
+
"chunk-6ZFVV3AV.cjs",
|
|
66
|
+
"chunk-7M5OAOQ4.js",
|
|
58
67
|
"chunk-C7BM4DGX.cjs",
|
|
68
|
+
"chunk-CZVYVNLI.cjs",
|
|
69
|
+
"chunk-E23E2VUC.js",
|
|
59
70
|
"chunk-HJO26HIQ.js",
|
|
60
|
-
"chunk-
|
|
61
|
-
"chunk-
|
|
62
|
-
"chunk-
|
|
71
|
+
"chunk-KJ6NDOOL.js",
|
|
72
|
+
"chunk-P4QJCHMM.cjs",
|
|
73
|
+
"chunk-PZVUKTZG.js",
|
|
63
74
|
"chunk-WBHHHICS.js",
|
|
64
|
-
"chunk-WRQC6BVJ.js",
|
|
65
75
|
"chunk-XHYR3SGG.cjs",
|
|
76
|
+
"chunk-XOJ2VWX4.cjs",
|
|
66
77
|
"define-repository-Ca62_YCy.d.ts",
|
|
67
78
|
"define-repository-MDEWHqM7.d.cts",
|
|
68
79
|
"define-slot-BKvArA02.d.cts",
|
|
@@ -71,17 +82,23 @@
|
|
|
71
82
|
"define.d.cts",
|
|
72
83
|
"define.d.ts",
|
|
73
84
|
"define.js",
|
|
85
|
+
"den-serve.js",
|
|
86
|
+
"descriptor-DZDNH7R5.d.ts",
|
|
87
|
+
"descriptor-h0JE9jFx.d.cts",
|
|
74
88
|
"index.cjs",
|
|
75
89
|
"index.d.cts",
|
|
76
90
|
"index.d.ts",
|
|
77
91
|
"index.js",
|
|
78
92
|
"island",
|
|
79
93
|
"package.json",
|
|
94
|
+
"routes-BqIoqbt1.d.cts",
|
|
95
|
+
"routes-CWpIqVAM.d.ts",
|
|
96
|
+
"serve",
|
|
80
97
|
"types-COwVwgzE.d.cts",
|
|
81
98
|
"types-COwVwgzE.d.ts",
|
|
82
|
-
"view-adapter-
|
|
83
|
-
"view-adapter-
|
|
99
|
+
"view-adapter-CPI8056c.d.ts",
|
|
100
|
+
"view-adapter-D2597RJZ.d.cts"
|
|
84
101
|
],
|
|
85
|
-
"sha": "
|
|
102
|
+
"sha": "46356bf",
|
|
86
103
|
"scripts": {}
|
|
87
104
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { g as GuardDefinition } from './types-COwVwgzE.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,64 @@
|
|
|
1
|
+
import { g as GuardDefinition } from './types-COwVwgzE.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 };
|