@almadar/ui 2.2.0 → 2.5.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,1033 @@
1
+ import { __publicField } from './chunk-PKBMQBKP.js';
2
+ import { createContext, useRef, useCallback, useEffect, useContext, useState, useMemo } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import { componentMapping as componentMapping$1, patternsRegistry } from '@almadar/patterns';
5
+
6
+ // renderer/pattern-resolver.ts
7
+ var componentMapping = {};
8
+ var patternRegistry = {};
9
+ function initializePatternResolver(config) {
10
+ componentMapping = config.componentMapping;
11
+ patternRegistry = config.patternRegistry;
12
+ }
13
+ function setComponentMapping(mapping) {
14
+ componentMapping = mapping;
15
+ }
16
+ function setPatternRegistry(registry) {
17
+ patternRegistry = registry;
18
+ }
19
+ function resolvePattern(config) {
20
+ const { type, ...props } = config;
21
+ const mapping = componentMapping[type];
22
+ if (!mapping) {
23
+ if (Object.keys(componentMapping).length === 0) {
24
+ console.warn(
25
+ "[PatternResolver] Component mapping not initialized. Call initializePatternResolver() at app startup."
26
+ );
27
+ }
28
+ throw new Error(`Unknown pattern type: ${type}`);
29
+ }
30
+ if (mapping.deprecated) {
31
+ console.warn(
32
+ `[PatternResolver] Pattern "${type}" is deprecated.` + (mapping.replacedBy ? ` Use "${mapping.replacedBy}" instead.` : "")
33
+ );
34
+ }
35
+ const validatedProps = validatePatternProps(type, props);
36
+ return {
37
+ component: mapping.component,
38
+ importPath: mapping.importPath,
39
+ category: mapping.category,
40
+ validatedProps
41
+ };
42
+ }
43
+ function validatePatternProps(patternType, props) {
44
+ const definition = patternRegistry[patternType];
45
+ if (!definition || !definition.propsSchema) {
46
+ return props;
47
+ }
48
+ const validated = { ...props };
49
+ const schema = definition.propsSchema;
50
+ for (const [propName, propDef] of Object.entries(schema)) {
51
+ if (propDef.required && !(propName in validated)) {
52
+ console.warn(
53
+ `[PatternResolver] Missing required prop "${propName}" for pattern "${patternType}"`
54
+ );
55
+ }
56
+ }
57
+ return validated;
58
+ }
59
+ function isKnownPattern(type) {
60
+ return type in componentMapping;
61
+ }
62
+ function getKnownPatterns() {
63
+ return Object.keys(componentMapping);
64
+ }
65
+ function getPatternsByCategory(category) {
66
+ return Object.entries(componentMapping).filter(([, mapping]) => mapping.category === category).map(([type]) => type);
67
+ }
68
+ function getPatternMapping(type) {
69
+ return componentMapping[type];
70
+ }
71
+ function getPatternDefinition(type) {
72
+ return patternRegistry[type];
73
+ }
74
+
75
+ // renderer/client-effect-executor.ts
76
+ function executeClientEffects(effects, config) {
77
+ if (!effects || effects.length === 0) {
78
+ return;
79
+ }
80
+ for (const effect of effects) {
81
+ try {
82
+ executeEffect(effect, config);
83
+ } catch (error) {
84
+ console.error(
85
+ `[ClientEffectExecutor] Error executing effect:`,
86
+ effect,
87
+ error
88
+ );
89
+ }
90
+ }
91
+ config.onComplete?.();
92
+ }
93
+ function executeEffect(effect, config) {
94
+ const [effectType, ...args] = effect;
95
+ switch (effectType) {
96
+ case "render-ui": {
97
+ const [slot, patternConfig] = args;
98
+ executeRenderUI(slot, patternConfig, config);
99
+ break;
100
+ }
101
+ case "navigate": {
102
+ const [path, params] = args;
103
+ executeNavigate(path, params, config);
104
+ break;
105
+ }
106
+ case "notify": {
107
+ const [message, options] = args;
108
+ executeNotify(message, options, config);
109
+ break;
110
+ }
111
+ case "emit": {
112
+ const [event, payload] = args;
113
+ executeEmit(event, payload, config);
114
+ break;
115
+ }
116
+ default:
117
+ console.warn(`[ClientEffectExecutor] Unknown effect type: ${effectType}`);
118
+ }
119
+ }
120
+ function executeRenderUI(slot, patternConfig, config) {
121
+ config.renderToSlot(slot, patternConfig);
122
+ }
123
+ function executeNavigate(path, params, config) {
124
+ config.navigate(path, params);
125
+ }
126
+ function executeNotify(message, options, config) {
127
+ config.notify(message, options);
128
+ }
129
+ function executeEmit(event, payload, config) {
130
+ config.eventBus.emit(event, payload);
131
+ }
132
+ function parseClientEffect(raw) {
133
+ if (!Array.isArray(raw) || raw.length < 1) {
134
+ console.warn("[ClientEffectExecutor] Invalid effect format:", raw);
135
+ return null;
136
+ }
137
+ const [type, ...args] = raw;
138
+ if (typeof type !== "string") {
139
+ console.warn("[ClientEffectExecutor] Effect type must be string:", raw);
140
+ return null;
141
+ }
142
+ switch (type) {
143
+ case "render-ui":
144
+ return ["render-ui", args[0], args[1]];
145
+ case "navigate":
146
+ return ["navigate", args[0], args[1]];
147
+ case "notify":
148
+ return ["notify", args[0], args[1]];
149
+ case "emit":
150
+ return ["emit", args[0], args[1]];
151
+ default:
152
+ console.warn(`[ClientEffectExecutor] Unknown effect type: ${type}`);
153
+ return null;
154
+ }
155
+ }
156
+ function parseClientEffects(raw) {
157
+ if (!raw || !Array.isArray(raw)) {
158
+ return [];
159
+ }
160
+ return raw.map((effect) => parseClientEffect(effect)).filter((effect) => effect !== null);
161
+ }
162
+ function filterEffectsByType(effects, type) {
163
+ return effects.filter(
164
+ (effect) => effect[0] === type
165
+ );
166
+ }
167
+ function getRenderUIEffects(effects) {
168
+ return filterEffectsByType(effects, "render-ui");
169
+ }
170
+ function getNavigateEffects(effects) {
171
+ return filterEffectsByType(effects, "navigate");
172
+ }
173
+ function getNotifyEffects(effects) {
174
+ return filterEffectsByType(effects, "notify");
175
+ }
176
+ function getEmitEffects(effects) {
177
+ return filterEffectsByType(effects, "emit");
178
+ }
179
+ function useClientEffects(effects, options) {
180
+ const {
181
+ enabled = true,
182
+ debug = false,
183
+ onComplete,
184
+ ...config
185
+ } = options;
186
+ const executedRef = useRef(/* @__PURE__ */ new Set());
187
+ const executingRef = useRef(false);
188
+ const executedCountRef = useRef(0);
189
+ const getEffectKey = useCallback((effect) => {
190
+ return JSON.stringify(effect);
191
+ }, []);
192
+ const execute = useCallback((effectsToExecute) => {
193
+ if (executingRef.current || effectsToExecute.length === 0) {
194
+ return;
195
+ }
196
+ executingRef.current = true;
197
+ const newEffects = effectsToExecute.filter((effect) => {
198
+ const key = getEffectKey(effect);
199
+ if (executedRef.current.has(key)) {
200
+ if (debug) {
201
+ console.log("[useClientEffects] Skipping duplicate effect:", effect);
202
+ }
203
+ return false;
204
+ }
205
+ return true;
206
+ });
207
+ if (newEffects.length === 0) {
208
+ executingRef.current = false;
209
+ return;
210
+ }
211
+ if (debug) {
212
+ console.log("[useClientEffects] Executing effects:", newEffects);
213
+ }
214
+ newEffects.forEach((effect) => {
215
+ executedRef.current.add(getEffectKey(effect));
216
+ });
217
+ executeClientEffects(newEffects, {
218
+ ...config,
219
+ onComplete: () => {
220
+ executedCountRef.current = newEffects.length;
221
+ executingRef.current = false;
222
+ onComplete?.();
223
+ }
224
+ });
225
+ }, [config, debug, getEffectKey, onComplete]);
226
+ useEffect(() => {
227
+ if (!enabled || !effects || effects.length === 0) {
228
+ return;
229
+ }
230
+ execute(effects);
231
+ }, [effects, enabled, execute]);
232
+ const prevEffectsRef = useRef(void 0);
233
+ useEffect(() => {
234
+ if (effects !== prevEffectsRef.current) {
235
+ prevEffectsRef.current = effects;
236
+ }
237
+ }, [effects]);
238
+ return {
239
+ executedCount: executedCountRef.current,
240
+ executing: executingRef.current,
241
+ execute
242
+ };
243
+ }
244
+ var ClientEffectConfigContext = createContext(null);
245
+ var ClientEffectConfigProvider = ClientEffectConfigContext.Provider;
246
+ function useClientEffectConfig() {
247
+ const context = useContext(ClientEffectConfigContext);
248
+ if (!context) {
249
+ throw new Error(
250
+ "useClientEffectConfig must be used within a ClientEffectConfigProvider. Make sure your component tree is wrapped with OrbitalProvider."
251
+ );
252
+ }
253
+ return context;
254
+ }
255
+ function useClientEffectConfigOptional() {
256
+ return useContext(ClientEffectConfigContext);
257
+ }
258
+
259
+ // renderer/data-resolver.ts
260
+ function resolveEntityData(entityName, context) {
261
+ if (context.fetchedData && entityName in context.fetchedData) {
262
+ const data = context.fetchedData[entityName];
263
+ return {
264
+ data: Array.isArray(data) ? data : [],
265
+ loading: false
266
+ };
267
+ }
268
+ if (context.entityStore) {
269
+ try {
270
+ const data = context.entityStore.getRecords(entityName);
271
+ return {
272
+ data: Array.isArray(data) ? data : [],
273
+ loading: false
274
+ };
275
+ } catch (error) {
276
+ console.warn(
277
+ `[DataResolver] Error getting records from entity store for "${entityName}":`,
278
+ error
279
+ );
280
+ }
281
+ }
282
+ const hasAnySources = context.fetchedData || context.entityStore;
283
+ return {
284
+ data: [],
285
+ loading: !hasAnySources
286
+ // Only loading if no sources configured
287
+ };
288
+ }
289
+ function resolveEntityDataWithQuery(entityName, queryRef, context) {
290
+ const resolution = resolveEntityData(entityName, context);
291
+ if (!queryRef || !context.querySingleton) {
292
+ return resolution;
293
+ }
294
+ try {
295
+ const filters = context.querySingleton.getFilters(queryRef);
296
+ const filteredData = applyFilters(resolution.data, filters);
297
+ return {
298
+ ...resolution,
299
+ data: filteredData
300
+ };
301
+ } catch (error) {
302
+ console.warn(
303
+ `[DataResolver] Error applying query filters for "${queryRef}":`,
304
+ error
305
+ );
306
+ return resolution;
307
+ }
308
+ }
309
+ function applyFilters(data, filters) {
310
+ if (!filters || Object.keys(filters).length === 0) {
311
+ return data;
312
+ }
313
+ return data.filter((item) => {
314
+ if (typeof item !== "object" || item === null) {
315
+ return false;
316
+ }
317
+ const record = item;
318
+ return Object.entries(filters).every(([key, value]) => {
319
+ if (value === void 0 || value === null) {
320
+ return true;
321
+ }
322
+ const recordValue = record[key];
323
+ if (Array.isArray(value)) {
324
+ return value.includes(recordValue);
325
+ }
326
+ if (typeof value === "string" && typeof recordValue === "string") {
327
+ if (value.startsWith("*") && value.endsWith("*")) {
328
+ const pattern = value.slice(1, -1);
329
+ return recordValue.toLowerCase().includes(pattern.toLowerCase());
330
+ }
331
+ }
332
+ return recordValue === value;
333
+ });
334
+ });
335
+ }
336
+ function resolveEntityById(entityName, id, context) {
337
+ const { data } = resolveEntityData(entityName, context);
338
+ return data.find((item) => {
339
+ if (typeof item !== "object" || item === null) {
340
+ return false;
341
+ }
342
+ const record = item;
343
+ return record.id === id || record._id === id;
344
+ }) ?? null;
345
+ }
346
+ function resolveEntityCount(entityName, context, filters) {
347
+ const { data } = resolveEntityData(entityName, context);
348
+ if (filters) {
349
+ return applyFilters(data, filters).length;
350
+ }
351
+ return data.length;
352
+ }
353
+ function hasEntities(entityName, context) {
354
+ const { data } = resolveEntityData(entityName, context);
355
+ return data.length > 0;
356
+ }
357
+ function createFetchedDataContext(data) {
358
+ return { fetchedData: data };
359
+ }
360
+ function mergeDataContexts(...contexts) {
361
+ const merged = {};
362
+ for (const context of contexts) {
363
+ if (context.fetchedData) {
364
+ merged.fetchedData = {
365
+ ...merged.fetchedData,
366
+ ...context.fetchedData
367
+ };
368
+ }
369
+ if (context.entityStore) {
370
+ merged.entityStore = context.entityStore;
371
+ }
372
+ if (context.querySingleton) {
373
+ merged.querySingleton = context.querySingleton;
374
+ }
375
+ }
376
+ return merged;
377
+ }
378
+
379
+ // renderer/slot-definitions.ts
380
+ var SLOT_DEFINITIONS = {
381
+ // -------------------------------------------------------------------------
382
+ // Inline Slots - Render in place within the component tree
383
+ // -------------------------------------------------------------------------
384
+ main: {
385
+ name: "main",
386
+ type: "inline"
387
+ },
388
+ sidebar: {
389
+ name: "sidebar",
390
+ type: "inline"
391
+ },
392
+ // -------------------------------------------------------------------------
393
+ // Portal Slots - Render to document.body via React Portal
394
+ // -------------------------------------------------------------------------
395
+ modal: {
396
+ name: "modal",
397
+ type: "portal",
398
+ portalTarget: "body",
399
+ zIndex: 1e3
400
+ },
401
+ drawer: {
402
+ name: "drawer",
403
+ type: "portal",
404
+ portalTarget: "body",
405
+ zIndex: 900
406
+ },
407
+ overlay: {
408
+ name: "overlay",
409
+ type: "portal",
410
+ portalTarget: "body",
411
+ zIndex: 1100
412
+ },
413
+ center: {
414
+ name: "center",
415
+ type: "portal",
416
+ portalTarget: "body",
417
+ zIndex: 1e3
418
+ },
419
+ toast: {
420
+ name: "toast",
421
+ type: "portal",
422
+ portalTarget: "body",
423
+ zIndex: 1200
424
+ },
425
+ // -------------------------------------------------------------------------
426
+ // Game HUD Slots - Portal for game overlay UI
427
+ // -------------------------------------------------------------------------
428
+ "hud-top": {
429
+ name: "hud-top",
430
+ type: "portal",
431
+ portalTarget: "body",
432
+ zIndex: 500
433
+ },
434
+ "hud-bottom": {
435
+ name: "hud-bottom",
436
+ type: "portal",
437
+ portalTarget: "body",
438
+ zIndex: 500
439
+ },
440
+ floating: {
441
+ name: "floating",
442
+ type: "portal",
443
+ portalTarget: "body",
444
+ zIndex: 800
445
+ }
446
+ };
447
+ function getSlotDefinition(slot) {
448
+ return SLOT_DEFINITIONS[slot];
449
+ }
450
+ function isPortalSlot(slot) {
451
+ return SLOT_DEFINITIONS[slot]?.type === "portal";
452
+ }
453
+ function isInlineSlot(slot) {
454
+ return SLOT_DEFINITIONS[slot]?.type === "inline";
455
+ }
456
+ function getSlotsByType(type) {
457
+ return Object.entries(SLOT_DEFINITIONS).filter(([, def]) => def.type === type).map(([name]) => name);
458
+ }
459
+ function getInlineSlots() {
460
+ return getSlotsByType("inline");
461
+ }
462
+ function getPortalSlots() {
463
+ return getSlotsByType("portal");
464
+ }
465
+ var ALL_SLOTS = Object.keys(SLOT_DEFINITIONS);
466
+ var effectIdCounter = 0;
467
+ function generateEffectId() {
468
+ return `offline-effect-${++effectIdCounter}-${Date.now()}`;
469
+ }
470
+ var OfflineExecutor = class {
471
+ constructor(config) {
472
+ __publicField(this, "config");
473
+ __publicField(this, "state");
474
+ __publicField(this, "storage");
475
+ /**
476
+ * Handle going online
477
+ */
478
+ __publicField(this, "handleOnline", () => {
479
+ this.state.isOffline = false;
480
+ });
481
+ /**
482
+ * Handle going offline
483
+ */
484
+ __publicField(this, "handleOffline", () => {
485
+ this.state.isOffline = true;
486
+ });
487
+ this.config = {
488
+ enableSyncQueue: true,
489
+ maxQueueSize: 100,
490
+ ...config
491
+ };
492
+ this.state = {
493
+ isOffline: !this.checkOnline(),
494
+ syncQueue: [],
495
+ localEffectsProcessed: 0,
496
+ effectsSynced: 0
497
+ };
498
+ this.storage = typeof localStorage !== "undefined" ? localStorage : null;
499
+ this.loadSyncQueue();
500
+ if (typeof window !== "undefined") {
501
+ window.addEventListener("online", this.handleOnline);
502
+ window.addEventListener("offline", this.handleOffline);
503
+ }
504
+ }
505
+ /**
506
+ * Check if we're online (browser API)
507
+ */
508
+ checkOnline() {
509
+ return typeof navigator !== "undefined" ? navigator.onLine : true;
510
+ }
511
+ /**
512
+ * Load sync queue from localStorage
513
+ */
514
+ loadSyncQueue() {
515
+ if (!this.storage) return;
516
+ try {
517
+ const stored = this.storage.getItem("orbital-offline-queue");
518
+ if (stored) {
519
+ this.state.syncQueue = JSON.parse(stored);
520
+ }
521
+ } catch (error) {
522
+ console.warn("[OfflineExecutor] Failed to load sync queue:", error);
523
+ }
524
+ }
525
+ /**
526
+ * Save sync queue to localStorage
527
+ */
528
+ saveSyncQueue() {
529
+ if (!this.storage) return;
530
+ try {
531
+ this.storage.setItem("orbital-offline-queue", JSON.stringify(this.state.syncQueue));
532
+ } catch (error) {
533
+ console.warn("[OfflineExecutor] Failed to save sync queue:", error);
534
+ }
535
+ }
536
+ /**
537
+ * Add an effect to the sync queue
538
+ */
539
+ queueForSync(type, payload) {
540
+ if (!this.config.enableSyncQueue) return;
541
+ const effect = {
542
+ id: generateEffectId(),
543
+ timestamp: Date.now(),
544
+ type,
545
+ payload,
546
+ retries: 0,
547
+ maxRetries: 3
548
+ };
549
+ this.state.syncQueue.push(effect);
550
+ if (this.state.syncQueue.length > (this.config.maxQueueSize ?? 100)) {
551
+ this.state.syncQueue.shift();
552
+ }
553
+ this.saveSyncQueue();
554
+ this.config.onEffectQueued?.(effect);
555
+ this.config.onQueueChange?.(this.state.syncQueue);
556
+ }
557
+ /**
558
+ * Execute client effects immediately.
559
+ */
560
+ executeClientEffects(effects) {
561
+ if (effects.length === 0) return;
562
+ executeClientEffects(effects, this.config);
563
+ this.state.localEffectsProcessed += effects.length;
564
+ }
565
+ /**
566
+ * Process an event in offline mode.
567
+ *
568
+ * Returns a simulated EventResponse with mock data.
569
+ * Client effects are executed immediately.
570
+ * Server effects are queued for sync.
571
+ */
572
+ processEventOffline(event, payload, effects) {
573
+ const clientEffects = [];
574
+ const fetchedData = {};
575
+ if (effects) {
576
+ for (const effect of effects) {
577
+ if (!Array.isArray(effect) || effect.length < 1) continue;
578
+ const [type, ...args] = effect;
579
+ switch (type) {
580
+ // Client effects - execute immediately
581
+ case "render-ui":
582
+ case "navigate":
583
+ case "notify":
584
+ case "emit":
585
+ clientEffects.push(effect);
586
+ break;
587
+ // Fetch effect - use mock data
588
+ case "fetch": {
589
+ const [entityName, _query] = args;
590
+ if (typeof entityName === "string" && this.config.mockDataProvider) {
591
+ fetchedData[entityName] = this.config.mockDataProvider(entityName);
592
+ }
593
+ break;
594
+ }
595
+ // Server effects - queue for sync
596
+ case "persist":
597
+ case "call-service":
598
+ case "spawn":
599
+ case "despawn":
600
+ this.queueForSync(type, { args, event, payload });
601
+ break;
602
+ default:
603
+ console.warn(`[OfflineExecutor] Unknown effect type: ${type}`);
604
+ }
605
+ }
606
+ }
607
+ if (clientEffects.length > 0) {
608
+ this.executeClientEffects(clientEffects);
609
+ }
610
+ return {
611
+ success: true,
612
+ data: Object.keys(fetchedData).length > 0 ? fetchedData : void 0,
613
+ clientEffects: clientEffects.length > 0 ? clientEffects : void 0
614
+ };
615
+ }
616
+ /**
617
+ * Sync pending effects to server.
618
+ *
619
+ * @param serverUrl - Base URL for the orbital server
620
+ * @param authToken - Optional auth token for requests
621
+ * @returns Number of successfully synced effects
622
+ */
623
+ async syncPendingEffects(serverUrl, authToken) {
624
+ if (this.state.syncQueue.length === 0) {
625
+ return 0;
626
+ }
627
+ this.state.lastSyncAttempt = Date.now();
628
+ let syncedCount = 0;
629
+ const failedEffects = [];
630
+ const headers = {
631
+ "Content-Type": "application/json"
632
+ };
633
+ if (authToken) {
634
+ headers["Authorization"] = `Bearer ${authToken}`;
635
+ }
636
+ for (const effect of this.state.syncQueue) {
637
+ try {
638
+ const response = await fetch(`${serverUrl}/sync-effect`, {
639
+ method: "POST",
640
+ headers,
641
+ body: JSON.stringify({
642
+ type: effect.type,
643
+ payload: effect.payload,
644
+ offlineId: effect.id,
645
+ offlineTimestamp: effect.timestamp
646
+ })
647
+ });
648
+ if (response.ok) {
649
+ syncedCount++;
650
+ } else {
651
+ effect.retries++;
652
+ if (effect.retries < effect.maxRetries) {
653
+ failedEffects.push(effect);
654
+ }
655
+ }
656
+ } catch (error) {
657
+ effect.retries++;
658
+ if (effect.retries < effect.maxRetries) {
659
+ failedEffects.push(effect);
660
+ }
661
+ }
662
+ }
663
+ this.state.syncQueue = failedEffects;
664
+ this.state.effectsSynced += syncedCount;
665
+ if (syncedCount > 0) {
666
+ this.state.lastSuccessfulSync = Date.now();
667
+ }
668
+ this.saveSyncQueue();
669
+ this.config.onQueueChange?.(this.state.syncQueue);
670
+ return syncedCount;
671
+ }
672
+ /**
673
+ * Get current executor state
674
+ */
675
+ getState() {
676
+ return { ...this.state };
677
+ }
678
+ /**
679
+ * Get number of pending effects
680
+ */
681
+ getPendingCount() {
682
+ return this.state.syncQueue.length;
683
+ }
684
+ /**
685
+ * Clear the sync queue
686
+ */
687
+ clearQueue() {
688
+ this.state.syncQueue = [];
689
+ this.saveSyncQueue();
690
+ this.config.onQueueChange?.(this.state.syncQueue);
691
+ }
692
+ /**
693
+ * Dispose the executor and clean up listeners
694
+ */
695
+ dispose() {
696
+ if (typeof window !== "undefined") {
697
+ window.removeEventListener("online", this.handleOnline);
698
+ window.removeEventListener("offline", this.handleOffline);
699
+ }
700
+ }
701
+ };
702
+ function createOfflineExecutor(config) {
703
+ return new OfflineExecutor(config);
704
+ }
705
+ function useOfflineExecutor(options) {
706
+ const executorRef = useRef(null);
707
+ const [state, setState] = useState({
708
+ isOffline: false,
709
+ syncQueue: [],
710
+ localEffectsProcessed: 0,
711
+ effectsSynced: 0
712
+ });
713
+ useEffect(() => {
714
+ const executor = new OfflineExecutor({
715
+ ...options,
716
+ onQueueChange: (queue) => {
717
+ setState(executor.getState());
718
+ options.onQueueChange?.(queue);
719
+ }
720
+ });
721
+ executorRef.current = executor;
722
+ setState(executor.getState());
723
+ return () => {
724
+ executor.dispose();
725
+ executorRef.current = null;
726
+ };
727
+ }, []);
728
+ useEffect(() => {
729
+ if (!options.autoSync || !options.serverUrl) return;
730
+ const handleOnline = async () => {
731
+ if (executorRef.current) {
732
+ await executorRef.current.syncPendingEffects(
733
+ options.serverUrl,
734
+ options.authToken
735
+ );
736
+ setState(executorRef.current.getState());
737
+ }
738
+ };
739
+ window.addEventListener("online", handleOnline);
740
+ return () => window.removeEventListener("online", handleOnline);
741
+ }, [options.autoSync, options.serverUrl, options.authToken]);
742
+ const executeEffects = useCallback((effects) => {
743
+ executorRef.current?.executeClientEffects(effects);
744
+ if (executorRef.current) {
745
+ setState(executorRef.current.getState());
746
+ }
747
+ }, []);
748
+ const processEventOffline = useCallback(
749
+ (event, payload, effects) => {
750
+ const result = executorRef.current?.processEventOffline(event, payload, effects);
751
+ if (executorRef.current) {
752
+ setState(executorRef.current.getState());
753
+ }
754
+ return result ?? { success: false, error: "Executor not initialized" };
755
+ },
756
+ []
757
+ );
758
+ const sync = useCallback(async () => {
759
+ if (!executorRef.current || !options.serverUrl) return 0;
760
+ const count = await executorRef.current.syncPendingEffects(
761
+ options.serverUrl,
762
+ options.authToken
763
+ );
764
+ setState(executorRef.current.getState());
765
+ return count;
766
+ }, [options.serverUrl, options.authToken]);
767
+ const clearQueue = useCallback(() => {
768
+ executorRef.current?.clearQueue();
769
+ if (executorRef.current) {
770
+ setState(executorRef.current.getState());
771
+ }
772
+ }, []);
773
+ return {
774
+ state,
775
+ isOffline: state.isOffline,
776
+ pendingCount: state.syncQueue.length,
777
+ executeClientEffects: executeEffects,
778
+ processEventOffline,
779
+ sync,
780
+ clearQueue
781
+ };
782
+ }
783
+ function matchPath(pattern, path) {
784
+ const normalizeSegment = (p) => {
785
+ let normalized = p.trim();
786
+ if (!normalized.startsWith("/")) normalized = "/" + normalized;
787
+ if (normalized.length > 1 && normalized.endsWith("/")) {
788
+ normalized = normalized.slice(0, -1);
789
+ }
790
+ return normalized;
791
+ };
792
+ const normalizedPattern = normalizeSegment(pattern);
793
+ const normalizedPath = normalizeSegment(path);
794
+ const patternParts = normalizedPattern.split("/").filter(Boolean);
795
+ const pathParts = normalizedPath.split("/").filter(Boolean);
796
+ if (patternParts.length !== pathParts.length) {
797
+ return null;
798
+ }
799
+ const params = {};
800
+ for (let i = 0; i < patternParts.length; i++) {
801
+ const patternPart = patternParts[i];
802
+ const pathPart = pathParts[i];
803
+ if (patternPart.startsWith(":")) {
804
+ const paramName = patternPart.slice(1);
805
+ params[paramName] = decodeURIComponent(pathPart);
806
+ } else if (patternPart !== pathPart) {
807
+ return null;
808
+ }
809
+ }
810
+ return params;
811
+ }
812
+ function extractRouteParams(pattern, path) {
813
+ return matchPath(pattern, path) || {};
814
+ }
815
+ function pathMatches(pattern, path) {
816
+ return matchPath(pattern, path) !== null;
817
+ }
818
+ function isInlineOrbital(orbital) {
819
+ return "name" in orbital && typeof orbital.name === "string";
820
+ }
821
+ function isInlinePage(page) {
822
+ return typeof page === "object" && page !== null && "name" in page && typeof page.name === "string";
823
+ }
824
+ function findPageByPath(schema, path) {
825
+ if (!schema.orbitals) return null;
826
+ for (const orbital of schema.orbitals) {
827
+ if (!isInlineOrbital(orbital)) continue;
828
+ if (!orbital.pages) continue;
829
+ for (const pageRef of orbital.pages) {
830
+ if (!isInlinePage(pageRef)) continue;
831
+ const page = pageRef;
832
+ const pagePath = page.path;
833
+ if (!pagePath) continue;
834
+ const params = matchPath(pagePath, path);
835
+ if (params !== null) {
836
+ return { page, params, orbitalName: orbital.name };
837
+ }
838
+ }
839
+ }
840
+ return null;
841
+ }
842
+ function findPageByName(schema, pageName) {
843
+ if (!schema.orbitals) return null;
844
+ for (const orbital of schema.orbitals) {
845
+ if (!isInlineOrbital(orbital)) continue;
846
+ if (!orbital.pages) continue;
847
+ for (const pageRef of orbital.pages) {
848
+ if (!isInlinePage(pageRef)) continue;
849
+ const page = pageRef;
850
+ if (page.name === pageName) {
851
+ return { page, orbitalName: orbital.name };
852
+ }
853
+ }
854
+ }
855
+ return null;
856
+ }
857
+ function getDefaultPage(schema) {
858
+ if (!schema.orbitals) return null;
859
+ for (const orbital of schema.orbitals) {
860
+ if (!isInlineOrbital(orbital)) continue;
861
+ if (!orbital.pages) continue;
862
+ for (const pageRef of orbital.pages) {
863
+ if (isInlinePage(pageRef)) {
864
+ return { page: pageRef, orbitalName: orbital.name };
865
+ }
866
+ }
867
+ }
868
+ return null;
869
+ }
870
+ function getAllPages(schema) {
871
+ const pages = [];
872
+ if (!schema.orbitals) return pages;
873
+ for (const orbital of schema.orbitals) {
874
+ if (!isInlineOrbital(orbital)) continue;
875
+ if (!orbital.pages) continue;
876
+ for (const pageRef of orbital.pages) {
877
+ if (isInlinePage(pageRef)) {
878
+ pages.push({ page: pageRef, orbitalName: orbital.name });
879
+ }
880
+ }
881
+ }
882
+ return pages;
883
+ }
884
+ var NavigationContext = createContext(null);
885
+ function NavigationProvider({
886
+ schema,
887
+ initialPage,
888
+ updateUrl = true,
889
+ onNavigate,
890
+ children
891
+ }) {
892
+ const initialState = useMemo(() => {
893
+ let page = null;
894
+ let path = "/";
895
+ if (initialPage) {
896
+ const found = findPageByName(schema, initialPage);
897
+ if (found) {
898
+ page = found.page;
899
+ path = page.path || "/";
900
+ }
901
+ }
902
+ if (!page) {
903
+ const defaultPage = getDefaultPage(schema);
904
+ if (defaultPage) {
905
+ page = defaultPage.page;
906
+ path = page.path || "/";
907
+ }
908
+ }
909
+ return {
910
+ activePage: page?.name || "",
911
+ currentPath: path,
912
+ initPayload: {},
913
+ navigationId: 0
914
+ };
915
+ }, [schema, initialPage]);
916
+ const [state, setState] = useState(initialState);
917
+ const navigateTo = useCallback((path, payload) => {
918
+ const result = findPageByPath(schema, path);
919
+ if (!result) {
920
+ console.error(`[Navigation] No page found for path: ${path}`);
921
+ return;
922
+ }
923
+ const { page, params } = result;
924
+ const finalPayload = { ...params, ...payload };
925
+ console.log("[Navigation] Navigating to:", {
926
+ path,
927
+ page: page.name,
928
+ params,
929
+ payload,
930
+ finalPayload
931
+ });
932
+ setState((prev) => ({
933
+ activePage: page.name,
934
+ currentPath: path,
935
+ initPayload: finalPayload,
936
+ navigationId: prev.navigationId + 1
937
+ }));
938
+ if (updateUrl && typeof window !== "undefined") {
939
+ try {
940
+ window.history.pushState(finalPayload, "", path);
941
+ } catch (e) {
942
+ console.warn("[Navigation] Could not update URL:", e);
943
+ }
944
+ }
945
+ if (onNavigate) {
946
+ onNavigate(page.name, path, finalPayload);
947
+ }
948
+ }, [schema, updateUrl, onNavigate]);
949
+ const navigateToPage = useCallback((pageName, payload) => {
950
+ const result = findPageByName(schema, pageName);
951
+ if (!result) {
952
+ console.error(`[Navigation] No page found with name: ${pageName}`);
953
+ return;
954
+ }
955
+ const { page } = result;
956
+ const path = page.path || `/${pageName.toLowerCase()}`;
957
+ console.log("[Navigation] Navigating to page:", {
958
+ pageName,
959
+ path,
960
+ payload
961
+ });
962
+ setState((prev) => ({
963
+ activePage: page.name,
964
+ currentPath: path,
965
+ initPayload: payload || {},
966
+ navigationId: prev.navigationId + 1
967
+ }));
968
+ if (updateUrl && typeof window !== "undefined") {
969
+ try {
970
+ window.history.pushState(payload || {}, "", path);
971
+ } catch (e) {
972
+ console.warn("[Navigation] Could not update URL:", e);
973
+ }
974
+ }
975
+ if (onNavigate) {
976
+ onNavigate(page.name, path, payload || {});
977
+ }
978
+ }, [schema, updateUrl, onNavigate]);
979
+ const contextValue = useMemo(() => ({
980
+ state,
981
+ navigateTo,
982
+ navigateToPage,
983
+ schema,
984
+ isReady: !!state.activePage
985
+ }), [state, navigateTo, navigateToPage, schema]);
986
+ return /* @__PURE__ */ jsx(NavigationContext.Provider, { value: contextValue, children });
987
+ }
988
+ function useNavigation() {
989
+ return useContext(NavigationContext);
990
+ }
991
+ function useNavigateTo() {
992
+ const context = useContext(NavigationContext);
993
+ const noOp = useCallback((path, _payload) => {
994
+ console.warn(`[Navigation] navigateTo called outside NavigationProvider. Path: ${path}`);
995
+ }, []);
996
+ return context?.navigateTo || noOp;
997
+ }
998
+ function useNavigationState() {
999
+ const context = useContext(NavigationContext);
1000
+ return context?.state || null;
1001
+ }
1002
+ function useInitPayload() {
1003
+ const context = useContext(NavigationContext);
1004
+ return context?.state.initPayload || {};
1005
+ }
1006
+ function useActivePage() {
1007
+ const context = useContext(NavigationContext);
1008
+ return context?.state.activePage || null;
1009
+ }
1010
+ function useNavigationId() {
1011
+ const context = useContext(NavigationContext);
1012
+ return context?.state.navigationId || 0;
1013
+ }
1014
+ function initializePatterns() {
1015
+ console.log("[PatternResolver] initializePatterns called");
1016
+ console.log("[PatternResolver] componentMappingJson:", componentMapping$1);
1017
+ console.log("[PatternResolver] registryJson keys:", Object.keys(patternsRegistry));
1018
+ const componentMappingData = componentMapping$1;
1019
+ const componentMapping2 = componentMappingData.mappings || {};
1020
+ console.log("[PatternResolver] Extracted mappings count:", Object.keys(componentMapping2).length);
1021
+ console.log("[PatternResolver] Sample mappings:", Object.keys(componentMapping2).slice(0, 5));
1022
+ const registryData = patternsRegistry;
1023
+ const patternRegistry2 = registryData.patterns || {};
1024
+ console.log("[PatternResolver] Extracted patterns count:", Object.keys(patternRegistry2).length);
1025
+ initializePatternResolver({
1026
+ componentMapping: componentMapping2,
1027
+ patternRegistry: patternRegistry2
1028
+ });
1029
+ console.log(`[PatternResolver] Initialized with ${Object.keys(componentMapping2).length} component mappings and ${Object.keys(patternRegistry2).length} pattern definitions`);
1030
+ return Object.keys(componentMapping2).length;
1031
+ }
1032
+
1033
+ export { ALL_SLOTS, ClientEffectConfigContext, ClientEffectConfigProvider, NavigationProvider, OfflineExecutor, SLOT_DEFINITIONS, createFetchedDataContext, createOfflineExecutor, executeClientEffects, extractRouteParams, filterEffectsByType, findPageByName, findPageByPath, getAllPages, getDefaultPage, getEmitEffects, getInlineSlots, getKnownPatterns, getNavigateEffects, getNotifyEffects, getPatternDefinition, getPatternMapping, getPatternsByCategory, getPortalSlots, getRenderUIEffects, getSlotDefinition, getSlotsByType, hasEntities, initializePatternResolver, initializePatterns, isInlineSlot, isKnownPattern, isPortalSlot, matchPath, mergeDataContexts, parseClientEffect, parseClientEffects, pathMatches, resolveEntityById, resolveEntityCount, resolveEntityData, resolveEntityDataWithQuery, resolvePattern, setComponentMapping, setPatternRegistry, useActivePage, useClientEffectConfig, useClientEffectConfigOptional, useClientEffects, useInitPayload, useNavigateTo, useNavigation, useNavigationId, useNavigationState, useOfflineExecutor };