@foxford/den 2.0.0 → 3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxford/den",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Den — декларативный метафреймворк Foxford (core runtime)",
5
5
  "keywords": [
6
6
  "foxford",
@@ -56,17 +56,17 @@
56
56
  "chunk-5DJZZML5.cjs",
57
57
  "chunk-A6ZPAM6Z.cjs",
58
58
  "chunk-C7BM4DGX.cjs",
59
+ "chunk-E23E2VUC.js",
59
60
  "chunk-HJO26HIQ.js",
60
- "chunk-LJE7K7VD.cjs",
61
- "chunk-TCYGDT2A.js",
62
61
  "chunk-TPBI6TOU.js",
63
62
  "chunk-WBHHHICS.js",
64
63
  "chunk-WRQC6BVJ.js",
65
64
  "chunk-XHYR3SGG.cjs",
66
- "define-repository-DZg34Tb3.d.ts",
67
- "define-repository-OpMj-Q9O.d.cts",
68
- "define-slot-ESU7FR9O.d.ts",
69
- "define-slot-RfgqKa6n.d.cts",
65
+ "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",
70
70
  "define.cjs",
71
71
  "define.d.cts",
72
72
  "define.d.ts",
@@ -77,11 +77,11 @@
77
77
  "index.js",
78
78
  "island",
79
79
  "package.json",
80
- "types-Deyolj1-.d.cts",
81
- "types-Deyolj1-.d.ts",
82
- "view-adapter-CL0vZ-rv.d.ts",
83
- "view-adapter-vwO-7b1t.d.cts"
80
+ "types-COwVwgzE.d.cts",
81
+ "types-COwVwgzE.d.ts",
82
+ "view-adapter-BvDC5O4y.d.cts",
83
+ "view-adapter-DtNgyjIf.d.ts"
84
84
  ],
85
- "sha": "d3153b1",
85
+ "sha": "ba6123d",
86
86
  "scripts": {}
87
87
  }
@@ -169,36 +169,6 @@ interface DefineGuardOptions<Deps extends readonly Token<unknown>[] = readonly [
169
169
  */
170
170
  check?: (params: Record<string, string>) => GuardResult | Promise<GuardResult>;
171
171
  }
