@foxford/den 3.2.4 → 3.2.5-beta-3141d32-20260904

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 CHANGED
@@ -186,7 +186,9 @@ describeDocument(): DocumentContribution {
186
186
  **Статус HTTP описывает транспорт, а исход страницы едет полем `status` в теле.** `200` значит
187
187
  «приложение ответило» — хоть `404`, хоть редиректом. Всё остальное значит «до приложения не
188
188
  дошли»: `400` — сломанный запрос, `404` — неизвестная ручка или путь вне заявки, `500` — рендер
189
- упал. Так `404` от приложения не путается с `404` от неверно настроенного прокси.
189
+ упал. Так `404` от приложения не путается с `404` от неверно настроенного прокси. Упавший
190
+ рендер уходит как `reply.send(error)`: Fastify зовёт `onError` у потребителя с исходным
191
+ исключением; тело — стандартный JSON ошибки Fastify.
190
192
 
191
193
  Браузерные заголовки рантайм читает только из `X-Forwarded-*` и кладёт в `RequestContextToken`;
192
194
  `Cookie` — обычным именем.
@@ -303,7 +305,8 @@ export default defineAppConfig({
303
305
  | `@foxford/den` | рантайм, `define*`, менеджеры контейнеров и состояния |
304
306
  | `@foxford/den/define` | те же `define*` без сайд-эффекта установки резолвера |
305
307
  | `@foxford/den/adapter` | шов резолва для адаптеров хостов |
306
- | `@foxford/den/island` | остров: жизненный цикл, шов `renderApp`, порт `ViewAdapter` |
308
+ | `@foxford/den/island` | остров: жизненный цикл, шов `renderApp`, порт `ViewAdapter`, очередь клиентских падений (`reportIslandError` / `installIslandErrorDrain`) |
309
+
307
310
  | `@foxford/den/serve` | сетевой транспорт: `serveApp`, `appPlugin` |
308
311
  | `@foxford/den/build` | конфиг приложения и сборка: `defineAppConfig`, `buildApp` |
309
312
  | `@foxford/den/bin` | точка входа команды `den` |
package/island/index.cjs CHANGED
@@ -23,6 +23,50 @@ require('../chunk-A6ZPAM6Z.cjs');
23
23
 
24
24
  var _chunkT5QVCXVBcjs = require('../chunk-T5QVCXVB.cjs');
25
25
 
26
+ // src/island/errors.ts
27
+ var ISLAND_ERRORS_KEY = "__DEN_ISLAND_ERRORS__";
28
+ function denWindow() {
29
+ return typeof window === "undefined" ? void 0 : window;
30
+ }
31
+ function errorsSlot(w) {
32
+ const existing = w[ISLAND_ERRORS_KEY];
33
+ if (existing !== void 0) {
34
+ return existing;
35
+ }
36
+ const created = [];
37
+ w[ISLAND_ERRORS_KEY] = created;
38
+ return created;
39
+ }
40
+ function reportIslandError(error, context) {
41
+ const w = denWindow();
42
+ if (w === void 0) {
43
+ return;
44
+ }
45
+ errorsSlot(w).push({ context, error });
46
+ }
47
+ function installIslandErrorDrain(drain) {
48
+ const w = denWindow();
49
+ if (w === void 0) {
50
+ return;
51
+ }
52
+ if (drain === null) {
53
+ const empty = [];
54
+ w[ISLAND_ERRORS_KEY] = empty;
55
+ return;
56
+ }
57
+ const current = w[ISLAND_ERRORS_KEY];
58
+ const pending = Array.isArray(current) ? current : [];
59
+ for (const report of pending) {
60
+ drain(report.error, report.context);
61
+ }
62
+ w[ISLAND_ERRORS_KEY] = {
63
+ push(report) {
64
+ drain(report.error, report.context);
65
+ return 1;
66
+ }
67
+ };
68
+ }
69
+
26
70
  // src/island/navigate.ts
27
71
  var NAVIGATE_EVENT = "den:navigate";
28
72
  function navigateIsland(node, route) {
@@ -122,4 +166,7 @@ function describeIsland(descriptor, denState) {
122
166
 
123
167
 
124
168
 
125
- exports.DOCUMENT_EVENT = DOCUMENT_EVENT; exports.NAVIGATE_EVENT = NAVIGATE_EVENT; exports.ViewLayerToken = _chunkXOJ2VWX4cjs.ViewLayerToken; exports.activateIslandViewModels = _chunkXDM6J4LJcjs.activateIslandViewModels; exports.collectDocumentContribution = _chunkXDM6J4LJcjs.collectDocumentContribution; exports.collectIslandState = _chunkXDM6J4LJcjs.collectIslandState; exports.createIslandContainer = _chunkXDM6J4LJcjs.createIslandContainer; exports.describeIsland = describeIsland; exports.getAppContainer = _chunkXDM6J4LJcjs.getAppContainer; exports.getIslandsSnapshot = getIslandsSnapshot; exports.hydrateIslandState = _chunkXDM6J4LJcjs.hydrateIslandState; exports.isDocumentSource = _chunkXDM6J4LJcjs.isDocumentSource; exports.matchRoute = _chunkXDM6J4LJcjs.matchRoute; exports.navigateIsland = navigateIsland; exports.normalizeRoutes = _chunkXDM6J4LJcjs.normalizeRoutes; exports.onIslandDocument = onIslandDocument; exports.onIslandNavigate = onIslandNavigate; exports.pathPattern = _chunkXDM6J4LJcjs.pathPattern; exports.publishIslandDocument = publishIslandDocument; exports.registerIsland = registerIsland; exports.renderApp = _chunkXDM6J4LJcjs.renderApp; exports.resolveEagerUnits = _chunkXDM6J4LJcjs.resolveEagerUnits; exports.resolveViewAdapter = _chunkXOJ2VWX4cjs.resolveViewAdapter; exports.slotAddress = slotAddress; exports.subscribeIslands = subscribeIslands; exports.viewAdapterOf = _chunkXOJ2VWX4cjs.viewAdapterOf;
169
+
170
+
171
+
172
+ exports.DOCUMENT_EVENT = DOCUMENT_EVENT; exports.ISLAND_ERRORS_KEY = ISLAND_ERRORS_KEY; exports.NAVIGATE_EVENT = NAVIGATE_EVENT; exports.ViewLayerToken = _chunkXOJ2VWX4cjs.ViewLayerToken; exports.activateIslandViewModels = _chunkXDM6J4LJcjs.activateIslandViewModels; exports.collectDocumentContribution = _chunkXDM6J4LJcjs.collectDocumentContribution; exports.collectIslandState = _chunkXDM6J4LJcjs.collectIslandState; exports.createIslandContainer = _chunkXDM6J4LJcjs.createIslandContainer; exports.describeIsland = describeIsland; exports.getAppContainer = _chunkXDM6J4LJcjs.getAppContainer; exports.getIslandsSnapshot = getIslandsSnapshot; exports.hydrateIslandState = _chunkXDM6J4LJcjs.hydrateIslandState; exports.installIslandErrorDrain = installIslandErrorDrain; exports.isDocumentSource = _chunkXDM6J4LJcjs.isDocumentSource; exports.matchRoute = _chunkXDM6J4LJcjs.matchRoute; exports.navigateIsland = navigateIsland; exports.normalizeRoutes = _chunkXDM6J4LJcjs.normalizeRoutes; exports.onIslandDocument = onIslandDocument; exports.onIslandNavigate = onIslandNavigate; exports.pathPattern = _chunkXDM6J4LJcjs.pathPattern; exports.publishIslandDocument = publishIslandDocument; exports.registerIsland = registerIsland; exports.renderApp = _chunkXDM6J4LJcjs.renderApp; exports.reportIslandError = reportIslandError; exports.resolveEagerUnits = _chunkXDM6J4LJcjs.resolveEagerUnits; exports.resolveViewAdapter = _chunkXOJ2VWX4cjs.resolveViewAdapter; exports.slotAddress = slotAddress; exports.subscribeIslands = subscribeIslands; exports.viewAdapterOf = _chunkXOJ2VWX4cjs.viewAdapterOf;
@@ -69,6 +69,51 @@ interface DocumentSource {
69
69
  */
70
70
  declare function isDocumentSource(value: unknown): value is DocumentSource;
71
71
 
72
+ /**
73
+ * Клиентские падения острова между бандлами.
74
+ *
75
+ * Сетевое приложение оживает своим бандлом, хост — своим. У каждого своя копия модулей,
76
+ * module-level очередь в одной не видна в другой. Шина — один ключ на `window`.
77
+ *
78
+ * Остров всегда делает `.push({ error, context })` ({@link reportIslandError}). Пока хоста
79
+ * нет — это обычный массив. Хост после Sentry.init зовёт {@link installIslandErrorDrain}:
80
+ * сливает backlog и подменяет значение объектом с тем же `.push` — дальше ошибки идут
81
+ * сразу в приёмник. Обёртки нет: стек и тип остаются приложению.
82
+ */
83
+ /** Фаза, на которой остров упал. */
84
+ type IslandErrorPhase = 'render' | 'hydrate' | 'navigate';
85
+ /** Контекст падения острова для приёмника. */
86
+ interface IslandErrorContext {
87
+ /** Имя приложения из дескриптора. */
88
+ appName: string;
89
+ phase: IslandErrorPhase;
90
+ }
91
+ /** Одна запись: исходное исключение и контекст. */
92
+ interface IslandErrorReport {
93
+ error: unknown;
94
+ context: IslandErrorContext;
95
+ }
96
+ /** Приёмник: хост шлёт в Sentry (или куда угодно). */
97
+ type IslandErrorDrain = (error: unknown, context: IslandErrorContext) => void;
98
+ /**
99
+ * Ключ на `window` — обязан совпадать у хоста и remote.
100
+ *
101
+ * Значение: массив (до приёмника) либо объект с `push` (после {@link installIslandErrorDrain}).
102
+ */
103
+ declare const ISLAND_ERRORS_KEY = "__DEN_ISLAND_ERRORS__";
104
+ /**
105
+ * Кладёт исходное исключение в шину на `window`.
106
+ *
107
+ * До приёмника — в массив; после — сразу в drain (тот же `.push`).
108
+ */
109
+ declare function reportIslandError(error: unknown, context: IslandErrorContext): void;
110
+ /**
111
+ * Сливает накопленное и подменяет слот drain-объектом с `.push`.
112
+ *
113
+ * Зовёт хост после инициализации наблюдаемости. `null` — снимает: снова обычный массив.
114
+ */
115
+ declare function installIslandErrorDrain(drain: IslandErrorDrain | null): void;
116
+
72
117
  /**
73
118
  * Адрес, по которому рендерится остров.
74
119
  *
@@ -324,4 +369,4 @@ declare function getAppContainer(): Container;
324
369
  */
325
370
  declare function slotAddress(app: string, slot: string): string;
326
371
 
327
- export { AppAssets, type CreateIslandContainerOptions, DOCUMENT_EVENT, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, navigateIsland, onIslandDocument, onIslandNavigate, publishIslandDocument, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
372
+ export { AppAssets, type CreateIslandContainerOptions, DOCUMENT_EVENT, type DocumentContribution, type DocumentHead, type DocumentSource, ISLAND_ERRORS_KEY, IslandDescriptor, type IslandErrorContext, type IslandErrorDrain, type IslandErrorPhase, type IslandErrorReport, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, installIslandErrorDrain, isDocumentSource, navigateIsland, onIslandDocument, onIslandNavigate, publishIslandDocument, registerIsland, renderApp, reportIslandError, resolveEagerUnits, slotAddress, subscribeIslands };
package/island/index.d.ts CHANGED
@@ -69,6 +69,51 @@ interface DocumentSource {
69
69
  */
70
70
  declare function isDocumentSource(value: unknown): value is DocumentSource;
71
71
 
72
+ /**
73
+ * Клиентские падения острова между бандлами.
74
+ *
75
+ * Сетевое приложение оживает своим бандлом, хост — своим. У каждого своя копия модулей,
76
+ * module-level очередь в одной не видна в другой. Шина — один ключ на `window`.
77
+ *
78
+ * Остров всегда делает `.push({ error, context })` ({@link reportIslandError}). Пока хоста
79
+ * нет — это обычный массив. Хост после Sentry.init зовёт {@link installIslandErrorDrain}:
80
+ * сливает backlog и подменяет значение объектом с тем же `.push` — дальше ошибки идут
81
+ * сразу в приёмник. Обёртки нет: стек и тип остаются приложению.
82
+ */
83
+ /** Фаза, на которой остров упал. */
84
+ type IslandErrorPhase = 'render' | 'hydrate' | 'navigate';
85
+ /** Контекст падения острова для приёмника. */
86
+ interface IslandErrorContext {
87
+ /** Имя приложения из дескриптора. */
88
+ appName: string;
89
+ phase: IslandErrorPhase;
90
+ }
91
+ /** Одна запись: исходное исключение и контекст. */
92
+ interface IslandErrorReport {
93
+ error: unknown;
94
+ context: IslandErrorContext;
95
+ }
96
+ /** Приёмник: хост шлёт в Sentry (или куда угодно). */
97
+ type IslandErrorDrain = (error: unknown, context: IslandErrorContext) => void;
98
+ /**
99
+ * Ключ на `window` — обязан совпадать у хоста и remote.
100
+ *
101
+ * Значение: массив (до приёмника) либо объект с `push` (после {@link installIslandErrorDrain}).
102
+ */
103
+ declare const ISLAND_ERRORS_KEY = "__DEN_ISLAND_ERRORS__";
104
+ /**
105
+ * Кладёт исходное исключение в шину на `window`.
106
+ *
107
+ * До приёмника — в массив; после — сразу в drain (тот же `.push`).
108
+ */
109
+ declare function reportIslandError(error: unknown, context: IslandErrorContext): void;
110
+ /**
111
+ * Сливает накопленное и подменяет слот drain-объектом с `.push`.
112
+ *
113
+ * Зовёт хост после инициализации наблюдаемости. `null` — снимает: снова обычный массив.
114
+ */
115
+ declare function installIslandErrorDrain(drain: IslandErrorDrain | null): void;
116
+
72
117
  /**
73
118
  * Адрес, по которому рендерится остров.
74
119
  *
@@ -324,4 +369,4 @@ declare function getAppContainer(): Container;
324
369
  */
325
370
  declare function slotAddress(app: string, slot: string): string;
326
371
 
327
- export { AppAssets, type CreateIslandContainerOptions, DOCUMENT_EVENT, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, navigateIsland, onIslandDocument, onIslandNavigate, publishIslandDocument, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
372
+ export { AppAssets, type CreateIslandContainerOptions, DOCUMENT_EVENT, type DocumentContribution, type DocumentHead, type DocumentSource, ISLAND_ERRORS_KEY, IslandDescriptor, type IslandErrorContext, type IslandErrorDrain, type IslandErrorPhase, type IslandErrorReport, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, installIslandErrorDrain, isDocumentSource, navigateIsland, onIslandDocument, onIslandNavigate, publishIslandDocument, registerIsland, renderApp, reportIslandError, resolveEagerUnits, slotAddress, subscribeIslands };
package/island/index.js CHANGED
@@ -23,6 +23,50 @@ import {
23
23
  __spreadValues
24
24
  } from "../chunk-6FNC3XMI.js";
25
25
 
26
+ // src/island/errors.ts
27
+ var ISLAND_ERRORS_KEY = "__DEN_ISLAND_ERRORS__";
28
+ function denWindow() {
29
+ return typeof window === "undefined" ? void 0 : window;
30
+ }
31
+ function errorsSlot(w) {
32
+ const existing = w[ISLAND_ERRORS_KEY];
33
+ if (existing !== void 0) {
34
+ return existing;
35
+ }
36
+ const created = [];
37
+ w[ISLAND_ERRORS_KEY] = created;
38
+ return created;
39
+ }
40
+ function reportIslandError(error, context) {
41
+ const w = denWindow();
42
+ if (w === void 0) {
43
+ return;
44
+ }
45
+ errorsSlot(w).push({ context, error });
46
+ }
47
+ function installIslandErrorDrain(drain) {
48
+ const w = denWindow();
49
+ if (w === void 0) {
50
+ return;
51
+ }
52
+ if (drain === null) {
53
+ const empty = [];
54
+ w[ISLAND_ERRORS_KEY] = empty;
55
+ return;
56
+ }
57
+ const current = w[ISLAND_ERRORS_KEY];
58
+ const pending = Array.isArray(current) ? current : [];
59
+ for (const report of pending) {
60
+ drain(report.error, report.context);
61
+ }
62
+ w[ISLAND_ERRORS_KEY] = {
63
+ push(report) {
64
+ drain(report.error, report.context);
65
+ return 1;
66
+ }
67
+ };
68
+ }
69
+
26
70
  // src/island/navigate.ts
27
71
  var NAVIGATE_EVENT = "den:navigate";
28
72
  function navigateIsland(node, route) {
@@ -97,6 +141,7 @@ function describeIsland(descriptor, denState) {
97
141
  }
98
142
  export {
99
143
  DOCUMENT_EVENT,
144
+ ISLAND_ERRORS_KEY,
100
145
  NAVIGATE_EVENT,
101
146
  ViewLayerToken,
102
147
  activateIslandViewModels,
@@ -107,6 +152,7 @@ export {
107
152
  getAppContainer,
108
153
  getIslandsSnapshot,
109
154
  hydrateIslandState,
155
+ installIslandErrorDrain,
110
156
  isDocumentSource,
111
157
  matchRoute,
112
158
  navigateIsland,
@@ -117,6 +163,7 @@ export {
117
163
  publishIslandDocument,
118
164
  registerIsland,
119
165
  renderApp,
166
+ reportIslandError,
120
167
  resolveEagerUnits,
121
168
  resolveViewAdapter,
122
169
  slotAddress,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxford/den",
3
- "version": "3.2.4",
3
+ "version": "3.2.5-beta-3141d32-20260904",
4
4
  "description": "Den — декларативный метафреймворк Foxford (core runtime)",
5
5
  "keywords": [
6
6
  "foxford",
@@ -134,6 +134,6 @@
134
134
  "view-adapter-BV62bdhM.d.ts",
135
135
  "view-adapter-DF1me25J.d.cts"
136
136
  ],
137
- "sha": "41b3ba5",
137
+ "sha": "3141d32",
138
138
  "scripts": {}
139
139
  }
package/serve/index.cjs CHANGED
@@ -140,7 +140,7 @@ var appPlugin = (instance, options) => {
140
140
  return result;
141
141
  } catch (error) {
142
142
  serveLog.error(`\u0440\u0435\u043D\u0434\u0435\u0440 \xAB${pathname}\xBB \u0443\u043F\u0430\u043B \u0437\u0430 ${String(Date.now() - startedAt)}\u043C\u0441`, error);
143
- return reply.code(500).send({ error: error instanceof Error ? error.message : String(error) });
143
+ return reply.send(error instanceof Error ? error : new Error(String(error)));
144
144
  }
145
145
  }));
146
146
  return Promise.resolve();
package/serve/index.js CHANGED
@@ -140,7 +140,7 @@ var appPlugin = (instance, options) => {
140
140
  return result;
141
141
  } catch (error) {
142
142
  serveLog.error(`\u0440\u0435\u043D\u0434\u0435\u0440 \xAB${pathname}\xBB \u0443\u043F\u0430\u043B \u0437\u0430 ${String(Date.now() - startedAt)}\u043C\u0441`, error);
143
- return reply.code(500).send({ error: error instanceof Error ? error.message : String(error) });
143
+ return reply.send(error instanceof Error ? error : new Error(String(error)));
144
144
  }
145
145
  }));
146
146
  return Promise.resolve();