@foxford/den 1.0.2 → 2.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.
@@ -0,0 +1,137 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+ var _chunkLJE7K7VDcjs = require('../chunk-LJE7K7VD.cjs');
6
+
7
+
8
+
9
+
10
+ var _chunkA6ZPAM6Zcjs = require('../chunk-A6ZPAM6Z.cjs');
11
+
12
+
13
+ var _chunkXHYR3SGGcjs = require('../chunk-XHYR3SGG.cjs');
14
+
15
+ // src/island/lifecycle.ts
16
+ var _logger = require('@foxford/logger');
17
+
18
+ // src/island/runtime.ts
19
+ var _ioc = require('@foxford/ioc');
20
+ var appContainer = null;
21
+ function getAppContainer() {
22
+ var _a;
23
+ if (appContainer === null) {
24
+ appContainer = new (0, _ioc.Container)();
25
+ ((_a = _chunkA6ZPAM6Zcjs.logger.getLogger("island:runtime")) != null ? _a : _chunkA6ZPAM6Zcjs.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 = _logger.log.getLogger((_a = descriptor.name) != null ? _a : "app")) != null ? _b : _chunkA6ZPAM6Zcjs.logger;
35
+ const islandLog = (_c = appLog.getLogger("client")) != null ? _c : appLog;
36
+ try {
37
+ const container = parent.createChild();
38
+ container.bind(_chunkA6ZPAM6Zcjs.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
+ _chunkA6ZPAM6Zcjs.unitLogger.call(void 0, 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
+ _chunkA6ZPAM6Zcjs.unitLogger.call(void 0, 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
+
80
+ // src/island/slot-address.ts
81
+ function slotAddress(app, slot) {
82
+ return `${app}:${slot}`;
83
+ }
84
+
85
+ // src/island/registry.ts
86
+ var nextId = 1;
87
+ var islands = /* @__PURE__ */ new Map();
88
+ var listeners = /* @__PURE__ */ new Set();
89
+ var snapshot = [];
90
+ function notify() {
91
+ snapshot = [...islands.values()];
92
+ for (const listener of listeners) {
93
+ listener();
94
+ }
95
+ }
96
+ function registerIsland(record) {
97
+ const id = nextId++;
98
+ islands.set(id, _chunkXHYR3SGGcjs.__spreadValues.call(void 0, { id }, record));
99
+ notify();
100
+ return () => {
101
+ islands.delete(id);
102
+ notify();
103
+ };
104
+ }
105
+ function getIslandsSnapshot() {
106
+ return snapshot;
107
+ }
108
+ function subscribeIslands(listener) {
109
+ listeners.add(listener);
110
+ return () => listeners.delete(listener);
111
+ }
112
+ function describeIsland(descriptor, denState) {
113
+ var _a, _b;
114
+ return {
115
+ denStateKeys: Object.keys(denState != null ? denState : {}),
116
+ name: descriptor.name,
117
+ // Адресами, а не локальными ключами: панель показывает острова разных приложений разом,
118
+ // и `aside` без префикса в этом списке ничего не значит.
119
+ slots: Object.keys((_a = descriptor.slots) != null ? _a : {}).map((slot) => slotAddress(descriptor.name, slot)),
120
+ units: descriptor.units.map((unit) => unit.token.description),
121
+ viewModels: ((_b = descriptor.viewModels) != null ? _b : []).map((vm) => vm.token.description)
122
+ };
123
+ }
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+ exports.ViewLayerToken = _chunkLJE7K7VDcjs.ViewLayerToken; exports.activateIslandViewModels = activateIslandViewModels; exports.createIslandContainer = createIslandContainer; exports.describeIsland = describeIsland; exports.getAppContainer = getAppContainer; exports.getIslandsSnapshot = getIslandsSnapshot; exports.hydrateIslandState = hydrateIslandState; exports.registerIsland = registerIsland; exports.resolveViewAdapter = _chunkLJE7K7VDcjs.resolveViewAdapter; exports.slotAddress = slotAddress; exports.subscribeIslands = subscribeIslands; exports.viewAdapterOf = _chunkLJE7K7VDcjs.viewAdapterOf;
@@ -0,0 +1,158 @@
1
+ import { I as IslandDescriptor } from '../view-adapter-vwO-7b1t.cjs';
2
+ export { V as ViewAdapter, a as ViewLayerDefinition, b as ViewLayerToken, r as resolveViewAdapter, v as viewAdapterOf } from '../view-adapter-vwO-7b1t.cjs';
3
+ import { Container } from '@foxford/ioc';
4
+ import '../define-slot-RfgqKa6n.cjs';
5
+ import '../types-Deyolj1-.cjs';
6
+
7
+ /** Опции сборки контейнера острова. */
8
+ interface CreateIslandContainerOptions {
9
+ /**
10
+ * Родитель контейнера острова. По умолчанию — app-scope контейнер федерации.
11
+ *
12
+ * Через него остров и получает всё, что настроил хост: конфиг HTTP, транспорт, общие
13
+ * сервисы. Хост биндит их один раз в app-контейнер, а резолв поднимается по parent-chain.
14
+ */
15
+ parent?: Container;
16
+ }
17
+ /**
18
+ * Собирает контейнер острова: scope-логгер, биндинги хоста, регистрация units + VM + слотов.
19
+ *
20
+ * Сборка идёт в песочнице: там, где хост рендерит остров на сервере (renderToString),
21
+ * error boundary view-фреймворка не работает, и неперехваченный сбой (например, потребителю
22
+ * отказали в обязательной возможности) уронил бы весь HTML-ответ. При падении возвращается
23
+ * `null` — адаптер рисует плашку вместо острова, страница живёт, а сервер и клиент приходят
24
+ * к одному результату (тот же отказ → та же плашка, без mismatch).
25
+ *
26
+ * @param descriptor - Дескриптор острова
27
+ * @param options - Родительский контейнер и биндинги хоста
28
+ * @returns Контейнер острова либо `null`, если сборка упала
29
+ */
30
+ declare function createIslandContainer(descriptor: IslandDescriptor, options?: CreateIslandContainerOptions): Container | null;
31
+ /**
32
+ * Сидит VM острова значениями из denState (ключ записи = `token.description`).
33
+ *
34
+ * Зовётся на КАЖДУЮ смену payload, а не однократно при сборке: при клиентской навигации
35
+ * хост обновляет данные страницы, не размонтируя дерево, и новое состояние приходит в
36
+ * живой остров. Отсекать повторный сидинг одного и того же значения — забота адаптера:
37
+ * он один знает, менялся ли payload с прошлого прохода.
38
+ *
39
+ * @param container - Контейнер острова
40
+ * @param descriptor - Дескриптор острова
41
+ * @param denState - Серверное состояние (ключ = `token.description` VM)
42
+ */
43
+ declare function hydrateIslandState(container: Container, descriptor: IslandDescriptor, denState: Record<string, unknown>): void;
44
+ /**
45
+ * Активирует VM острова самостоятельно — когда серверного denState нет (ремоунт острова
46
+ * без данных, чисто клиентский переход). С denState активация не нужна: состояние уже
47
+ * приехало готовым.
48
+ *
49
+ * @param container - Контейнер острова
50
+ * @param descriptor - Дескриптор острова
51
+ */
52
+ declare function activateIslandViewModels(container: Container, descriptor: IslandDescriptor): void;
53
+
54
+ /** Запись живого острова: имя + фактический состав его контейнера. */
55
+ interface IslandRecord {
56
+ /** Уникальный id инстанса острова (островов одного приложения может быть несколько). */
57
+ id: number;
58
+ /** Имя приложения из дескриптора (`market-app`) либо fallback-имя. */
59
+ name: string;
60
+ /** Data/Business единицы, зарегистрированные в контейнере острова (token.description). */
61
+ units: string[];
62
+ /** VM-единицы острова (token.description). */
63
+ viewModels: string[];
64
+ /** Слоты, задекларированные страницей острова (имена). */
65
+ slots: string[];
66
+ /** Ключи denState, из которых гидрированы VM (пусто — VM активировались сами). */
67
+ denStateKeys: string[];
68
+ }
69
+ /**
70
+ * Регистрирует живой остров. Вызывается island-рантаймом при монтировании.
71
+ * @param record - Состав острова (без id — его выдаёт реестр)
72
+ * @returns Функция снятия с учёта (вызвать при размонтировании)
73
+ */
74
+ declare function registerIsland(record: Omit<IslandRecord, 'id'>): () => void;
75
+ /** Текущий снапшот живых островов (стабильная ссылка до следующего изменения). */
76
+ declare function getIslandsSnapshot(): IslandRecord[];
77
+ /** Подписка на изменения реестра (для useSyncExternalStore и его аналогов). */
78
+ declare function subscribeIslands(listener: () => void): () => void;
79
+ /**
80
+ * Состав острова для реестра — то же самое, что увидит dev-панель.
81
+ *
82
+ * Считается ядром, а не адаптером: это пересказ дескриптора и denState, во view-фреймворк
83
+ * он не заглядывает.
84
+ *
85
+ * @param descriptor - Дескриптор острова
86
+ * @param denState - Серверное состояние острова (может отсутствовать)
87
+ * @returns Запись без id — её выдаст {@link registerIsland}
88
+ */
89
+ declare function describeIsland(descriptor: IslandDescriptor, denState?: Record<string, unknown>): Omit<IslandRecord, 'id'>;
90
+
91
+ /** App-scope контейнер федерации (создаётся при первом обращении). */
92
+ declare function getAppContainer(): Container;
93
+
94
+ /**
95
+ * Глобальный адрес слота — `market-app:aside`.
96
+ *
97
+ * Внутри острова слоты зовутся локально (ключ из карты `slots` дескриптора): реестр живёт в
98
+ * контейнере острова, и пересечься там не с кем. Адрес нужен там, где приложения встречаются:
99
+ * `mountsInto` одного указывает на место другого, и хост сводит их по этой строке.
100
+ *
101
+ * Пространство имён даёт имя приложения, а не автор слота, поэтому увести чужой адрес нечем —
102
+ * префикс не в руках объявляющего. Правило живёт здесь, чтобы приложение, хост и dev-панель
103
+ * считали адрес одинаково, а не по трём совпадающим шаблонным строкам.
104
+ *
105
+ * @param app - Имя приложения из дескриптора (`market-app`)
106
+ * @param slot - Локальное имя слота — ключ в карте `slots`
107
+ * @returns Адрес вида `app:slot`
108
+ */
109
+ declare function slotAddress(app: string, slot: string): string;
110
+
111
+ /** Что приложение отдало серверным рендером. */
112
+ interface ServerRenderResult {
113
+ /** Разметка дерева — результат переданного рендера. */
114
+ html: string;
115
+ /**
116
+ * Вклад в `<head>` ГОТОВОЙ разметкой: то, что рождается только во время рендера и
117
+ * данными не описывается.
118
+ *
119
+ * Таковы стили CSS-in-JS: стороннее представление у них одно — тег со служебными
120
+ * атрибутами, по которым клиентский рантайм узнаёт свои правила. Разобрав его на поля,
121
+ * потеряешь регидратацию, и стили впрыснутся вторым экземпляром. Поэтому вклад в документ
122
+ * ДАННЫМИ (`DocumentHead` у хостов) остаётся отдельным каналом, а это — разметка.
123
+ *
124
+ * Разметка приезжает в документ хоста ДОСЛОВНО, без экранирования — иначе служебные
125
+ * атрибуты не пережили бы вставку. Отвечает за содержимое приложение: у сетевого
126
+ * транспорта строка приходит из чужого процесса, и хост её не разбирает.
127
+ */
128
+ head?: string;
129
+ }
130
+ /** Рендер дерева в разметку. Его даёт тот, кто серверный рендер исполняет. */
131
+ type RenderTree<Tree = unknown> = (tree: Tree) => string;
132
+ /**
133
+ * Серверный рендер приложения: приложению дают его дерево и рендер, оно возвращает
134
+ * разметку и свой вклад в документ.
135
+ *
136
+ * Рендер приходит АРГУМЕНТОМ, потому что приложение им не владеет: страницу собирает тот,
137
+ * кто держит документ, и приложений на ней может быть несколько. Отдав рендер аргументом,
138
+ * приложение получает обычную функцию — состояние прохода живёт локальной переменной и
139
+ * между запросами не утекает.
140
+ *
141
+ * Подпись одна на оба транспорта, и в этом смысл: у приложения в процессе хоста рендер
142
+ * передаёт хост, у сетевого — его собственный сервер, а `html` и `head` едут ответом.
143
+ * Перевод приложения между транспортами эту часть не задевает.
144
+ *
145
+ * `Tree` — дерево view-фреймворка, для ядра непрозрачное. View-адаптер сужает параметр
146
+ * до своего типа (`ReactServerRender` в `@foxford/den-react`), и приложение пишет проход
147
+ * в терминах СВОЕГО фреймворка.
148
+ *
149
+ * @example
150
+ * serverRender: (tree, render) => {
151
+ * const sheet = new ServerStyleSheet()
152
+ *
153
+ * return { head: sheet.getStyleTags(), html: render(sheet.collectStyles(tree)) }
154
+ * }
155
+ */
156
+ type ServerRender<Tree = unknown> = (tree: Tree, render: RenderTree<Tree>) => ServerRenderResult;
157
+
158
+ export { type CreateIslandContainerOptions, IslandDescriptor, type IslandRecord, type RenderTree, type ServerRender, type ServerRenderResult, activateIslandViewModels, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, registerIsland, slotAddress, subscribeIslands };
@@ -0,0 +1,158 @@
1
+ import { I as IslandDescriptor } from '../view-adapter-CL0vZ-rv.js';
2
+ export { V as ViewAdapter, a as ViewLayerDefinition, b as ViewLayerToken, r as resolveViewAdapter, v as viewAdapterOf } from '../view-adapter-CL0vZ-rv.js';
3
+ import { Container } from '@foxford/ioc';
4
+ import '../define-slot-ESU7FR9O.js';
5
+ import '../types-Deyolj1-.js';
6
+
7
+ /** Опции сборки контейнера острова. */
8
+ interface CreateIslandContainerOptions {
9
+ /**
10
+ * Родитель контейнера острова. По умолчанию — app-scope контейнер федерации.
11
+ *
12
+ * Через него остров и получает всё, что настроил хост: конфиг HTTP, транспорт, общие
13
+ * сервисы. Хост биндит их один раз в app-контейнер, а резолв поднимается по parent-chain.
14
+ */
15
+ parent?: Container;
16
+ }
17
+ /**
18
+ * Собирает контейнер острова: scope-логгер, биндинги хоста, регистрация units + VM + слотов.
19
+ *
20
+ * Сборка идёт в песочнице: там, где хост рендерит остров на сервере (renderToString),
21
+ * error boundary view-фреймворка не работает, и неперехваченный сбой (например, потребителю
22
+ * отказали в обязательной возможности) уронил бы весь HTML-ответ. При падении возвращается
23
+ * `null` — адаптер рисует плашку вместо острова, страница живёт, а сервер и клиент приходят
24
+ * к одному результату (тот же отказ → та же плашка, без mismatch).
25
+ *
26
+ * @param descriptor - Дескриптор острова
27
+ * @param options - Родительский контейнер и биндинги хоста
28
+ * @returns Контейнер острова либо `null`, если сборка упала
29
+ */
30
+ declare function createIslandContainer(descriptor: IslandDescriptor, options?: CreateIslandContainerOptions): Container | null;
31
+ /**
32
+ * Сидит VM острова значениями из denState (ключ записи = `token.description`).
33
+ *
34
+ * Зовётся на КАЖДУЮ смену payload, а не однократно при сборке: при клиентской навигации
35
+ * хост обновляет данные страницы, не размонтируя дерево, и новое состояние приходит в
36
+ * живой остров. Отсекать повторный сидинг одного и того же значения — забота адаптера:
37
+ * он один знает, менялся ли payload с прошлого прохода.
38
+ *
39
+ * @param container - Контейнер острова
40
+ * @param descriptor - Дескриптор острова
41
+ * @param denState - Серверное состояние (ключ = `token.description` VM)
42
+ */
43
+ declare function hydrateIslandState(container: Container, descriptor: IslandDescriptor, denState: Record<string, unknown>): void;
44
+ /**
45
+ * Активирует VM острова самостоятельно — когда серверного denState нет (ремоунт острова
46
+ * без данных, чисто клиентский переход). С denState активация не нужна: состояние уже
47
+ * приехало готовым.
48
+ *
49
+ * @param container - Контейнер острова
50
+ * @param descriptor - Дескриптор острова
51
+ */
52
+ declare function activateIslandViewModels(container: Container, descriptor: IslandDescriptor): void;
53
+
54
+ /** Запись живого острова: имя + фактический состав его контейнера. */
55
+ interface IslandRecord {
56
+ /** Уникальный id инстанса острова (островов одного приложения может быть несколько). */
57
+ id: number;
58
+ /** Имя приложения из дескриптора (`market-app`) либо fallback-имя. */
59
+ name: string;
60
+ /** Data/Business единицы, зарегистрированные в контейнере острова (token.description). */
61
+ units: string[];
62
+ /** VM-единицы острова (token.description). */
63
+ viewModels: string[];
64
+ /** Слоты, задекларированные страницей острова (имена). */
65
+ slots: string[];
66
+ /** Ключи denState, из которых гидрированы VM (пусто — VM активировались сами). */
67
+ denStateKeys: string[];
68
+ }
69
+ /**
70
+ * Регистрирует живой остров. Вызывается island-рантаймом при монтировании.
71
+ * @param record - Состав острова (без id — его выдаёт реестр)
72
+ * @returns Функция снятия с учёта (вызвать при размонтировании)
73
+ */
74
+ declare function registerIsland(record: Omit<IslandRecord, 'id'>): () => void;
75
+ /** Текущий снапшот живых островов (стабильная ссылка до следующего изменения). */
76
+ declare function getIslandsSnapshot(): IslandRecord[];
77
+ /** Подписка на изменения реестра (для useSyncExternalStore и его аналогов). */
78
+ declare function subscribeIslands(listener: () => void): () => void;
79
+ /**
80
+ * Состав острова для реестра — то же самое, что увидит dev-панель.
81
+ *
82
+ * Считается ядром, а не адаптером: это пересказ дескриптора и denState, во view-фреймворк
83
+ * он не заглядывает.
84
+ *
85
+ * @param descriptor - Дескриптор острова
86
+ * @param denState - Серверное состояние острова (может отсутствовать)
87
+ * @returns Запись без id — её выдаст {@link registerIsland}
88
+ */
89
+ declare function describeIsland(descriptor: IslandDescriptor, denState?: Record<string, unknown>): Omit<IslandRecord, 'id'>;
90
+
91
+ /** App-scope контейнер федерации (создаётся при первом обращении). */
92
+ declare function getAppContainer(): Container;
93
+
94
+ /**
95
+ * Глобальный адрес слота — `market-app:aside`.
96
+ *
97
+ * Внутри острова слоты зовутся локально (ключ из карты `slots` дескриптора): реестр живёт в
98
+ * контейнере острова, и пересечься там не с кем. Адрес нужен там, где приложения встречаются:
99
+ * `mountsInto` одного указывает на место другого, и хост сводит их по этой строке.
100
+ *
101
+ * Пространство имён даёт имя приложения, а не автор слота, поэтому увести чужой адрес нечем —
102
+ * префикс не в руках объявляющего. Правило живёт здесь, чтобы приложение, хост и dev-панель
103
+ * считали адрес одинаково, а не по трём совпадающим шаблонным строкам.
104
+ *
105
+ * @param app - Имя приложения из дескриптора (`market-app`)
106
+ * @param slot - Локальное имя слота — ключ в карте `slots`
107
+ * @returns Адрес вида `app:slot`
108
+ */
109
+ declare function slotAddress(app: string, slot: string): string;
110
+
111
+ /** Что приложение отдало серверным рендером. */
112
+ interface ServerRenderResult {
113
+ /** Разметка дерева — результат переданного рендера. */
114
+ html: string;
115
+ /**
116
+ * Вклад в `<head>` ГОТОВОЙ разметкой: то, что рождается только во время рендера и
117
+ * данными не описывается.
118
+ *
119
+ * Таковы стили CSS-in-JS: стороннее представление у них одно — тег со служебными
120
+ * атрибутами, по которым клиентский рантайм узнаёт свои правила. Разобрав его на поля,
121
+ * потеряешь регидратацию, и стили впрыснутся вторым экземпляром. Поэтому вклад в документ
122
+ * ДАННЫМИ (`DocumentHead` у хостов) остаётся отдельным каналом, а это — разметка.
123
+ *
124
+ * Разметка приезжает в документ хоста ДОСЛОВНО, без экранирования — иначе служебные
125
+ * атрибуты не пережили бы вставку. Отвечает за содержимое приложение: у сетевого
126
+ * транспорта строка приходит из чужого процесса, и хост её не разбирает.
127
+ */
128
+ head?: string;
129
+ }
130
+ /** Рендер дерева в разметку. Его даёт тот, кто серверный рендер исполняет. */
131
+ type RenderTree<Tree = unknown> = (tree: Tree) => string;
132
+ /**
133
+ * Серверный рендер приложения: приложению дают его дерево и рендер, оно возвращает
134
+ * разметку и свой вклад в документ.
135
+ *
136
+ * Рендер приходит АРГУМЕНТОМ, потому что приложение им не владеет: страницу собирает тот,
137
+ * кто держит документ, и приложений на ней может быть несколько. Отдав рендер аргументом,
138
+ * приложение получает обычную функцию — состояние прохода живёт локальной переменной и
139
+ * между запросами не утекает.
140
+ *
141
+ * Подпись одна на оба транспорта, и в этом смысл: у приложения в процессе хоста рендер
142
+ * передаёт хост, у сетевого — его собственный сервер, а `html` и `head` едут ответом.
143
+ * Перевод приложения между транспортами эту часть не задевает.
144
+ *
145
+ * `Tree` — дерево view-фреймворка, для ядра непрозрачное. View-адаптер сужает параметр
146
+ * до своего типа (`ReactServerRender` в `@foxford/den-react`), и приложение пишет проход
147
+ * в терминах СВОЕГО фреймворка.
148
+ *
149
+ * @example
150
+ * serverRender: (tree, render) => {
151
+ * const sheet = new ServerStyleSheet()
152
+ *
153
+ * return { head: sheet.getStyleTags(), html: render(sheet.collectStyles(tree)) }
154
+ * }
155
+ */
156
+ type ServerRender<Tree = unknown> = (tree: Tree, render: RenderTree<Tree>) => ServerRenderResult;
157
+
158
+ export { type CreateIslandContainerOptions, IslandDescriptor, type IslandRecord, type RenderTree, type ServerRender, type ServerRenderResult, activateIslandViewModels, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, registerIsland, slotAddress, subscribeIslands };
@@ -0,0 +1,137 @@
1
+ import {
2
+ ViewLayerToken,
3
+ resolveViewAdapter,
4
+ viewAdapterOf
5
+ } from "../chunk-TCYGDT2A.js";
6
+ import {
7
+ LoggerToken,
8
+ logger,
9
+ unitLogger
10
+ } from "../chunk-TPBI6TOU.js";
11
+ import {
12
+ __spreadValues
13
+ } from "../chunk-HJO26HIQ.js";
14
+
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
+
80
+ // src/island/slot-address.ts
81
+ function slotAddress(app, slot) {
82
+ return `${app}:${slot}`;
83
+ }
84
+
85
+ // src/island/registry.ts
86
+ var nextId = 1;
87
+ var islands = /* @__PURE__ */ new Map();
88
+ var listeners = /* @__PURE__ */ new Set();
89
+ var snapshot = [];
90
+ function notify() {
91
+ snapshot = [...islands.values()];
92
+ for (const listener of listeners) {
93
+ listener();
94
+ }
95
+ }
96
+ function registerIsland(record) {
97
+ const id = nextId++;
98
+ islands.set(id, __spreadValues({ id }, record));
99
+ notify();
100
+ return () => {
101
+ islands.delete(id);
102
+ notify();
103
+ };
104
+ }
105
+ function getIslandsSnapshot() {
106
+ return snapshot;
107
+ }
108
+ function subscribeIslands(listener) {
109
+ listeners.add(listener);
110
+ return () => listeners.delete(listener);
111
+ }
112
+ function describeIsland(descriptor, denState) {
113
+ var _a, _b;
114
+ return {
115
+ denStateKeys: Object.keys(denState != null ? denState : {}),
116
+ name: descriptor.name,
117
+ // Адресами, а не локальными ключами: панель показывает острова разных приложений разом,
118
+ // и `aside` без префикса в этом списке ничего не значит.
119
+ slots: Object.keys((_a = descriptor.slots) != null ? _a : {}).map((slot) => slotAddress(descriptor.name, slot)),
120
+ units: descriptor.units.map((unit) => unit.token.description),
121
+ viewModels: ((_b = descriptor.viewModels) != null ? _b : []).map((vm) => vm.token.description)
122
+ };
123
+ }
124
+ export {
125
+ ViewLayerToken,
126
+ activateIslandViewModels,
127
+ createIslandContainer,
128
+ describeIsland,
129
+ getAppContainer,
130
+ getIslandsSnapshot,
131
+ hydrateIslandState,
132
+ registerIsland,
133
+ resolveViewAdapter,
134
+ slotAddress,
135
+ subscribeIslands,
136
+ viewAdapterOf
137
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxford/den",
3
- "version": "1.0.2",
3
+ "version": "2.0.0",
4
4
  "description": "Den — декларативный метафреймворк Foxford (core runtime)",
5
5
  "keywords": [
6
6
  "foxford",
@@ -40,6 +40,11 @@
40
40
  "import": "./define.js",
41
41
  "require": "./define.cjs",
42
42
  "types": "./define.d.ts"
43
+ },
44
+ "./island": {
45
+ "import": "./island/index.js",
46
+ "require": "./island/index.cjs",
47
+ "types": "./island/index.d.ts"
43
48
  }
44
49
  },
45
50
  "files": [
@@ -48,12 +53,20 @@
48
53
  "adapter.d.cts",
49
54
  "adapter.d.ts",
50
55
  "adapter.js",
51
- "chunk-4FPCRHSM.cjs",
52
- "chunk-E7HG2NXO.js",
53
- "chunk-NRE6DV6O.cjs",
54
- "chunk-S5KTWI6P.js",
55
- "define-slot--HM6B232.d.ts",
56
- "define-slot-es1K6_-L.d.cts",
56
+ "chunk-5DJZZML5.cjs",
57
+ "chunk-A6ZPAM6Z.cjs",
58
+ "chunk-C7BM4DGX.cjs",
59
+ "chunk-HJO26HIQ.js",
60
+ "chunk-LJE7K7VD.cjs",
61
+ "chunk-TCYGDT2A.js",
62
+ "chunk-TPBI6TOU.js",
63
+ "chunk-WBHHHICS.js",
64
+ "chunk-WRQC6BVJ.js",
65
+ "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",
57
70
  "define.cjs",
58
71
  "define.d.cts",
59
72
  "define.d.ts",
@@ -62,10 +75,13 @@
62
75
  "index.d.cts",
63
76
  "index.d.ts",
64
77
  "index.js",
78
+ "island",
65
79
  "package.json",
66
- "types-B4Arep3V.d.cts",
67
- "types-B4Arep3V.d.ts"
80
+ "types-Deyolj1-.d.cts",
81
+ "types-Deyolj1-.d.ts",
82
+ "view-adapter-CL0vZ-rv.d.ts",
83
+ "view-adapter-vwO-7b1t.d.cts"
68
84
  ],
69
- "sha": "c4ff474",
85
+ "sha": "d3153b1",
70
86
  "scripts": {}
71
87
  }