172
- /**
173
- * Конфигурация приложения, регистрируемая в IoC-контейнере.
174
- */
175
- interface HostConfig {
176
- /** Сервисы, инициализируемые немедленно при старте */
177
- readonly eager: readonly Token<unknown>[];
178
- /** Сервисы, инициализируемые лениво (по требованию) */
179
- readonly lazy: readonly Token<unknown>[];
180
- /** Глобальные guards, применяемые ко всем страницам */
181
- readonly guards: readonly Token<Guard>[];
182
- }
183
- /**
184
- * HostDefinition — результат вызова defineHost.
185
- * Декларирует конфигурацию приложения: eager/lazy сервисы, глобальные guards.
186
- */
187
- interface HostDefinition extends Definition<HostConfig> {
188
- /** Дискриминатор типа */
189
- readonly __type: 'host';
190
- /** Метаданные определения */
191
- readonly __meta: {
192
- /** Приложение не имеет requires-зависимостей */
193
- readonly requires: readonly [];
194
- /** Eager-сервисы */
195
- readonly eager: readonly Token<unknown>[];
196
- /** Lazy-сервисы */
197
- readonly lazy: readonly Token<unknown>[];
198
- /** Глобальные guards */
199
- readonly guards: readonly Token<Guard>[];
200
- };
201
- }
202
172
  /**
203
173
  * RepositoryDefinition — результат вызова defineRepository.
204
174
  * Описывает репозиторий уровня данных с IoC-биндингом.
@@ -220,15 +190,6 @@ interface RepositoryDefinition<T, Deps extends readonly Token<unknown>[]> extend
220
190
  readonly scope: 'app' | 'request';
221
191
  };
222
192
  }
223
- /** Опции для defineHost */
224
- interface DefineHostOptions {
225
- /** Сервисы, инициализируемые немедленно при старте приложения */
226
- readonly eager?: readonly Token<unknown>[];
227
- /** Сервисы, инициализируемые лениво (по требованию) */
228
- readonly lazy?: readonly Token<unknown>[];
229
- /** Глобальные guards, применяемые ко всем страницам */
230
- readonly guards?: readonly Token<Guard>[];
231
- }
232
193
  /**
233
194
  * Опции для defineRepository
234
195
  * @template Deps - Кортеж токенов зависимостей
@@ -527,14 +488,28 @@ interface RequestContext {
527
488
  }
528
489
  /**
529
490
  * Результат проверки guard.
530
- * - `{ allowed: true }` — переход разрешён
531
- * - `{ allowed: false, redirect: string }` — переход запрещён, перенаправить по redirect
491
+ *
492
+ * - `{ allowed: true }` — доступ разрешён;
493
+ * - `{ allowed: false, redirect }` — запрещён, увести по адресу;
494
+ * - `{ allowed: false }` — запрещён, и вести некуда: хост отвечает отказом (403).
495
+ *
496
+ * `redirect` необязателен именно ради второго случая: «нельзя, но формы входа для этого нет»
497
+ * иначе типом не выражалось, и приходилось выдумывать адрес.
498
+ *
499
+ * `escalate` касается только приложений, ВСТРОЕННЫХ в чужое место: по умолчанию отказ убирает
500
+ * из дерева их одних, а страница живёт. Поднять отказ до всей страницы может понадобиться —
501
+ * скажем, сессия протухла, и показывать соседей смысла нет, — но это исключение, поэтому
502
+ * заявляется явно. У владельца адреса флаг ничего не меняет: его отказ и так отказ странице.
503
+ * Последнее слово за хостом: он владеет документом и решает, чем отвечать.
532
504
  */
533
505
  type GuardResult = {
534
506
  allowed: true;
535
507
  } | {
536
508
  allowed: false;
537
- redirect: string;
509
+ /** Куда увести. Нет — хост отвечает отказом сам (403). */
510
+ redirect?: string;
511
+ /** Поднять отказ встроенного приложения до всей страницы. */
512
+ escalate?: boolean;
538
513
  };
539
514
  /**
540
515
  * Guard — функция проверки доступа к странице.
@@ -799,4 +774,4 @@ declare const ActivatableToken: Token<Activatable>;
799
774
  */
800
775
  declare const RequestContextToken: Token<RequestContext>;
801
776
 
