@almadar/ui 2.1.11 → 2.4.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.
@@ -1,426 +0,0 @@
1
- import { __publicField } from './chunk-PKBMQBKP.js';
2
- import { useRef, useState, useEffect, useCallback } from 'react';
3
-
4
- // renderer/client-effect-executor.ts
5
- function executeClientEffects(effects, config) {
6
- if (!effects || effects.length === 0) {
7
- return;
8
- }
9
- for (const effect of effects) {
10
- try {
11
- executeEffect(effect, config);
12
- } catch (error) {
13
- console.error(
14
- `[ClientEffectExecutor] Error executing effect:`,
15
- effect,
16
- error
17
- );
18
- }
19
- }
20
- config.onComplete?.();
21
- }
22
- function executeEffect(effect, config) {
23
- const [effectType, ...args] = effect;
24
- switch (effectType) {
25
- case "render-ui": {
26
- const [slot, patternConfig] = args;
27
- executeRenderUI(slot, patternConfig, config);
28
- break;
29
- }
30
- case "navigate": {
31
- const [path, params] = args;
32
- executeNavigate(path, params, config);
33
- break;
34
- }
35
- case "notify": {
36
- const [message, options] = args;
37
- executeNotify(message, options, config);
38
- break;
39
- }
40
- case "emit": {
41
- const [event, payload] = args;
42
- executeEmit(event, payload, config);
43
- break;
44
- }
45
- default:
46
- console.warn(`[ClientEffectExecutor] Unknown effect type: ${effectType}`);
47
- }
48
- }
49
- function executeRenderUI(slot, patternConfig, config) {
50
- config.renderToSlot(slot, patternConfig);
51
- }
52
- function executeNavigate(path, params, config) {
53
- config.navigate(path, params);
54
- }
55
- function executeNotify(message, options, config) {
56
- config.notify(message, options);
57
- }
58
- function executeEmit(event, payload, config) {
59
- config.eventBus.emit(event, payload);
60
- }
61
- function parseClientEffect(raw) {
62
- if (!Array.isArray(raw) || raw.length < 1) {
63
- console.warn("[ClientEffectExecutor] Invalid effect format:", raw);
64
- return null;
65
- }
66
- const [type, ...args] = raw;
67
- if (typeof type !== "string") {
68
- console.warn("[ClientEffectExecutor] Effect type must be string:", raw);
69
- return null;
70
- }
71
- switch (type) {
72
- case "render-ui":
73
- return ["render-ui", args[0], args[1]];
74
- case "navigate":
75
- return ["navigate", args[0], args[1]];
76
- case "notify":
77
- return ["notify", args[0], args[1]];
78
- case "emit":
79
- return ["emit", args[0], args[1]];
80
- default:
81
- console.warn(`[ClientEffectExecutor] Unknown effect type: ${type}`);
82
- return null;
83
- }
84
- }
85
- function parseClientEffects(raw) {
86
- if (!raw || !Array.isArray(raw)) {
87
- return [];
88
- }
89
- return raw.map((effect) => parseClientEffect(effect)).filter((effect) => effect !== null);
90
- }
91
- function filterEffectsByType(effects, type) {
92
- return effects.filter(
93
- (effect) => effect[0] === type
94
- );
95
- }
96
- function getRenderUIEffects(effects) {
97
- return filterEffectsByType(effects, "render-ui");
98
- }
99
- function getNavigateEffects(effects) {
100
- return filterEffectsByType(effects, "navigate");
101
- }
102
- function getNotifyEffects(effects) {
103
- return filterEffectsByType(effects, "notify");
104
- }
105
- function getEmitEffects(effects) {
106
- return filterEffectsByType(effects, "emit");
107
- }
108
- var effectIdCounter = 0;
109
- function generateEffectId() {
110
- return `offline-effect-${++effectIdCounter}-${Date.now()}`;
111
- }
112
- var OfflineExecutor = class {
113
- constructor(config) {
114
- __publicField(this, "config");
115
- __publicField(this, "state");
116
- __publicField(this, "storage");
117
- /**
118
- * Handle going online
119
- */
120
- __publicField(this, "handleOnline", () => {
121
- this.state.isOffline = false;
122
- });
123
- /**
124
- * Handle going offline
125
- */
126
- __publicField(this, "handleOffline", () => {
127
- this.state.isOffline = true;
128
- });
129
- this.config = {
130
- enableSyncQueue: true,
131
- maxQueueSize: 100,
132
- ...config
133
- };
134
- this.state = {
135
- isOffline: !this.checkOnline(),
136
- syncQueue: [],
137
- localEffectsProcessed: 0,
138
- effectsSynced: 0
139
- };
140
- this.storage = typeof localStorage !== "undefined" ? localStorage : null;
141
- this.loadSyncQueue();
142
- if (typeof window !== "undefined") {
143
- window.addEventListener("online", this.handleOnline);
144
- window.addEventListener("offline", this.handleOffline);
145
- }
146
- }
147
- /**
148
- * Check if we're online (browser API)
149
- */
150
- checkOnline() {
151
- return typeof navigator !== "undefined" ? navigator.onLine : true;
152
- }
153
- /**
154
- * Load sync queue from localStorage
155
- */
156
- loadSyncQueue() {
157
- if (!this.storage) return;
158
- try {
159
- const stored = this.storage.getItem("orbital-offline-queue");
160
- if (stored) {
161
- this.state.syncQueue = JSON.parse(stored);
162
- }
163
- } catch (error) {
164
- console.warn("[OfflineExecutor] Failed to load sync queue:", error);
165
- }
166
- }
167
- /**
168
- * Save sync queue to localStorage
169
- */
170
- saveSyncQueue() {
171
- if (!this.storage) return;
172
- try {
173
- this.storage.setItem("orbital-offline-queue", JSON.stringify(this.state.syncQueue));
174
- } catch (error) {
175
- console.warn("[OfflineExecutor] Failed to save sync queue:", error);
176
- }
177
- }
178
- /**
179
- * Add an effect to the sync queue
180
- */
181
- queueForSync(type, payload) {
182
- if (!this.config.enableSyncQueue) return;
183
- const effect = {
184
- id: generateEffectId(),
185
- timestamp: Date.now(),
186
- type,
187
- payload,
188
- retries: 0,
189
- maxRetries: 3
190
- };
191
- this.state.syncQueue.push(effect);
192
- if (this.state.syncQueue.length > (this.config.maxQueueSize ?? 100)) {
193
- this.state.syncQueue.shift();
194
- }
195
- this.saveSyncQueue();
196
- this.config.onEffectQueued?.(effect);
197
- this.config.onQueueChange?.(this.state.syncQueue);
198
- }
199
- /**
200
- * Execute client effects immediately.
201
- */
202
- executeClientEffects(effects) {
203
- if (effects.length === 0) return;
204
- executeClientEffects(effects, this.config);
205
- this.state.localEffectsProcessed += effects.length;
206
- }
207
- /**
208
- * Process an event in offline mode.
209
- *
210
- * Returns a simulated EventResponse with mock data.
211
- * Client effects are executed immediately.
212
- * Server effects are queued for sync.
213
- */
214
- processEventOffline(event, payload, effects) {
215
- const clientEffects = [];
216
- const fetchedData = {};
217
- if (effects) {
218
- for (const effect of effects) {
219
- if (!Array.isArray(effect) || effect.length < 1) continue;
220
- const [type, ...args] = effect;
221
- switch (type) {
222
- // Client effects - execute immediately
223
- case "render-ui":
224
- case "navigate":
225
- case "notify":
226
- case "emit":
227
- clientEffects.push(effect);
228
- break;
229
- // Fetch effect - use mock data
230
- case "fetch": {
231
- const [entityName, _query] = args;
232
- if (typeof entityName === "string" && this.config.mockDataProvider) {
233
- fetchedData[entityName] = this.config.mockDataProvider(entityName);
234
- }
235
- break;
236
- }
237
- // Server effects - queue for sync
238
- case "persist":
239
- case "call-service":
240
- case "spawn":
241
- case "despawn":
242
- this.queueForSync(type, { args, event, payload });
243
- break;
244
- default:
245
- console.warn(`[OfflineExecutor] Unknown effect type: ${type}`);
246
- }
247
- }
248
- }
249
- if (clientEffects.length > 0) {
250
- this.executeClientEffects(clientEffects);
251
- }
252
- return {
253
- success: true,
254
- data: Object.keys(fetchedData).length > 0 ? fetchedData : void 0,
255
- clientEffects: clientEffects.length > 0 ? clientEffects : void 0
256
- };
257
- }
258
- /**
259
- * Sync pending effects to server.
260
- *
261
- * @param serverUrl - Base URL for the orbital server
262
- * @param authToken - Optional auth token for requests
263
- * @returns Number of successfully synced effects
264
- */
265
- async syncPendingEffects(serverUrl, authToken) {
266
- if (this.state.syncQueue.length === 0) {
267
- return 0;
268
- }
269
- this.state.lastSyncAttempt = Date.now();
270
- let syncedCount = 0;
271
- const failedEffects = [];
272
- const headers = {
273
- "Content-Type": "application/json"
274
- };
275
- if (authToken) {
276
- headers["Authorization"] = `Bearer ${authToken}`;
277
- }
278
- for (const effect of this.state.syncQueue) {
279
- try {
280
- const response = await fetch(`${serverUrl}/sync-effect`, {
281
- method: "POST",
282
- headers,
283
- body: JSON.stringify({
284
- type: effect.type,
285
- payload: effect.payload,
286
- offlineId: effect.id,
287
- offlineTimestamp: effect.timestamp
288
- })
289
- });
290
- if (response.ok) {
291
- syncedCount++;
292
- } else {
293
- effect.retries++;
294
- if (effect.retries < effect.maxRetries) {
295
- failedEffects.push(effect);
296
- }
297
- }
298
- } catch (error) {
299
- effect.retries++;
300
- if (effect.retries < effect.maxRetries) {
301
- failedEffects.push(effect);
302
- }
303
- }
304
- }
305
- this.state.syncQueue = failedEffects;
306
- this.state.effectsSynced += syncedCount;
307
- if (syncedCount > 0) {
308
- this.state.lastSuccessfulSync = Date.now();
309
- }
310
- this.saveSyncQueue();
311
- this.config.onQueueChange?.(this.state.syncQueue);
312
- return syncedCount;
313
- }
314
- /**
315
- * Get current executor state
316
- */
317
- getState() {
318
- return { ...this.state };
319
- }
320
- /**
321
- * Get number of pending effects
322
- */
323
- getPendingCount() {
324
- return this.state.syncQueue.length;
325
- }
326
- /**
327
- * Clear the sync queue
328
- */
329
- clearQueue() {
330
- this.state.syncQueue = [];
331
- this.saveSyncQueue();
332
- this.config.onQueueChange?.(this.state.syncQueue);
333
- }
334
- /**
335
- * Dispose the executor and clean up listeners
336
- */
337
- dispose() {
338
- if (typeof window !== "undefined") {
339
- window.removeEventListener("online", this.handleOnline);
340
- window.removeEventListener("offline", this.handleOffline);
341
- }
342
- }
343
- };
344
- function createOfflineExecutor(config) {
345
- return new OfflineExecutor(config);
346
- }
347
- function useOfflineExecutor(options) {
348
- const executorRef = useRef(null);
349
- const [state, setState] = useState({
350
- isOffline: false,
351
- syncQueue: [],
352
- localEffectsProcessed: 0,
353
- effectsSynced: 0
354
- });
355
- useEffect(() => {
356
- const executor = new OfflineExecutor({
357
- ...options,
358
- onQueueChange: (queue) => {
359
- setState(executor.getState());
360
- options.onQueueChange?.(queue);
361
- }
362
- });
363
- executorRef.current = executor;
364
- setState(executor.getState());
365
- return () => {
366
- executor.dispose();
367
- executorRef.current = null;
368
- };
369
- }, []);
370
- useEffect(() => {
371
- if (!options.autoSync || !options.serverUrl) return;
372
- const handleOnline = async () => {
373
- if (executorRef.current) {
374
- await executorRef.current.syncPendingEffects(
375
- options.serverUrl,
376
- options.authToken
377
- );
378
- setState(executorRef.current.getState());
379
- }
380
- };
381
- window.addEventListener("online", handleOnline);
382
- return () => window.removeEventListener("online", handleOnline);
383
- }, [options.autoSync, options.serverUrl, options.authToken]);
384
- const executeEffects = useCallback((effects) => {
385
- executorRef.current?.executeClientEffects(effects);
386
- if (executorRef.current) {
387
- setState(executorRef.current.getState());
388
- }
389
- }, []);
390
- const processEventOffline = useCallback(
391
- (event, payload, effects) => {
392
- const result = executorRef.current?.processEventOffline(event, payload, effects);
393
- if (executorRef.current) {
394
- setState(executorRef.current.getState());
395
- }
396
- return result ?? { success: false, error: "Executor not initialized" };
397
- },
398
- []
399
- );
400
- const sync = useCallback(async () => {
401
- if (!executorRef.current || !options.serverUrl) return 0;
402
- const count = await executorRef.current.syncPendingEffects(
403
- options.serverUrl,
404
- options.authToken
405
- );
406
- setState(executorRef.current.getState());
407
- return count;
408
- }, [options.serverUrl, options.authToken]);
409
- const clearQueue = useCallback(() => {
410
- executorRef.current?.clearQueue();
411
- if (executorRef.current) {
412
- setState(executorRef.current.getState());
413
- }
414
- }, []);
415
- return {
416
- state,
417
- isOffline: state.isOffline,
418
- pendingCount: state.syncQueue.length,
419
- executeClientEffects: executeEffects,
420
- processEventOffline,
421
- sync,
422
- clearQueue
423
- };
424
- }
425
-
426
- export { OfflineExecutor, createOfflineExecutor, executeClientEffects, filterEffectsByType, getEmitEffects, getNavigateEffects, getNotifyEffects, getRenderUIEffects, parseClientEffect, parseClientEffects, useOfflineExecutor };