@almadar/ui 2.0.4 → 2.1.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,401 @@
1
+ /**
2
+ * Shared Renderer Types
3
+ *
4
+ * Type definitions used by both Builder and compiled shells for
5
+ * the dual execution model. These types define the contract between
6
+ * server and client for effect execution.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ /**
11
+ * A client effect is a tuple where the first element is the effect type
12
+ * and the remaining elements are the arguments.
13
+ *
14
+ * @example
15
+ * ['render-ui', 'main', { type: 'entity-table', ... }]
16
+ * ['navigate', '/tasks/123']
17
+ * ['notify', 'Task created!', { type: 'success' }]
18
+ * ['emit', 'TASK_CREATED', { id: '123' }]
19
+ */
20
+ type ClientEffect = ['render-ui', string, PatternConfig | null] | ['navigate', string, Record<string, unknown>?] | ['notify', string, NotifyOptions?] | ['emit', string, unknown?];
21
+ /**
22
+ * Options for notify effect
23
+ */
24
+ interface NotifyOptions {
25
+ type?: 'success' | 'error' | 'info' | 'warning';
26
+ duration?: number;
27
+ }
28
+ /**
29
+ * Configuration for a pattern to render in a slot.
30
+ * This is what render-ui effects carry as their payload.
31
+ */
32
+ interface PatternConfig {
33
+ /** Pattern type from registry (e.g., 'entity-table', 'form-section') */
34
+ type: string;
35
+ /** Entity name for data binding */
36
+ entity?: string;
37
+ /** Additional props for the pattern component */
38
+ [key: string]: unknown;
39
+ }
40
+ /**
41
+ * Resolved pattern with component information from component-mapping.json
42
+ */
43
+ interface ResolvedPattern {
44
+ /** Component name (e.g., 'DataTable') */
45
+ component: string;
46
+ /** Import path for the component */
47
+ importPath: string;
48
+ /** Pattern category (e.g., 'display', 'form') */
49
+ category: string;
50
+ /** Validated and normalized props */
51
+ validatedProps: Record<string, unknown>;
52
+ }
53
+ /**
54
+ * Response from server after processing an event.
55
+ * This is the unified response format for both Builder and compiled shells.
56
+ */
57
+ interface EventResponse {
58
+ /** Whether the event was processed successfully */
59
+ success: boolean;
60
+ /** New state after transition (if transition occurred) */
61
+ newState?: string;
62
+ /** Data fetched by server effects (e.g., { Task: [...] }) */
63
+ data?: Record<string, unknown[]>;
64
+ /** Client effects to execute (render-ui, navigate, notify, emit) */
65
+ clientEffects?: ClientEffect[];
66
+ /** Results of individual effect executions (for debugging) */
67
+ effectResults?: Array<{
68
+ effect: string;
69
+ success: boolean;
70
+ data?: unknown;
71
+ error?: string;
72
+ }>;
73
+ /** Error message if success is false */
74
+ error?: string;
75
+ }
76
+ /**
77
+ * Configuration for the client effect executor.
78
+ * Provides implementations for each effect type.
79
+ */
80
+ interface ClientEffectExecutorConfig {
81
+ /**
82
+ * Render a pattern to a slot.
83
+ * Called for 'render-ui' effects.
84
+ */
85
+ renderToSlot: (slot: string, pattern: PatternConfig | null) => void;
86
+ /**
87
+ * Navigate to a route.
88
+ * Called for 'navigate' effects.
89
+ */
90
+ navigate: (path: string, params?: Record<string, unknown>) => void;
91
+ /**
92
+ * Show a notification.
93
+ * Called for 'notify' effects.
94
+ */
95
+ notify: (message: string, options?: NotifyOptions) => void;
96
+ /**
97
+ * Emit an event to the event bus.
98
+ * Called for 'emit' effects.
99
+ */
100
+ eventBus: {
101
+ emit: (event: string, payload?: unknown) => void;
102
+ };
103
+ /**
104
+ * Optional: Data from server response for render-ui.
105
+ * Components can use this to access fetched entity data.
106
+ */
107
+ data?: Record<string, unknown[]>;
108
+ /**
109
+ * Optional: Callback when all effects have been executed.
110
+ */
111
+ onComplete?: () => void;
112
+ }
113
+ /**
114
+ * Valid UI slot names
115
+ */
116
+ type UISlot = 'main' | 'sidebar' | 'modal' | 'drawer' | 'overlay' | 'center' | 'toast' | 'hud-top' | 'hud-bottom' | 'floating';
117
+ /**
118
+ * Slot type classification
119
+ */
120
+ type SlotType = 'inline' | 'portal';
121
+ /**
122
+ * Definition of a slot including its rendering behavior
123
+ */
124
+ interface SlotDefinition {
125
+ /** Slot name */
126
+ name: UISlot;
127
+ /** Whether to render inline or via portal */
128
+ type: SlotType;
129
+ /** For portal slots: where to render (default: document.body) */
130
+ portalTarget?: string;
131
+ /** Z-index for portal slots */
132
+ zIndex?: number;
133
+ }
134
+ /**
135
+ * Context for resolving entity data.
136
+ * Supports multiple data sources with priority.
137
+ */
138
+ interface DataContext {
139
+ /** Server-provided data (highest priority) */
140
+ fetchedData?: Record<string, unknown[]>;
141
+ /** In-memory mock data (for Builder preview) */
142
+ entityStore?: {
143
+ getRecords: (entityName: string) => unknown[];
144
+ };
145
+ /** Query singleton for filtering */
146
+ querySingleton?: {
147
+ getFilters: (queryRef: string) => Record<string, unknown>;
148
+ };
149
+ }
150
+ /**
151
+ * Result of data resolution
152
+ */
153
+ interface DataResolution {
154
+ /** Resolved data array */
155
+ data: unknown[];
156
+ /** Whether data is still loading */
157
+ loading: boolean;
158
+ /** Error if resolution failed */
159
+ error?: Error;
160
+ }
161
+
162
+ /**
163
+ * Offline Effect Executor
164
+ *
165
+ * Enables client-only mode for applications that need to work without
166
+ * server connectivity. Provides:
167
+ * - Mock data providers for simulating server responses
168
+ * - Local effect execution without server round-trip
169
+ * - Sync queue for effects that need server persistence when back online
170
+ *
171
+ * Used by both Builder preview (offline mode) and compiled shells (PWA mode).
172
+ *
173
+ * @packageDocumentation
174
+ */
175
+
176
+ /**
177
+ * Effect that needs to be synced to server when online
178
+ */
179
+ interface PendingSyncEffect {
180
+ /** Unique ID for this effect */
181
+ id: string;
182
+ /** Timestamp when effect was queued */
183
+ timestamp: number;
184
+ /** Effect type (persist, call-service, etc.) */
185
+ type: string;
186
+ /** Effect payload */
187
+ payload: unknown;
188
+ /** Number of retry attempts */
189
+ retries: number;
190
+ /** Maximum retries before giving up */
191
+ maxRetries: number;
192
+ }
193
+ /**
194
+ * Configuration for offline executor
195
+ */
196
+ interface OfflineExecutorConfig extends ClientEffectExecutorConfig {
197
+ /**
198
+ * Mock data provider for simulating fetch responses.
199
+ * Returns data for a given entity name.
200
+ */
201
+ mockDataProvider?: (entityName: string) => unknown[];
202
+ /**
203
+ * Whether to queue server effects for sync when online.
204
+ * Default: true
205
+ */
206
+ enableSyncQueue?: boolean;
207
+ /**
208
+ * Maximum number of effects to queue before dropping oldest.
209
+ * Default: 100
210
+ */
211
+ maxQueueSize?: number;
212
+ /**
213
+ * Callback when an effect is added to sync queue.
214
+ */
215
+ onEffectQueued?: (effect: PendingSyncEffect) => void;
216
+ /**
217
+ * Callback when sync queue changes.
218
+ */
219
+ onQueueChange?: (queue: PendingSyncEffect[]) => void;
220
+ }
221
+ /**
222
+ * Offline executor state
223
+ */
224
+ interface OfflineExecutorState {
225
+ /** Whether we're in offline mode */
226
+ isOffline: boolean;
227
+ /** Pending effects waiting for sync */
228
+ syncQueue: PendingSyncEffect[];
229
+ /** Number of effects processed locally */
230
+ localEffectsProcessed: number;
231
+ /** Number of effects synced to server */
232
+ effectsSynced: number;
233
+ /** Last sync attempt timestamp */
234
+ lastSyncAttempt?: number;
235
+ /** Last successful sync timestamp */
236
+ lastSuccessfulSync?: number;
237
+ }
238
+ /**
239
+ * OfflineExecutor - Handles effects in offline/client-only mode.
240
+ *
241
+ * Features:
242
+ * - Executes client effects immediately (render-ui, navigate, notify, emit)
243
+ * - Queues server effects (persist, fetch, call-service) for later sync
244
+ * - Provides mock data for fetch effects when offline
245
+ * - Syncs queued effects when connection is restored
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * const executor = new OfflineExecutor({
250
+ * renderToSlot: (slot, pattern) => slotManager.render(slot, pattern),
251
+ * navigate: (path) => router.push(path),
252
+ * notify: (message, opts) => toast.show(message, opts),
253
+ * eventBus: { emit: (event, payload) => bus.emit(event, payload) },
254
+ * mockDataProvider: (entityName) => mockStore.getAll(entityName),
255
+ * });
256
+ *
257
+ * // Process effects locally
258
+ * const response = executor.processEventOffline('LOAD', { id: '123' });
259
+ *
260
+ * // When back online
261
+ * await executor.syncPendingEffects(serverUrl);
262
+ * ```
263
+ */
264
+ declare class OfflineExecutor {
265
+ private config;
266
+ private state;
267
+ private storage;
268
+ constructor(config: OfflineExecutorConfig);
269
+ /**
270
+ * Check if we're online (browser API)
271
+ */
272
+ private checkOnline;
273
+ /**
274
+ * Handle going online
275
+ */
276
+ private handleOnline;
277
+ /**
278
+ * Handle going offline
279
+ */
280
+ private handleOffline;
281
+ /**
282
+ * Load sync queue from localStorage
283
+ */
284
+ private loadSyncQueue;
285
+ /**
286
+ * Save sync queue to localStorage
287
+ */
288
+ private saveSyncQueue;
289
+ /**
290
+ * Add an effect to the sync queue
291
+ */
292
+ private queueForSync;
293
+ /**
294
+ * Execute client effects immediately.
295
+ */
296
+ executeClientEffects(effects: ClientEffect[]): void;
297
+ /**
298
+ * Process an event in offline mode.
299
+ *
300
+ * Returns a simulated EventResponse with mock data.
301
+ * Client effects are executed immediately.
302
+ * Server effects are queued for sync.
303
+ */
304
+ processEventOffline(event: string, payload?: Record<string, unknown>, effects?: Array<unknown[]>): EventResponse;
305
+ /**
306
+ * Sync pending effects to server.
307
+ *
308
+ * @param serverUrl - Base URL for the orbital server
309
+ * @param authToken - Optional auth token for requests
310
+ * @returns Number of successfully synced effects
311
+ */
312
+ syncPendingEffects(serverUrl: string, authToken?: string): Promise<number>;
313
+ /**
314
+ * Get current executor state
315
+ */
316
+ getState(): OfflineExecutorState;
317
+ /**
318
+ * Get number of pending effects
319
+ */
320
+ getPendingCount(): number;
321
+ /**
322
+ * Clear the sync queue
323
+ */
324
+ clearQueue(): void;
325
+ /**
326
+ * Dispose the executor and clean up listeners
327
+ */
328
+ dispose(): void;
329
+ }
330
+ /**
331
+ * Create an offline executor with sensible defaults.
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * const executor = createOfflineExecutor({
336
+ * renderToSlot: slotManager.render,
337
+ * navigate: router.push,
338
+ * notify: toast.show,
339
+ * eventBus: { emit: bus.emit },
340
+ * mockDataProvider: (entity) => store.getAll(entity),
341
+ * });
342
+ * ```
343
+ */
344
+ declare function createOfflineExecutor(config: OfflineExecutorConfig): OfflineExecutor;
345
+ /**
346
+ * Options for useOfflineExecutor hook
347
+ */
348
+ interface UseOfflineExecutorOptions extends OfflineExecutorConfig {
349
+ /** Server URL for syncing */
350
+ serverUrl?: string;
351
+ /** Auth token for server requests */
352
+ authToken?: string;
353
+ /** Auto-sync when coming back online */
354
+ autoSync?: boolean;
355
+ }
356
+ /**
357
+ * Result of useOfflineExecutor hook
358
+ */
359
+ interface UseOfflineExecutorResult {
360
+ /** Current executor state */
361
+ state: OfflineExecutorState;
362
+ /** Whether we're offline */
363
+ isOffline: boolean;
364
+ /** Number of pending effects */
365
+ pendingCount: number;
366
+ /** Execute client effects */
367
+ executeClientEffects: (effects: ClientEffect[]) => void;
368
+ /** Process event offline */
369
+ processEventOffline: (event: string, payload?: Record<string, unknown>, effects?: Array<unknown[]>) => EventResponse;
370
+ /** Manually trigger sync */
371
+ sync: () => Promise<number>;
372
+ /** Clear the queue */
373
+ clearQueue: () => void;
374
+ }
375
+ /**
376
+ * React hook for offline effect execution.
377
+ *
378
+ * @example
379
+ * ```tsx
380
+ * function MyComponent() {
381
+ * const { isOffline, pendingCount, processEventOffline, sync } = useOfflineExecutor({
382
+ * renderToSlot: slotManager.render,
383
+ * navigate: router.push,
384
+ * notify: toast.show,
385
+ * eventBus: { emit: bus.emit },
386
+ * serverUrl: '/api/orbitals',
387
+ * autoSync: true,
388
+ * });
389
+ *
390
+ * return (
391
+ * <div>
392
+ * {isOffline && <Banner>Offline - {pendingCount} changes pending</Banner>}
393
+ * <button onClick={() => processEventOffline('SAVE', data)}>Save</button>
394
+ * </div>
395
+ * );
396
+ * }
397
+ * ```
398
+ */
399
+ declare function useOfflineExecutor(options: UseOfflineExecutorOptions): UseOfflineExecutorResult;
400
+
401
+ export { type ClientEffect as C, type DataContext as D, type EventResponse as E, type NotifyOptions as N, OfflineExecutor as O, type PatternConfig as P, type ResolvedPattern as R, type SlotDefinition as S, type UseOfflineExecutorResult as U, type UseOfflineExecutorOptions as a, type ClientEffectExecutorConfig as b, type DataResolution as c, type UISlot as d, type SlotType as e, type OfflineExecutorConfig as f, type OfflineExecutorState as g, type PendingSyncEffect as h, createOfflineExecutor as i, useOfflineExecutor as u };