802
- export { type ActionGuard as A, GuardRunnerToken as B, type Core as C, type Disposable as D, type ExtensionConfig as E, NavigationManagerToken as F, type GuardRunner as G, type HostConfig as H, type RepositoryDefinition as I, RequestContextToken as J, type ServiceDefinition as K, type LeaveGuardResult as L, type SlotEntry as M, type NavigationManager as N, SlotRegistry as O, SlotRegistryToken as P, StateCache as Q, type RequestContext as R, type StateSerializer as S, type StateParticipant as T, StateRegistry as U, StateRegistryToken as V, StateSerializerToken as W, type ViewModelDefinition as X, type ContainerManager as a, type Guard as b, type GuardResult as c, type NavigationEntry as d, type NavigationInterceptor as e, type DefineGuardOptions as f, type GuardDefinition as g, type DefineHostOptions as h, type HostDefinition as i, type DefineExtensionOptions as j, type ExtensionDefinition as k, type DefineActionGuardOptions as l, type ActionGuardDefinition as m, type LayoutConfig as n, type DefineLayoutOptions as o, type LayoutDefinition as p, type LayerConfig as q, type DefineLayerOptions as r, type LayerDefinition as s, type Activatable as t, ActivatableToken as u, ContainerManagerToken as v, type DefineRepositoryOptions as w, type DefineServiceOptions as x, type DefineViewModelOptions as y, type Definition as z };
777
+ export { type ActionGuard as A, type RepositoryDefinition as B, type Core as C, type Disposable as D, type ExtensionConfig as E, RequestContextToken as F, type GuardRunner as G, type ServiceDefinition as H, type SlotEntry as I, SlotRegistry as J, SlotRegistryToken as K, type LeaveGuardResult as L, StateCache as M, type NavigationManager as N, type StateParticipant as O, StateRegistry as P, StateRegistryToken as Q, type RequestContext as R, type StateSerializer as S, StateSerializerToken as T, type ViewModelDefinition as V, type ContainerManager as a, type Guard as b, type GuardResult as c, type NavigationEntry as d, type NavigationInterceptor as e, type DefineGuardOptions as f, type GuardDefinition as g, type DefineExtensionOptions as h, type ExtensionDefinition as i, type DefineActionGuardOptions as j, type ActionGuardDefinition as k, type LayoutConfig as l, type DefineLayoutOptions as m, type LayoutDefinition as n, type LayerConfig as o, type DefineLayerOptions as p, type LayerDefinition as q, type Activatable as r, ActivatableToken as s, ContainerManagerToken as t, type DefineRepositoryOptions as u, type DefineServiceOptions as v, type DefineViewModelOptions as w, type Definition as x, GuardRunnerToken as y, NavigationManagerToken as z };
@@ -169,36 +169,6 @@ interface DefineGuardOptions<Deps extends readonly Token<unknown>[] = readonly [
169
169
  */
170
170
  check?: (params: Record<string, string>) => GuardResult | Promise<GuardResult>;
171
171
  }
172
- /**
173
- * Конфигурация приложения, регистрируемая в IoC-контейнере.
174
- */
175
- interface HostConfig {
176
- /** Сервисы, инициализируемые немедленно при старте */
177
- readonly eager: readonly Token<unknown>[];
178
- /** Сервисы, инициализируемые лениво (по требованию) */
179
- readonly lazy: readonly Token<unknown>[];
180
- /** Глобальные guards, применяемые ко всем страницам */
181
- readonly guards: readonly Token<Guard>[];
182
- }
183
- /**
184
- * HostDefinition — результат вызова defineHost.
185
- * Декларирует конфигурацию приложения: eager/lazy сервисы, глобальные guards.
186
- */
187
- interface HostDefinition extends Definition<HostConfig> {
188
- /** Дискриминатор типа */
189
- readonly __type: 'host';
190
- /** Метаданные определения */
191
- readonly __meta: {
192
- /** Приложение не имеет requires-зависимостей */
193
- readonly requires: readonly [];
194
- /** Eager-сервисы */
195
- readonly eager: readonly Token<unknown>[];
196
- /** Lazy-сервисы */
197
- readonly lazy: readonly Token<unknown>[];
198
- /** Глобальные guards */
199
- readonly guards: readonly Token<Guard>[];
200
- };
201
- }
202
172
  /**
203
173
  * RepositoryDefinition — результат вызова defineRepository.
204
174
  * Описывает репозиторий уровня данных с IoC-биндингом.
@@ -220,15 +190,6 @@ interface RepositoryDefinition<T, Deps extends readonly Token<unknown>[]> extend
220
190
  readonly scope: 'app' | 'request';
221
191
  };
222
192
  }
223
- /** Опции для defineHost */
224
- interface DefineHostOptions {
225
- /** Сервисы, инициализируемые немедленно при старте приложения */
226
- readonly eager?: readonly Token<unknown>[];
227
- /** Сервисы, инициализируемые лениво (по требованию) */
228
- readonly lazy?: readonly Token<unknown>[];
229
- /** Глобальные guards, применяемые ко всем страницам */
230
- readonly guards?: readonly Token<Guard>[];
231
- }
232
193
  /**
233
194
  * Опции для defineRepository
234
195
  * @template Deps - Кортеж токенов зависимостей
@@ -527,14 +488,28 @@ interface RequestContext {
527
488
  }
528
489
  /**
529
490
  * Результат проверки guard.
530
- * - `{ allowed: true }` — переход разрешён
531
- * - `{ allowed: false, redirect: string }` — переход запрещён, перенаправить по redirect
491
+ *
492
+ * - `{ allowed: true }` — доступ разрешён;
493
+ * - `{ allowed: false, redirect }` — запрещён, увести по адресу;
494
+ * - `{ allowed: false }` — запрещён, и вести некуда: хост отвечает отказом (403).
495
+ *
496
+ * `redirect` необязателен именно ради второго случая: «нельзя, но формы входа для этого нет»
497
+ * иначе типом не выражалось, и приходилось выдумывать адрес.
498
+ *
499
+ * `escalate` касается только приложений, ВСТРОЕННЫХ в чужое место: по умолчанию отказ убирает
500
+ * из дерева их одних, а страница живёт. Поднять отказ до всей страницы может понадобиться —
501
+ * скажем, сессия протухла, и показывать соседей смысла нет, — но это исключение, поэтому
502
+ * заявляется явно. У владельца адреса флаг ничего не меняет: его отказ и так отказ странице.
503
+ * Последнее слово за хостом: он владеет документом и решает, чем отвечать.
532
504
  */
