setti 0.0.0-stage → 0.1.0-alpha.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/dist/index.js ADDED
@@ -0,0 +1,816 @@
1
+ import { stringify } from 'devalue';
2
+ export { parse as devalueParse, stringify as devalueStringify } from 'devalue';
3
+ import { createContext, Component, createElement } from 'react';
4
+
5
+ // src/define-config.ts
6
+ function defineNixoConfig(config) {
7
+ return config;
8
+ }
9
+
10
+ // src/define-route.ts
11
+ function defineRoute(routes) {
12
+ return routes;
13
+ }
14
+
15
+ // src/define-global-loader.ts
16
+ function defineGlobalLoader(map) {
17
+ return map;
18
+ }
19
+
20
+ // src/client/slices/AbstractSlice.ts
21
+ var AbstractSlice = class {
22
+ /** Текущее состояние. `undefined` до первого hydrate/setState. */
23
+ _state = void 0;
24
+ /** База для reset(): первое значение hydrate/setState. */
25
+ _initialState = void 0;
26
+ /** Флаг: hydrate уже вызывался. */
27
+ _hydrated = false;
28
+ /** Подписчики. */
29
+ listeners = /* @__PURE__ */ new Set();
30
+ /**
31
+ * Инициализация state результатом `loadData`.
32
+ *
33
+ * Вызывается Loader'ом один раз на экземпляр. Повторный вызов — ошибка.
34
+ *
35
+ * @param result результат `loadData`.
36
+ * @throws {Error} если hydrate уже вызывался.
37
+ */
38
+ hydrate(result) {
39
+ if (this._hydrated) {
40
+ throw new Error("Slice: hydrate \u0443\u0436\u0435 \u0432\u044B\u0437\u044B\u0432\u0430\u043B\u0441\u044F. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 setState.");
41
+ }
42
+ this._hydrated = true;
43
+ this._state = this._initState(result);
44
+ if (this._initialState === void 0) {
45
+ this._initialState = this._state;
46
+ }
47
+ this.notify();
48
+ }
49
+ /**
50
+ * Текущее состояние.
51
+ *
52
+ * @returns состояние слайса.
53
+ */
54
+ getState() {
55
+ return this._state;
56
+ }
57
+ /**
58
+ * Snapshot для SSR/гидратации (=== getState).
59
+ *
60
+ * @returns состояние слайса.
61
+ */
62
+ getServerSnapshot() {
63
+ return this._state;
64
+ }
65
+ /**
66
+ * Обновляет state и уведомляет подписчиков.
67
+ *
68
+ * Если `value === prev` (Object.is) — нотификации нет.
69
+ *
70
+ * @param next новое значение или функция-апдейтер.
71
+ */
72
+ setState(next) {
73
+ const prev = this._state;
74
+ const value = typeof next === "function" ? next(prev) : next;
75
+ if (Object.is(value, prev)) return;
76
+ this._state = value;
77
+ if (this._initialState === void 0) {
78
+ this._initialState = value;
79
+ }
80
+ this.notify();
81
+ }
82
+ /**
83
+ * Подписка на изменения.
84
+ *
85
+ * @param listener колбэк.
86
+ * @returns функция отписки.
87
+ */
88
+ subscribe(listener) {
89
+ this.listeners.add(listener);
90
+ return () => {
91
+ this.listeners.delete(listener);
92
+ };
93
+ }
94
+ /** Сброс к состоянию первого hydrate/setState. */
95
+ reset() {
96
+ if (this._state === this._initialState) return;
97
+ this._state = this._initialState;
98
+ this.notify();
99
+ }
100
+ /** Уничтожение: очистка подписчиков. */
101
+ dispose() {
102
+ this.listeners.clear();
103
+ }
104
+ /** Оповещает подписчиков. */
105
+ notify() {
106
+ for (const l of this.listeners) l();
107
+ }
108
+ };
109
+
110
+ // src/client/slices/Slice.ts
111
+ var Slice = class extends AbstractSlice {
112
+ /**
113
+ * Identity: state = result как есть.
114
+ *
115
+ * @param result результат `loadData`.
116
+ * @returns тот же результат.
117
+ */
118
+ _initState(result) {
119
+ return result;
120
+ }
121
+ };
122
+
123
+ // src/errors.ts
124
+ var NixoError = class extends Error {
125
+ constructor(message) {
126
+ super(message);
127
+ this.name = "NixoError";
128
+ }
129
+ };
130
+ var NixoRpcError = class extends NixoError {
131
+ /** Оригинальный JSON-RPC error. */
132
+ rpcError;
133
+ /**
134
+ * @param rpcError JSON-RPC error payload.
135
+ */
136
+ constructor(rpcError) {
137
+ const detail = rpcError.detail ? ` (${rpcError.detail})` : "";
138
+ super(`JSON-RPC error ${rpcError.code}: ${rpcError.message}${detail}`);
139
+ this.name = "NixoRpcError";
140
+ this.rpcError = rpcError;
141
+ }
142
+ /**
143
+ * Код ошибки.
144
+ *
145
+ * @returns код JSON-RPC.
146
+ */
147
+ get code() {
148
+ return this.rpcError.code;
149
+ }
150
+ /**
151
+ * Дополнительное описание.
152
+ *
153
+ * @returns detail или `undefined`.
154
+ */
155
+ get detail() {
156
+ return this.rpcError.detail;
157
+ }
158
+ /**
159
+ * Дополнительные данные.
160
+ *
161
+ * @returns data или `undefined`.
162
+ */
163
+ get data() {
164
+ return this.rpcError.data;
165
+ }
166
+ };
167
+
168
+ // src/client/slices/SliceRegistry.ts
169
+ var SliceRegistry = class {
170
+ /** Page-scoped слайсы. */
171
+ page = /* @__PURE__ */ new Map();
172
+ /** Global-scoped слайсы. */
173
+ global = /* @__PURE__ */ new Map();
174
+ /** Подписчики на изменение конкретного ключа (для useSyncExternalStore). */
175
+ keyListeners = /* @__PURE__ */ new Map();
176
+ /**
177
+ * Ищет слайс по ключу: сначала page, потом global.
178
+ *
179
+ * @param key имя слайса.
180
+ * @returns слайс или undefined.
181
+ */
182
+ get(key) {
183
+ return this.page.get(key) ?? this.global.get(key);
184
+ }
185
+ /**
186
+ * Строгий поиск слайса.
187
+ *
188
+ * @param key имя слайса.
189
+ * @returns слайс.
190
+ * @throws {NixoError} если слайса нет.
191
+ */
192
+ require(key) {
193
+ const slice = this.get(key);
194
+ if (!slice) {
195
+ throw new NixoError(`Slice "${key}" not found in registry`);
196
+ }
197
+ return slice;
198
+ }
199
+ /**
200
+ * Подписка на изменение присутствия/инстанса слайса по ключу.
201
+ *
202
+ * @param key имя слайса.
203
+ * @param cb колбэк.
204
+ * @returns функция отписки.
205
+ */
206
+ subscribeKey(key, cb) {
207
+ let set = this.keyListeners.get(key);
208
+ if (!set) {
209
+ set = /* @__PURE__ */ new Set();
210
+ this.keyListeners.set(key, set);
211
+ }
212
+ set.add(cb);
213
+ return () => {
214
+ const s = this.keyListeners.get(key);
215
+ if (!s) return;
216
+ s.delete(cb);
217
+ if (s.size === 0) this.keyListeners.delete(key);
218
+ };
219
+ }
220
+ /**
221
+ * Регистрирует page-scoped слайс.
222
+ *
223
+ * @param key имя слайса.
224
+ * @param slice экземпляр.
225
+ */
226
+ setPage(key, slice) {
227
+ this.page.set(key, slice);
228
+ this.notifyKey(key);
229
+ }
230
+ /**
231
+ * Регистрирует global-scoped слайс.
232
+ *
233
+ * @param key имя слайса.
234
+ * @param slice экземпляр.
235
+ */
236
+ setGlobal(key, slice) {
237
+ this.global.set(key, slice);
238
+ this.notifyKey(key);
239
+ }
240
+ /** Dispose всех page-слайсов и очистка карты (навигация). */
241
+ disposePage() {
242
+ const keys = [...this.page.keys()];
243
+ for (const key of keys) {
244
+ this.page.get(key)?.dispose();
245
+ }
246
+ this.page.clear();
247
+ for (const key of keys) this.notifyKey(key);
248
+ }
249
+ /** Dispose всех слайсов (конец запроса / размонтирование). */
250
+ disposeAll() {
251
+ this.disposePage();
252
+ const globalKeys = [...this.global.keys()];
253
+ for (const key of globalKeys) {
254
+ this.global.get(key)?.dispose();
255
+ }
256
+ this.global.clear();
257
+ for (const key of globalKeys) this.notifyKey(key);
258
+ }
259
+ /**
260
+ * Уведомляет подписчиков на конкретный ключ.
261
+ *
262
+ * @param key имя слайса.
263
+ */
264
+ notifyKey(key) {
265
+ const set = this.keyListeners.get(key);
266
+ if (set) for (const cb of set) cb();
267
+ }
268
+ };
269
+
270
+ // src/url.ts
271
+ function idSegments(route, byId) {
272
+ const segments = [];
273
+ let current = route;
274
+ while (current) {
275
+ if (!current.index) segments.unshift(current.id);
276
+ current = current.parentId ? byId.get(current.parentId) : void 0;
277
+ }
278
+ return segments;
279
+ }
280
+ function computePath(route, byId) {
281
+ if (route.path) return route.path;
282
+ const segments = idSegments(route, byId);
283
+ if (route.slug) {
284
+ let ri = 0;
285
+ for (const s of route.slug) {
286
+ if (s.startsWith("/regex:")) {
287
+ segments.push(ri === 0 ? ":slug" : `:slug${ri}`);
288
+ ri++;
289
+ } else {
290
+ segments.push(s);
291
+ }
292
+ }
293
+ }
294
+ return "/" + segments.join("/");
295
+ }
296
+
297
+ // src/client/rpc/Remote.ts
298
+ var Remote = class {
299
+ _remoteOpts;
300
+ /**
301
+ * @param opts опции сервиса.
302
+ */
303
+ constructor(opts) {
304
+ this._remoteOpts = opts;
305
+ }
306
+ /**
307
+ * Получить имя метода объекта для CRUD-операции.
308
+ *
309
+ * @param op CRUD-операция.
310
+ * @returns имя метода (`Read`, `List`, ...).
311
+ */
312
+ resolveMethod(op) {
313
+ const binding = this._remoteOpts.binding ?? {};
314
+ return binding[op] ?? DEFAULT_BINDING[op];
315
+ }
316
+ /**
317
+ * Заглушка; полная реализация в R5.
318
+ *
319
+ * @param _method имя метода объекта.
320
+ * @param _params параметры вызова.
321
+ * @returns `Response`.
322
+ */
323
+ async _call(_method, _params) {
324
+ throw new Error("Remote: not implemented yet (Phase R5)");
325
+ }
326
+ // Поля-заглушки, чтобы `T` не был unused.
327
+ _phantom;
328
+ };
329
+ var DEFAULT_BINDING = {
330
+ read: "Read",
331
+ query: "List",
332
+ create: "Create",
333
+ update: "Update",
334
+ destroy: "Delete"
335
+ };
336
+
337
+ // src/client/rpc/server-bridge.ts
338
+ function isServer() {
339
+ return typeof window === "undefined";
340
+ }
341
+ function getCurrentHeaders() {
342
+ if (!isServer()) return {};
343
+ const getCtx = globalThis.__nixoRpcGetContext;
344
+ return getCtx?.()?.headers ?? {};
345
+ }
346
+ function getServerExecutor() {
347
+ if (!isServer()) return null;
348
+ const getCtx = globalThis.__nixoRpcGetContext;
349
+ return getCtx?.()?.executor ?? null;
350
+ }
351
+ function getServerCache() {
352
+ if (!isServer()) return null;
353
+ const getCtx = globalThis.__nixoRpcGetContext;
354
+ return getCtx?.()?.cache ?? null;
355
+ }
356
+ var clientServiceCache = /* @__PURE__ */ new Map();
357
+ var Service = class extends Remote {
358
+ _serviceOpts;
359
+ /**
360
+ * @param opts опции.
361
+ */
362
+ constructor(opts = {}) {
363
+ super(opts);
364
+ this._serviceOpts = opts;
365
+ }
366
+ /**
367
+ * Ключ кэша: `endpoint.method:devalueStringify(params)`.
368
+ *
369
+ * @param method метод.
370
+ * @param params параметры.
371
+ * @returns ключ.
372
+ */
373
+ getCacheKey(method, params) {
374
+ const endpoint = this._serviceOpts.endpoint ?? "";
375
+ return `${endpoint}.${method}:${stringify(params ?? {})}`;
376
+ }
377
+ /**
378
+ * Хранилище кэша: ALS (SSR) или модульный `Map` (клиент).
379
+ *
380
+ * @returns `Map` или `null`.
381
+ */
382
+ getCacheStore() {
383
+ if (isServer()) return getServerCache();
384
+ return clientServiceCache;
385
+ }
386
+ /**
387
+ * Низкоуровневый JSON-RPC вызов (без кэша).
388
+ *
389
+ * @param method имя метода.
390
+ * @param params параметры.
391
+ * @returns `Response`.
392
+ */
393
+ async callRaw(method, params) {
394
+ const fullMethod = this._serviceOpts.endpoint ? `${this._serviceOpts.endpoint}.${method}` : method;
395
+ const payload = JSON.stringify({
396
+ jsonrpc: "2.0",
397
+ id: 1,
398
+ method: fullMethod,
399
+ params: params ?? {}
400
+ });
401
+ return this.executeRequest(payload);
402
+ }
403
+ /**
404
+ * JSON-RPC вызов с кэшем.
405
+ *
406
+ * @param method имя метода.
407
+ * @param params параметры.
408
+ * @param opts per-call опции (`{ cache }`).
409
+ * @returns `result`.
410
+ * @throws {NixoRpcError} при `error` в ответе.
411
+ */
412
+ async call(method, params, opts) {
413
+ const cacheTtl = opts?.cache !== void 0 ? opts.cache : this._serviceOpts.cache;
414
+ const useCache = typeof cacheTtl === "number" && cacheTtl > 0;
415
+ const store = useCache ? this.getCacheStore() : null;
416
+ const cacheKey = useCache ? this.getCacheKey(method, params) : "";
417
+ if (store && cacheKey) {
418
+ const cached = store.get(cacheKey);
419
+ if (cached && cached.expiresAt > Date.now()) {
420
+ return cached.value;
421
+ }
422
+ if (cached) store.delete(cacheKey);
423
+ }
424
+ const res = await this.callRaw(method, params);
425
+ const data = await res.json();
426
+ if (data.error) throw new NixoRpcError(data.error);
427
+ const result = data.result;
428
+ if (store && cacheKey && typeof cacheTtl === "number") {
429
+ store.set(cacheKey, {
430
+ value: result,
431
+ expiresAt: Date.now() + cacheTtl
432
+ });
433
+ }
434
+ return result;
435
+ }
436
+ /**
437
+ * Инвалидирует кэш.
438
+ *
439
+ * - `invalidate()` — всё для текущего `endpoint`.
440
+ * - `invalidate(method)` — все ключи метода.
441
+ * - `invalidate(method, params)` — точечно.
442
+ *
443
+ * @param method метод.
444
+ * @param params параметры.
445
+ */
446
+ invalidate(method, params) {
447
+ const store = this.getCacheStore();
448
+ if (!store) return;
449
+ const endpoint = this._serviceOpts.endpoint ?? "";
450
+ if (method === void 0) {
451
+ const prefix = `${endpoint}.`;
452
+ for (const key of store.keys()) {
453
+ if (key.startsWith(prefix)) store.delete(key);
454
+ }
455
+ return;
456
+ }
457
+ if (params === void 0) {
458
+ const prefix = `${endpoint}.${method}:`;
459
+ for (const key of store.keys()) {
460
+ if (key.startsWith(prefix)) store.delete(key);
461
+ }
462
+ return;
463
+ }
464
+ store.delete(this.getCacheKey(method, params));
465
+ }
466
+ /**
467
+ * Очищает **всё** клиентский кэш (кросс-инстанс).
468
+ *
469
+ * На сервере — no-op (per-request кэш в ALS всё равно уничтожится).
470
+ */
471
+ static invalidateAll() {
472
+ clientServiceCache.clear();
473
+ }
474
+ /**
475
+ * CRUD: query (`List` по дефолту).
476
+ *
477
+ * @param params параметры.
478
+ * @param opts per-call опции.
479
+ * @returns `result`.
480
+ */
481
+ async query(params, opts) {
482
+ return this.call(this.resolveMethod("query"), params, opts);
483
+ }
484
+ /**
485
+ * CRUD: read (`Read` по дефолту).
486
+ *
487
+ * @param params параметры.
488
+ * @param opts per-call опции.
489
+ * @returns `result`.
490
+ */
491
+ async read(params, opts) {
492
+ return this.call(this.resolveMethod("read"), params, opts);
493
+ }
494
+ /**
495
+ * CRUD: create (`Create` по дефолту).
496
+ *
497
+ * @param params параметры.
498
+ * @param opts per-call опции.
499
+ * @returns `result`.
500
+ */
501
+ async create(params, opts) {
502
+ return this.call(this.resolveMethod("create"), params, opts);
503
+ }
504
+ /**
505
+ * CRUD: update (`Update` по дефолту).
506
+ *
507
+ * @param params параметры.
508
+ * @param opts per-call опции.
509
+ * @returns `result`.
510
+ */
511
+ async update(params, opts) {
512
+ return this.call(this.resolveMethod("update"), params, opts);
513
+ }
514
+ /**
515
+ * CRUD: destroy (`Delete` по дефолту).
516
+ *
517
+ * @param params параметры.
518
+ * @param opts per-call опции.
519
+ * @returns `result`.
520
+ */
521
+ async destroy(params, opts) {
522
+ return this.call(this.resolveMethod("destroy"), params, opts);
523
+ }
524
+ /**
525
+ * URL endpoint.
526
+ *
527
+ * @returns URL.
528
+ */
529
+ getUrl() {
530
+ return this._serviceOpts.url ?? "/service";
531
+ }
532
+ /**
533
+ * Заголовки (на сервере — из ALS).
534
+ *
535
+ * @returns headers.
536
+ */
537
+ prepareHeaders() {
538
+ const base = { "content-type": "application/json" };
539
+ if (isServer()) {
540
+ Object.assign(base, getCurrentHeaders());
541
+ }
542
+ return base;
543
+ }
544
+ /**
545
+ * Изоморфный execute:
546
+ * - SSR + executor → прямой вызов (без HTTP);
547
+ * - иначе → fetch.
548
+ *
549
+ * @param payload JSON-RPC payload.
550
+ * @returns `Response`.
551
+ */
552
+ async executeRequest(payload) {
553
+ if (isServer()) {
554
+ const executor = getServerExecutor();
555
+ if (executor) return executor(payload, this.prepareHeaders());
556
+ }
557
+ return fetch(this.getUrl(), {
558
+ method: "POST",
559
+ headers: this.prepareHeaders(),
560
+ body: payload,
561
+ credentials: "same-origin"
562
+ });
563
+ }
564
+ };
565
+
566
+ // src/shared/prefetch.ts
567
+ async function runGlobalPrefetch(ctx, opts = {}) {
568
+ const { route, globalRegistry, loadModule, registry, router } = ctx;
569
+ const states = /* @__PURE__ */ new Map();
570
+ const slices = /* @__PURE__ */ new Map();
571
+ const needed = new Set(
572
+ route.contentConfig.prefetchConfig.dependencies ?? []
573
+ );
574
+ const queue = [...needed];
575
+ while (queue.length > 0) {
576
+ const name = queue.shift();
577
+ const def = globalRegistry[name];
578
+ if (!def) continue;
579
+ for (const dep of def.dependencies ?? []) {
580
+ if (!needed.has(dep)) {
581
+ needed.add(dep);
582
+ queue.push(dep);
583
+ }
584
+ }
585
+ }
586
+ if (opts.skipExisting) {
587
+ for (const name of [...needed]) {
588
+ if (registry.get(name)) needed.delete(name);
589
+ }
590
+ }
591
+ if (needed.size === 0) return states;
592
+ const sorted = topoSort(needed, (name) => {
593
+ const def = globalRegistry[name];
594
+ if (!def) {
595
+ throw new NixoError(
596
+ `Global dependency "${name}" not found in any *.loader.ts`
597
+ );
598
+ }
599
+ return def.dependencies ?? [];
600
+ });
601
+ for (const name of sorted) {
602
+ const def = globalRegistry[name];
603
+ const implRaw = await loadModule(def.dataFactoryName);
604
+ const impl = implRaw;
605
+ if (!impl || typeof impl.loadData !== "function" || typeof impl.slice !== "function") {
606
+ throw new NixoError(
607
+ `Invalid global loader "${name}": expected loadData + slice class`
608
+ );
609
+ }
610
+ const args = { ...def.dataFactoryArguments ?? {} };
611
+ if (typeof impl.getDataFactoryArguments === "function") {
612
+ Object.assign(args, await impl.getDataFactoryArguments());
613
+ }
614
+ const deps = {};
615
+ for (const dep of def.dependencies ?? []) deps[dep] = states.get(dep);
616
+ const data = await impl.loadData(args, deps, { router });
617
+ states.set(name, data);
618
+ slices.set(name, {
619
+ dataFactoryName: def.dataFactoryName,
620
+ state: data,
621
+ scope: "global"
622
+ });
623
+ const SliceClass = impl.slice;
624
+ const slice = new SliceClass();
625
+ slice.hydrate(data);
626
+ registry.setGlobal(name, slice);
627
+ }
628
+ return states;
629
+ }
630
+ async function runPagePrefetch(ctx, globalStates) {
631
+ const { route, globalRegistry, loadModule, registry, router } = ctx;
632
+ const prefetchCfg = route.contentConfig.prefetchConfig;
633
+ const states = new Map(globalStates);
634
+ const slices = /* @__PURE__ */ new Map();
635
+ const getConfigRaw = await loadModule(prefetchCfg.configLoader);
636
+ if (typeof getConfigRaw !== "function") {
637
+ throw new NixoError(
638
+ `configLoader "${prefetchCfg.configLoader}" must export a function`
639
+ );
640
+ }
641
+ const factoryMap = await getConfigRaw(
642
+ prefetchCfg.configParams ?? {},
643
+ router
644
+ );
645
+ if (!factoryMap || typeof factoryMap !== "object") {
646
+ throw new NixoError(
647
+ `getConfig from "${prefetchCfg.configLoader}" must return an object`
648
+ );
649
+ }
650
+ for (const name of Object.keys(factoryMap)) {
651
+ if (globalRegistry[name]) {
652
+ throw new NixoError(
653
+ `A3 violation: "${name}" declared both in page's getConfig and global registry`
654
+ );
655
+ }
656
+ }
657
+ const needed = new Set(Object.keys(factoryMap));
658
+ const queue = [...needed];
659
+ while (queue.length > 0) {
660
+ const name = queue.shift();
661
+ const def = factoryMap[name];
662
+ if (!def) continue;
663
+ for (const dep of def.dependencies ?? []) {
664
+ if (!needed.has(dep) && !globalStates.has(dep)) {
665
+ needed.add(dep);
666
+ queue.push(dep);
667
+ }
668
+ }
669
+ }
670
+ const sorted = topoSort(needed, (name) => {
671
+ const def = factoryMap[name];
672
+ if (!def) {
673
+ if (globalStates.has(name)) return [];
674
+ throw new NixoError(
675
+ `Unknown dependency "${name}" \u2014 not in getConfig, not a loaded global`
676
+ );
677
+ }
678
+ return def.dependencies ?? [];
679
+ });
680
+ for (const name of sorted) {
681
+ const def = factoryMap[name];
682
+ const implRaw = await loadModule(def.dataFactoryName);
683
+ const impl = implRaw;
684
+ if (!impl || typeof impl.loadData !== "function" || typeof impl.slice !== "function") {
685
+ throw new NixoError(`Invalid factory "${name}": expected loadData + slice`);
686
+ }
687
+ const args = { ...def.dataFactoryArguments ?? {} };
688
+ if (typeof impl.getDataFactoryArguments === "function") {
689
+ Object.assign(args, await impl.getDataFactoryArguments());
690
+ }
691
+ const deps = {};
692
+ for (const dep of def.dependencies ?? []) deps[dep] = states.get(dep);
693
+ const data = await impl.loadData(args, deps, { router });
694
+ states.set(name, data);
695
+ slices.set(name, {
696
+ dataFactoryName: def.dataFactoryName,
697
+ state: data,
698
+ scope: "page"
699
+ });
700
+ const SliceClass = impl.slice;
701
+ const slice = new SliceClass();
702
+ slice.hydrate(data);
703
+ registry.setPage(name, slice);
704
+ }
705
+ for (const [name, data] of globalStates) {
706
+ const def = globalRegistry[name];
707
+ if (def) {
708
+ slices.set(name, {
709
+ dataFactoryName: def.dataFactoryName,
710
+ state: data,
711
+ scope: "global"
712
+ });
713
+ }
714
+ }
715
+ return { states, slices };
716
+ }
717
+ async function runPrefetch(ctx) {
718
+ const globalStates = await runGlobalPrefetch(ctx);
719
+ return runPagePrefetch(ctx, globalStates);
720
+ }
721
+ function topoSort(nodes, depsOf) {
722
+ const result = [];
723
+ const state = /* @__PURE__ */ new Map();
724
+ const visit = (name, pathStack) => {
725
+ const s = state.get(name);
726
+ if (s === "done") return;
727
+ if (s === "visiting") {
728
+ throw new NixoError(
729
+ `Cycle detected: ${[...pathStack, name].join(" \u2192 ")}`
730
+ );
731
+ }
732
+ state.set(name, "visiting");
733
+ for (const dep of depsOf(name)) visit(dep, [...pathStack, name]);
734
+ state.set(name, "done");
735
+ result.push(name);
736
+ };
737
+ for (const name of nodes) visit(name, []);
738
+ return result;
739
+ }
740
+ createContext(null);
741
+ createContext(null);
742
+ var NixoErrorBoundary = class extends Component {
743
+ state = { error: null };
744
+ /**
745
+ * @param error ошибка из дочернего дерева.
746
+ * @returns новое состояние.
747
+ */
748
+ static getDerivedStateFromError(error) {
749
+ return { error };
750
+ }
751
+ /**
752
+ * Логирует ошибку.
753
+ *
754
+ * @param error ошибка.
755
+ * @param info React error info.
756
+ */
757
+ componentDidCatch(error, info) {
758
+ console.error("[nixo] error boundary caught:", error, info);
759
+ }
760
+ /**
761
+ * Рендер: fallback при ошибке, иначе `children`.
762
+ *
763
+ * @returns ReactNode.
764
+ */
765
+ render() {
766
+ if (this.state.error) {
767
+ const Fallback = this.props.fallback;
768
+ if (Fallback) {
769
+ return createElement(Fallback, { error: this.state.error });
770
+ }
771
+ return createElement(
772
+ "div",
773
+ {
774
+ style: {
775
+ padding: "1rem",
776
+ color: "crimson",
777
+ fontFamily: "system-ui, sans-serif"
778
+ }
779
+ },
780
+ "Error: " + this.state.error.message
781
+ );
782
+ }
783
+ return this.props.children;
784
+ }
785
+ };
786
+ function NixoLoading() {
787
+ return createElement(
788
+ "div",
789
+ {
790
+ style: {
791
+ padding: "1rem",
792
+ color: "#666",
793
+ fontFamily: "system-ui, sans-serif"
794
+ }
795
+ },
796
+ "Loading\u2026"
797
+ );
798
+ }
799
+ function NixoNotFound() {
800
+ return createElement(
801
+ "div",
802
+ {
803
+ style: {
804
+ padding: "3rem 1rem",
805
+ textAlign: "center",
806
+ fontFamily: "system-ui, sans-serif"
807
+ }
808
+ },
809
+ createElement("h1", { style: { fontSize: "3rem", margin: 0 } }, "404"),
810
+ createElement("p", { style: { color: "#666" } }, "Page not found")
811
+ );
812
+ }
813
+
814
+ export { AbstractSlice, NixoError, NixoErrorBoundary, NixoLoading, NixoNotFound, NixoRpcError, Service as NixoService, Slice, SliceRegistry, computePath, defineGlobalLoader, defineNixoConfig, defineRoute, runGlobalPrefetch, runPagePrefetch, runPrefetch };
815
+ //# sourceMappingURL=index.js.map
816
+ //# sourceMappingURL=index.js.map