533
505
  type GuardResult = {
534
506
  allowed: true;
535
507
  } | {
536
508
  allowed: false;
537
- redirect: string;
509
+ /** Куда увести. Нет — хост отвечает отказом сам (403). */
510
+ redirect?: string;
511
+ /** Поднять отказ встроенного приложения до всей страницы. */
512
+ escalate?: boolean;
538
513
  };
539
514
  /**
540
515
  * Guard — функция проверки доступа к странице.
@@ -799,4 +774,4 @@ declare const ActivatableToken: Token<Activatable>;
799
774
  */
800
775
  declare const RequestContextToken: Token<RequestContext>;
801
776
 
802
- export { type ActionGuard as A, GuardRunnerToken as B, type Core as C, type Disposable as D, type ExtensionConfig as E, NavigationManagerToken as F, type GuardRunner as G, type HostConfig as H, type RepositoryDefinition as I, RequestContextToken as J, type ServiceDefinition as K, type LeaveGuardResult as L, type SlotEntry as M, type NavigationManager as N, SlotRegistry as O, SlotRegistryToken as P, StateCache as Q, type RequestContext as R, type StateSerializer as S, type StateParticipant as T, StateRegistry as U, StateRegistryToken as V, StateSerializerToken as W, type ViewModelDefinition as X, type ContainerManager as a, type Guard as b, type GuardResult as c, type NavigationEntry as d, type NavigationInterceptor as e, type DefineGuardOptions as f, type GuardDefinition as g, type DefineHostOptions as h, type HostDefinition as i, type DefineExtensionOptions as j, type ExtensionDefinition as k, type DefineActionGuardOptions as l, type ActionGuardDefinition as m, type LayoutConfig as n, type DefineLayoutOptions as o, type LayoutDefinition as p, type LayerConfig as q, type DefineLayerOptions as r, type LayerDefinition as s, type Activatable as t, ActivatableToken as u, ContainerManagerToken as v, type DefineRepositoryOptions as w, type DefineServiceOptions as x, type DefineViewModelOptions as y, type Definition as z };
777
+ export { type ActionGuard as A, type RepositoryDefinition as B, type Core as C, type Disposable as D, type ExtensionConfig as E, RequestContextToken as F, type GuardRunner as G, type ServiceDefinition as H, type SlotEntry as I, SlotRegistry as J, SlotRegistryToken as K, type LeaveGuardResult as L, StateCache as M, type NavigationManager as N, type StateParticipant as O, StateRegistry as P, StateRegistryToken as Q, type RequestContext as R, type StateSerializer as S, StateSerializerToken as T, type ViewModelDefinition as V, type ContainerManager as a, type Guard as b, type GuardResult as c, type NavigationEntry as d, type NavigationInterceptor as e, type DefineGuardOptions as f, type GuardDefinition as g, type DefineExtensionOptions as h, type ExtensionDefinition as i, type DefineActionGuardOptions as j, type ActionGuardDefinition as k, type LayoutConfig as l, type DefineLayoutOptions as m, type LayoutDefinition as n, type LayerConfig as o, type DefineLayerOptions as p, type LayerDefinition as q, type Activatable as r, ActivatableToken as s, ContainerManagerToken as t, type DefineRepositoryOptions as u, type DefineServiceOptions as v, type DefineViewModelOptions as w, type Definition as x, GuardRunnerToken as y, NavigationManagerToken as z };
@@ -1,5 +1,5 @@
1
- import { S as SlotDefinition } from './define-slot-RfgqKa6n.cjs';
2
- import { s as LayerDefinition, z as Definition, q as LayerConfig } from './types-Deyolj1-.cjs';
1
+ import { S as SlotDefinition } from './define-slot-BKvArA02.cjs';
2
+ import { q as LayerDefinition, x as Definition, o as LayerConfig } from './types-COwVwgzE.cjs';
3
3
  import { Token, Container } from '@foxford/ioc';
4
4
 
5
5
  /**
@@ -45,6 +45,18 @@ interface IslandDescriptor {
45
45
  * ездит через denState.
46
46
  */
47
47
  viewModels?: ReadonlyArray<Definition<unknown>>;
48
+ /**
49
+ * Единицы, которые обязаны жить с загрузки, а не с первого резолва.
50
+ *
51
+ * Регистрация ленива по природе: не спросили — не создали. Обычно это то, что нужно, но
52
+ * сервису, который сам на что-то подписывается или греет кэш, ждать первого потребителя
53
+ * незачем. Токены — из тех же `units`.
54
+ *
55
+ * Поднимаются при МОНТИРОВАНИИ острова, а не при сборке контейнера: контейнер строится и на
56
+ * сервере, а сервер живёт один рендер — открытая там WS-подписка это не «eager», а утечка.
57
+ * Серверное состояние приложения считает активация VM.
58
+ */
59
+ eager?: ReadonlyArray<Token<unknown>>;
48
60
  /**
49
61
  * Места расширения приложения: ключ — локальное имя слота, значение — `defineSlot`.
50
62
  *
@@ -55,6 +67,60 @@ interface IslandDescriptor {
55
67
  slots?: Readonly<Record<string, SlotDefinition>>;
56
68
  }
57
69
 
70
+ /** Что приложение отдало серверным рендером. */
71
+ interface ServerRenderResult {
72
+ /** Разметка дерева — результат переданного рендера. */
73
+ html: string;
74
+ /**
75
+ * Вклад в `<head>` ГОТОВОЙ разметкой: то, что рождается только во время рендера и
76
+ * данными не описывается.
77
+ *
78
+ * Таковы стили CSS-in-JS: стороннее представление у них одно — тег со служебными
79
+ * атрибутами, по которым клиентский рантайм узнаёт свои правила. Разобрав его на поля,
80
+ * потеряешь регидратацию, и стили впрыснутся вторым экземпляром. Поэтому вклад в документ
81
+ * ДАННЫМИ (`DocumentHead` у хостов) остаётся отдельным каналом, а это — разметка.
82
+ *
83
+ * Разметка приезжает в документ хоста ДОСЛОВНО, без экранирования — иначе служебные
84
+ * атрибуты не пережили бы вставку. Отвечает за содержимое приложение: у сетевого
85
+ * транспорта строка приходит из чужого процесса, и хост её не разбирает.
86
+ */
87
+ head?: string;
88
+ }
89
+ /** Рендер дерева в разметку. Его даёт тот, кто серверный рендер исполняет. */
90
+ type RenderTree<Tree = unknown> = (tree: Tree) => string;
91
+ /**
92
+ * Серверный рендер приложения: приложению дают его дерево и рендер, оно возвращает
93
+ * разметку и свой вклад в документ.
94
+ *
95
+ * Рендер приходит АРГУМЕНТОМ, потому что приложение им не владеет: страницу собирает тот,
96
+ * кто держит документ, и приложений на ней может быть несколько. Отдав рендер аргументом,
97
+ * приложение получает обычную функцию — состояние прохода живёт локальной переменной и
98
+ * между запросами не утекает.
99
+ *
100
+ * Подпись одна на оба транспорта, и в этом смысл: у приложения в процессе хоста рендер
101
+ * передаёт хост, у сетевого — его собственный сервер, а `html` и `head` едут ответом.
102
+ * Перевод приложения между транспортами эту часть не задевает.
103
+ *
104
+ * `Tree` — дерево view-фреймворка, для ядра непрозрачное. View-адаптер сужает параметр
105
+ * до своего типа (`ReactServerRender` в `@foxford/den-react`), и приложение пишет проход
106
+ * в терминах СВОЕГО фреймворка.
107
+ *
108
+ * @example
109
+ * serverRender: (tree, render) => {
110
+ * const sheet = new ServerStyleSheet()
111
+ *
112
+ * return { head: sheet.getStyleTags(), html: render(sheet.collectStyles(tree)) }
113
+ * }
114
+ */
115
+ type ServerRender<Tree = unknown> = (tree: Tree, render: RenderTree<Tree>) => ServerRenderResult;
116
+
117
+ /** Что нужно адаптеру, чтобы отрисовать остров на сервере. */
118
+ interface RenderIslandOptions {
119
+ /** Контейнер острова — уже собранный, с активированными VM. */
120
+ container: Container;
121
+ /** Внешние заполнители слотов; для ядра непрозрачны, как и само дерево. */
122
+ slots?: Record<string, unknown>;
123
+ }
58
124
  /**
59
125
  * Порт view-адаптера. Реализуется пакетом фреймворка (`@foxford/den-react` и его аналоги),
60
126
  * объявляется приложением через `defineLayer(ViewLayerToken, { adapter })`.
@@ -70,6 +136,18 @@ interface ViewAdapter {
70
136
  * @returns Компонент фреймворка — для ядра непрозрачен
71
137
  */
72
138
  createIsland(descriptor: IslandDescriptor): unknown;
139
+ /**
140
+ * Отрисовывает остров в разметку на сервере.
141
+ *
142
+ * Единственная операция острова, которую ядро не может сделать само: `renderToString` —
143
+ * функция конкретного фреймворка, а ядро view-агностично. Здесь же адаптер исполняет
144
+ * `serverRender` приложения, если тот объявлен, — тип дерева известен только адаптеру.
145
+ *
146
+ * @param descriptor - Дескриптор острова
147
+ * @param options - Контейнер острова и заполнители слотов
148
+ * @returns Разметка и вклад в `<head>` готовой разметкой
149
+ */
150
+ renderIsland(descriptor: IslandDescriptor, options: RenderIslandOptions): ServerRenderResult;
73
151
  /**
74
152
  * Ставит стратегию резолва `define*` во view (`installResolver` из `@foxford/den/adapter`).
75
153
  * У каждого фреймворка она своя: у React — хук поверх контекста контейнера, у Vue —
@@ -107,4 +185,4 @@ declare function viewAdapterOf(layer: ViewLayerDefinition): ViewAdapter;
107
185
  */
108
186
  declare function resolveViewAdapter(container: Container): ViewAdapter;
109
187
 
110
- export { type IslandDescriptor as I, type ViewAdapter as V, type ViewLayerDefinition as a, ViewLayerToken as b, resolveViewAdapter as r, viewAdapterOf as v };
188
+ export { type IslandDescriptor as I, type RenderIslandOptions as R, type ServerRender as S, type ViewAdapter as V, type ViewLayerDefinition as a, ViewLayerToken as b, type RenderTree as c, type ServerRenderResult as d, resolveViewAdapter as r, viewAdapterOf as v };
@@ -1,5 +1,5 @@
1
- import { S as SlotDefinition } from './define-slot-ESU7FR9O.js';
2
- import { s as LayerDefinition, z as Definition, q as LayerConfig } from './types-Deyolj1-.js';
1
+ import { S as SlotDefinition } from './define-slot-Dil8Kr1A.js';
2
+ import { q as LayerDefinition, x as Definition, o as LayerConfig } from './types-COwVwgzE.js';
3
3
  import { Token, Container } from '@foxford/ioc';
4
4
 
5
5
  /**
@@ -45,6 +45,18 @@ interface IslandDescriptor {
45
45
  * ездит через denState.
46
46
  */
47
47
  viewModels?: ReadonlyArray<Definition<unknown>>;
48
+ /**
49
+ * Единицы, которые обязаны жить с загрузки, а не с первого резолва.
50
+ *
51
+ * Регистрация ленива по природе: не спросили — не создали. Обычно это то, что нужно, но
52
+ * сервису, который сам на что-то подписывается или греет кэш, ждать первого потребителя
53
+ * незачем. Токены — из тех же `units`.
54
+ *
55
+ * Поднимаются при МОНТИРОВАНИИ острова, а не при сборке контейнера: контейнер строится и на
56
+ * сервере, а сервер живёт один рендер — открытая там WS-подписка это не «eager», а утечка.
57
+ * Серверное состояние приложения считает активация VM.
58
+ */
59
+ eager?: ReadonlyArray<Token<unknown>>;
48
60
  /**
49
61
  * Места расширения приложения: ключ — локальное имя слота, значение — `defineSlot`.
50
62
  *
@@ -55,6 +67,60 @@ interface IslandDescriptor {
55
67
  slots?: Readonly<Record<string, SlotDefinition>>;
56
68
  }
57
69
 
70
+ /** Что приложение отдало серверным рендером. */
71
+ interface ServerRenderResult {
72
+ /** Разметка дерева — результат переданного рендера. */
73
+ html: string;
74
+ /**
75
+ * Вклад в `<head>` ГОТОВОЙ разметкой: то, что рождается только во время рендера и
76
+ * данными не описывается.
77
+ *
78
+ * Таковы стили CSS-in-JS: стороннее представление у них одно — тег со служебными
79
+ * атрибутами, по которым клиентский рантайм узнаёт свои правила. Разобрав его на поля,
80
+ * потеряешь регидратацию, и стили впрыснутся вторым экземпляром. Поэтому вклад в документ
81
+ * ДАННЫМИ (`DocumentHead` у хостов) остаётся отдельным каналом, а это — разметка.
82
+ *
83
+ * Разметка приезжает в документ хоста ДОСЛОВНО, без экранирования — иначе служебные
84
+ * атрибуты не пережили бы вставку. Отвечает за содержимое приложение: у сетевого
85
+ * транспорта строка приходит из чужого процесса, и хост её не разбирает.
86
+ */
87
+ head?: string;
88
+ }
89
+ /** Рендер дерева в разметку. Его даёт тот, кто серверный рендер исполняет. */
90
+ type RenderTree<Tree = unknown> = (tree: Tree) => string;
91
+ /**
92
+ * Серверный рендер приложения: приложению дают его дерево и рендер, оно возвращает
93
+ * разметку и свой вклад в документ.
94
+ *
95
+ * Рендер приходит АРГУМЕНТОМ, потому что приложение им не владеет: страницу собирает тот,
96
+ * кто держит документ, и приложений на ней может быть несколько. Отдав рендер аргументом,
97
+ * приложение получает обычную функцию — состояние прохода живёт локальной переменной и
98
+ * между запросами не утекает.
99
+ *
100
+ * Подпись одна на оба транспорта, и в этом смысл: у приложения в процессе хоста рендер
101
+ * передаёт хост, у сетевого — его собственный сервер, а `html` и `head` едут ответом.
102
+ * Перевод приложения между транспортами эту часть не задевает.
103
+ *
104
+ * `Tree` — дерево view-фреймворка, для ядра непрозрачное. View-адаптер сужает параметр
105
+ * до своего типа (`ReactServerRender` в `@foxford/den-react`), и приложение пишет проход
106
+ * в терминах СВОЕГО фреймворка.
107
+ *
108
+ * @example
109
+ * serverRender: (tree, render) => {
110
+ * const sheet = new ServerStyleSheet()
111
+ *
112
+ * return { head: sheet.getStyleTags(), html: render(sheet.collectStyles(tree)) }
113
+ * }
114
+ */
115
+ type ServerRender<Tree = unknown> = (tree: Tree, render: RenderTree<Tree>) => ServerRenderResult;
116
+
117
+ /** Что нужно адаптеру, чтобы отрисовать остров на сервере. */
118
+ interface RenderIslandOptions {
119
+ /** Контейнер острова — уже собранный, с активированными VM. */
120
+ container: Container;
121
+ /** Внешние заполнители слотов; для ядра непрозрачны, как и само дерево. */
122
+ slots?: Record<string, unknown>;
123
+ }
58
124
  /**
59
125
  * Порт view-адаптера. Реализуется пакетом фреймворка (`@foxford/den-react` и его аналоги),
60
126
  * объявляется приложением через `defineLayer(ViewLayerToken, { adapter })`.
@@ -70,6 +136,18 @@ interface ViewAdapter {
70
136
  * @returns Компонент фреймворка — для ядра непрозрачен
71
137
  */
72
138
  createIsland(descriptor: IslandDescriptor): unknown;
139
+ /**
140
+ * Отрисовывает остров в разметку на сервере.
141
+ *
142
+ * Единственная операция острова, которую ядро не может сделать само: `renderToString` —
143
+ * функция конкретного фреймворка, а ядро view-агностично. Здесь же адаптер исполняет
144
+ * `serverRender` приложения, если тот объявлен, — тип дерева известен только адаптеру.
145
+ *
146
+ * @param descriptor - Дескриптор острова
147
+ * @param options - Контейнер острова и заполнители слотов
148
+ * @returns Разметка и вклад в `<head>` готовой разметкой
149
+ */
150
+ renderIsland(descriptor: IslandDescriptor, options: RenderIslandOptions): ServerRenderResult;
73
151
  /**
74
152
  * Ставит стратегию резолва `define*` во view (`installResolver` из `@foxford/den/adapter`).
75
153
  * У каждого фреймворка она своя: у React — хук поверх контекста контейнера, у Vue —
@@ -107,4 +185,4 @@ declare function viewAdapterOf(layer: ViewLayerDefinition): ViewAdapter;
107
185
  */
108
186
  declare function resolveViewAdapter(container: Container): ViewAdapter;
109
187
 
110
- export { type IslandDescriptor as I, type ViewAdapter as V, type ViewLayerDefinition as a, ViewLayerToken as b, resolveViewAdapter as r, viewAdapterOf as v };
188
+ export { type IslandDescriptor as I, type RenderIslandOptions as R, type ServerRender as S, type ViewAdapter as V, type ViewLayerDefinition as a, ViewLayerToken as b, type RenderTree as c, type ServerRenderResult as d, resolveViewAdapter as r, viewAdapterOf as v };