@prabhask5/stellar-engine 1.1.18 → 1.2.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.
Files changed (73) hide show
  1. package/README.md +55 -1
  2. package/dist/bin/install-pwa.js +50 -0
  3. package/dist/bin/install-pwa.js.map +1 -1
  4. package/dist/config.d.ts +11 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +8 -2
  7. package/dist/config.js.map +1 -1
  8. package/dist/crdt/awareness.d.ts +128 -0
  9. package/dist/crdt/awareness.d.ts.map +1 -0
  10. package/dist/crdt/awareness.js +284 -0
  11. package/dist/crdt/awareness.js.map +1 -0
  12. package/dist/crdt/channel.d.ts +165 -0
  13. package/dist/crdt/channel.d.ts.map +1 -0
  14. package/dist/crdt/channel.js +522 -0
  15. package/dist/crdt/channel.js.map +1 -0
  16. package/dist/crdt/config.d.ts +58 -0
  17. package/dist/crdt/config.d.ts.map +1 -0
  18. package/dist/crdt/config.js +123 -0
  19. package/dist/crdt/config.js.map +1 -0
  20. package/dist/crdt/helpers.d.ts +104 -0
  21. package/dist/crdt/helpers.d.ts.map +1 -0
  22. package/dist/crdt/helpers.js +116 -0
  23. package/dist/crdt/helpers.js.map +1 -0
  24. package/dist/crdt/offline.d.ts +58 -0
  25. package/dist/crdt/offline.d.ts.map +1 -0
  26. package/dist/crdt/offline.js +130 -0
  27. package/dist/crdt/offline.js.map +1 -0
  28. package/dist/crdt/persistence.d.ts +65 -0
  29. package/dist/crdt/persistence.d.ts.map +1 -0
  30. package/dist/crdt/persistence.js +171 -0
  31. package/dist/crdt/persistence.js.map +1 -0
  32. package/dist/crdt/provider.d.ts +109 -0
  33. package/dist/crdt/provider.d.ts.map +1 -0
  34. package/dist/crdt/provider.js +543 -0
  35. package/dist/crdt/provider.js.map +1 -0
  36. package/dist/crdt/store.d.ts +111 -0
  37. package/dist/crdt/store.d.ts.map +1 -0
  38. package/dist/crdt/store.js +158 -0
  39. package/dist/crdt/store.js.map +1 -0
  40. package/dist/crdt/types.d.ts +281 -0
  41. package/dist/crdt/types.d.ts.map +1 -0
  42. package/dist/crdt/types.js +26 -0
  43. package/dist/crdt/types.js.map +1 -0
  44. package/dist/database.d.ts +1 -1
  45. package/dist/database.d.ts.map +1 -1
  46. package/dist/database.js +28 -7
  47. package/dist/database.js.map +1 -1
  48. package/dist/diagnostics.d.ts +75 -0
  49. package/dist/diagnostics.d.ts.map +1 -1
  50. package/dist/diagnostics.js +114 -2
  51. package/dist/diagnostics.js.map +1 -1
  52. package/dist/engine.d.ts.map +1 -1
  53. package/dist/engine.js +21 -1
  54. package/dist/engine.js.map +1 -1
  55. package/dist/entries/crdt.d.ts +32 -0
  56. package/dist/entries/crdt.d.ts.map +1 -0
  57. package/dist/entries/crdt.js +52 -0
  58. package/dist/entries/crdt.js.map +1 -0
  59. package/dist/entries/types.d.ts +1 -0
  60. package/dist/entries/types.d.ts.map +1 -1
  61. package/dist/index.d.ts +3 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +7 -0
  64. package/dist/index.js.map +1 -1
  65. package/package.json +7 -2
  66. package/dist/operations.d.ts +0 -73
  67. package/dist/operations.d.ts.map +0 -1
  68. package/dist/operations.js +0 -227
  69. package/dist/operations.js.map +0 -1
  70. package/dist/reconnectHandler.d.ts +0 -16
  71. package/dist/reconnectHandler.d.ts.map +0 -1
  72. package/dist/reconnectHandler.js +0 -21
  73. package/dist/reconnectHandler.js.map +0 -1
@@ -0,0 +1,284 @@
1
+ /**
2
+ * @fileoverview CRDT Presence / Awareness Management
3
+ *
4
+ * Bridges Supabase Presence ↔ local presence state for collaborative cursor
5
+ * and user tracking. Each open document has its own set of collaborators.
6
+ *
7
+ * Responsibilities:
8
+ * - Tracking local cursor/selection state per document
9
+ * - Debouncing cursor updates to avoid flooding the Presence channel
10
+ * - Maintaining a list of active collaborators per document
11
+ * - Providing subscription-based notifications for collaborator changes
12
+ * - Deterministic color assignment from userId hash
13
+ *
14
+ * The Supabase Presence integration is handled through the same Realtime
15
+ * channel used for Broadcast. Presence state is tracked separately from
16
+ * Yjs document updates — they use different Supabase Realtime features
17
+ * (Presence vs Broadcast) on the same channel.
18
+ *
19
+ * @see {@link ./types.ts} for {@link UserPresenceState}
20
+ * @see {@link ./channel.ts} for the underlying Broadcast channel
21
+ * @see {@link ./provider.ts} for the lifecycle orchestrator
22
+ */
23
+ import { getDeviceId } from '../deviceId';
24
+ import { debugLog } from '../debug';
25
+ import { getCRDTConfig } from './config';
26
+ // =============================================================================
27
+ // Color Palette
28
+ // =============================================================================
29
+ /**
30
+ * 12-color palette for deterministic collaborator color assignment.
31
+ *
32
+ * Colors are chosen for good contrast on both light and dark backgrounds,
33
+ * and to be distinguishable from each other even at small sizes (cursor lines,
34
+ * selection highlights).
35
+ */
36
+ const COLLABORATOR_COLORS = [
37
+ '#E57373', // Red
38
+ '#81C784', // Green
39
+ '#64B5F6', // Blue
40
+ '#FFD54F', // Amber
41
+ '#BA68C8', // Purple
42
+ '#4DB6AC', // Teal
43
+ '#FF8A65', // Deep Orange
44
+ '#A1887F', // Brown
45
+ '#90A4AE', // Blue Grey
46
+ '#F06292', // Pink
47
+ '#AED581', // Light Green
48
+ '#4FC3F7' // Light Blue
49
+ ];
50
+ // =============================================================================
51
+ // Module State
52
+ // =============================================================================
53
+ /** Active collaborators per document. */
54
+ const collaboratorsByDocument = new Map();
55
+ /** Change listeners per document. */
56
+ const changeListeners = new Map();
57
+ /** Last cursor update timestamp per document (for debouncing). */
58
+ const lastCursorUpdate = new Map();
59
+ // =============================================================================
60
+ // Color Assignment
61
+ // =============================================================================
62
+ /**
63
+ * Assign a deterministic color to a user based on their userId.
64
+ *
65
+ * Uses a simple hash of the userId to index into the 12-color palette.
66
+ * The same userId always gets the same color, so collaborators appear
67
+ * consistent across sessions and devices.
68
+ *
69
+ * @param userId - The user's UUID.
70
+ * @returns A hex color string from the palette.
71
+ */
72
+ export function assignColor(userId) {
73
+ let hash = 0;
74
+ for (let i = 0; i < userId.length; i++) {
75
+ hash = ((hash << 5) - hash + userId.charCodeAt(i)) | 0;
76
+ }
77
+ return COLLABORATOR_COLORS[Math.abs(hash) % COLLABORATOR_COLORS.length];
78
+ }
79
+ // =============================================================================
80
+ // Internal Lifecycle (called by provider.ts)
81
+ // =============================================================================
82
+ /**
83
+ * Initialize presence tracking for a document.
84
+ *
85
+ * Called by the provider when a document is opened. Sets up the collaborator
86
+ * map and announces the local user's presence.
87
+ *
88
+ * @param documentId - The document to join.
89
+ * @param isConnected - Whether the Broadcast channel is connected.
90
+ * @param initialPresence - The local user's initial presence info.
91
+ * @internal
92
+ */
93
+ export function joinPresence(documentId, isConnected, initialPresence) {
94
+ if (!collaboratorsByDocument.has(documentId)) {
95
+ collaboratorsByDocument.set(documentId, new Map());
96
+ }
97
+ if (isConnected) {
98
+ debugLog(`[CRDT] Document ${documentId}: announcing presence (name=${initialPresence.name})`);
99
+ }
100
+ }
101
+ /**
102
+ * Leave presence tracking for a document.
103
+ *
104
+ * Called by the provider when a document is closed. Cleans up the collaborator
105
+ * map and notifies listeners.
106
+ *
107
+ * @param documentId - The document to leave.
108
+ * @internal
109
+ */
110
+ export function leavePresence(documentId) {
111
+ collaboratorsByDocument.delete(documentId);
112
+ lastCursorUpdate.delete(documentId);
113
+ /* Don't delete listeners — they unsubscribe themselves. */
114
+ }
115
+ /**
116
+ * Handle a remote user joining the document.
117
+ *
118
+ * Called when a Supabase Presence `join` event is received.
119
+ *
120
+ * @param documentId - The document.
121
+ * @param presence - The remote user's presence state.
122
+ * @internal
123
+ */
124
+ export function handlePresenceJoin(documentId, presence) {
125
+ const collaborators = collaboratorsByDocument.get(documentId);
126
+ if (!collaborators)
127
+ return;
128
+ /* Multi-tab dedup: use `userId:deviceId` as the key so the same user on
129
+ * different devices shows as separate presence entries, but same user on
130
+ * the same device (multiple tabs) collapses to one. */
131
+ const key = `${presence.userId}:${presence.deviceId}`;
132
+ collaborators.set(key, presence);
133
+ debugLog(`[CRDT] Document ${documentId}: ${presence.name} joined (userId=${presence.userId}, deviceId=${presence.deviceId})`);
134
+ notifyListeners(documentId);
135
+ }
136
+ /**
137
+ * Handle a remote user leaving the document.
138
+ *
139
+ * @param documentId - The document.
140
+ * @param userId - The user's UUID.
141
+ * @param deviceId - The user's device ID.
142
+ * @internal
143
+ */
144
+ export function handlePresenceLeave(documentId, userId, deviceId) {
145
+ const collaborators = collaboratorsByDocument.get(documentId);
146
+ if (!collaborators)
147
+ return;
148
+ const key = `${userId}:${deviceId}`;
149
+ const presence = collaborators.get(key);
150
+ collaborators.delete(key);
151
+ if (presence) {
152
+ debugLog(`[CRDT] Document ${documentId}: ${presence.name} left`);
153
+ }
154
+ notifyListeners(documentId);
155
+ }
156
+ // =============================================================================
157
+ // Public API
158
+ // =============================================================================
159
+ /**
160
+ * Update the local user's cursor and selection in a document.
161
+ *
162
+ * Debounced to `cursorDebounceMs` (default 50ms) to avoid flooding the
163
+ * Presence channel with rapid cursor movements.
164
+ *
165
+ * @param documentId - The document to update cursor for.
166
+ * @param cursor - Editor-specific cursor position (opaque to the engine).
167
+ * @param selection - Editor-specific selection range (opaque to the engine).
168
+ *
169
+ * @example
170
+ * // In your editor's cursor change handler:
171
+ * editor.on('selectionUpdate', ({ editor }) => {
172
+ * updateCursor('doc-1', editor.state.selection.anchor, editor.state.selection);
173
+ * });
174
+ */
175
+ export function updateCursor(documentId, cursor, selection) {
176
+ const config = getCRDTConfig();
177
+ const now = Date.now();
178
+ const lastUpdate = lastCursorUpdate.get(documentId) ?? 0;
179
+ if (now - lastUpdate < config.cursorDebounceMs) {
180
+ debugLog(`[CRDT] Document ${documentId}: cursor update throttled (${now - lastUpdate}ms < ${config.cursorDebounceMs}ms)`);
181
+ return;
182
+ }
183
+ lastCursorUpdate.set(documentId, now);
184
+ /* Update local collaborator entry (if tracking). */
185
+ const collaborators = collaboratorsByDocument.get(documentId);
186
+ if (!collaborators)
187
+ return;
188
+ const deviceId = getDeviceId();
189
+ /* The local user's entry — find it or it might not exist yet. */
190
+ for (const [key, state] of collaborators) {
191
+ if (state.deviceId === deviceId) {
192
+ collaborators.set(key, {
193
+ ...state,
194
+ cursor,
195
+ selection,
196
+ lastActiveAt: new Date().toISOString()
197
+ });
198
+ notifyListeners(documentId);
199
+ break;
200
+ }
201
+ }
202
+ }
203
+ /**
204
+ * Get the current list of collaborators for a document.
205
+ *
206
+ * Excludes the local user (they don't need to see their own cursor).
207
+ *
208
+ * @param documentId - The document to get collaborators for.
209
+ * @returns Array of presence states for remote collaborators.
210
+ *
211
+ * @example
212
+ * const collaborators = getCollaborators('doc-1');
213
+ * // [{ userId: '...', name: 'Alice', color: '#E57373', cursor: {...} }]
214
+ */
215
+ export function getCollaborators(documentId) {
216
+ const collaborators = collaboratorsByDocument.get(documentId);
217
+ if (!collaborators)
218
+ return [];
219
+ const localDeviceId = getDeviceId();
220
+ const result = [];
221
+ for (const state of collaborators.values()) {
222
+ if (state.deviceId !== localDeviceId) {
223
+ result.push(state);
224
+ }
225
+ }
226
+ const count = result.length;
227
+ if (count > 0) {
228
+ debugLog(`[CRDT] Document ${documentId}: ${count} active collaborators`);
229
+ }
230
+ return result;
231
+ }
232
+ /**
233
+ * Subscribe to collaborator changes for a document.
234
+ *
235
+ * The callback is invoked whenever a collaborator joins, leaves, or updates
236
+ * their cursor position. The callback receives the current list of remote
237
+ * collaborators (excluding the local user).
238
+ *
239
+ * @param documentId - The document to subscribe to.
240
+ * @param callback - Called with the updated collaborator list.
241
+ * @returns An unsubscribe function.
242
+ *
243
+ * @example
244
+ * const unsub = onCollaboratorsChange('doc-1', (collaborators) => {
245
+ * avatarList = collaborators.map(c => ({ name: c.name, color: c.color }));
246
+ * });
247
+ * // Later:
248
+ * unsub();
249
+ */
250
+ export function onCollaboratorsChange(documentId, callback) {
251
+ let listeners = changeListeners.get(documentId);
252
+ if (!listeners) {
253
+ listeners = new Set();
254
+ changeListeners.set(documentId, listeners);
255
+ }
256
+ listeners.add(callback);
257
+ return () => {
258
+ listeners.delete(callback);
259
+ if (listeners.size === 0) {
260
+ changeListeners.delete(documentId);
261
+ }
262
+ };
263
+ }
264
+ // =============================================================================
265
+ // Internal Helpers
266
+ // =============================================================================
267
+ /**
268
+ * Notify all listeners for a document of a collaborator change.
269
+ */
270
+ function notifyListeners(documentId) {
271
+ const listeners = changeListeners.get(documentId);
272
+ if (!listeners || listeners.size === 0)
273
+ return;
274
+ const collaborators = getCollaborators(documentId);
275
+ for (const callback of listeners) {
276
+ try {
277
+ callback(collaborators);
278
+ }
279
+ catch {
280
+ /* Swallow listener errors to prevent cascading failures. */
281
+ }
282
+ }
283
+ }
284
+ //# sourceMappingURL=awareness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"awareness.js","sourceRoot":"","sources":["../../src/crdt/awareness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * @fileoverview CRDT Broadcast Channel — Supabase Realtime Transport
3
+ *
4
+ * Manages one Supabase Broadcast + Presence channel per open CRDT document.
5
+ * Responsible for:
6
+ * - Distributing Yjs updates to remote peers via Broadcast
7
+ * - Receiving and applying remote updates (with echo suppression)
8
+ * - Debouncing outbound updates and merging via `Y.mergeUpdates()`
9
+ * - Chunking payloads that exceed the Broadcast size limit
10
+ * - Running the sync protocol on join (exchange state vectors, send deltas)
11
+ * - Cross-tab sync via browser `BroadcastChannel` API (avoids Supabase for same-device)
12
+ * - Reconnection with exponential backoff
13
+ *
14
+ * Channel naming convention: `crdt:${prefix}:${documentId}`
15
+ *
16
+ * @see {@link ./provider.ts} for the orchestrator that creates channels
17
+ * @see {@link ./types.ts} for message type definitions
18
+ * @see {@link ./awareness.ts} for Presence management (separate concern)
19
+ */
20
+ import * as Y from 'yjs';
21
+ import type { CRDTConnectionState } from './types';
22
+ /**
23
+ * Manages the Supabase Broadcast channel for a single CRDT document.
24
+ *
25
+ * Handles update distribution, echo suppression, debouncing, chunking,
26
+ * the sync protocol, cross-tab sync, and reconnection.
27
+ *
28
+ * @internal — consumers interact via {@link ./provider.ts}, not this class directly.
29
+ */
30
+ export declare class CRDTChannel {
31
+ readonly documentId: string;
32
+ private readonly doc;
33
+ private readonly deviceId;
34
+ private readonly channelName;
35
+ /** Supabase Realtime channel instance. */
36
+ private channel;
37
+ /** Browser BroadcastChannel for cross-tab sync (same device). */
38
+ private localChannel;
39
+ /** Current connection state. */
40
+ private _connectionState;
41
+ /** Callback invoked when connection state changes. */
42
+ private onConnectionStateChange;
43
+ /** Initial presence info for Supabase Presence tracking. */
44
+ private presenceInfo;
45
+ private pendingUpdates;
46
+ private debounceTimer;
47
+ private chunkBuffers;
48
+ private reconnectAttempts;
49
+ private reconnectTimer;
50
+ private destroyed;
51
+ private syncResolvers;
52
+ constructor(documentId: string, doc: Y.Doc, onConnectionStateChange?: (state: CRDTConnectionState) => void);
53
+ /** Current connection state of the channel. */
54
+ get connectionState(): CRDTConnectionState;
55
+ /**
56
+ * Set the local user's presence info for Supabase Presence tracking.
57
+ *
58
+ * Call this before `join()` to announce presence immediately on channel subscribe,
59
+ * or after join to update the tracked presence.
60
+ */
61
+ setPresenceInfo(info: {
62
+ name: string;
63
+ avatarUrl?: string;
64
+ }): void;
65
+ /**
66
+ * Join the Broadcast channel and start receiving messages.
67
+ *
68
+ * After subscribing, initiates the sync protocol by sending a sync-step-1
69
+ * message with the local state vector so peers can respond with deltas.
70
+ */
71
+ join(): Promise<void>;
72
+ /**
73
+ * Leave the channel and clean up all resources.
74
+ */
75
+ leave(): Promise<void>;
76
+ /**
77
+ * Queue a Yjs update for broadcasting to remote peers.
78
+ *
79
+ * Updates are debounced for `broadcastDebounceMs` (default 100ms) and merged
80
+ * via `Y.mergeUpdates()` before sending. This reduces network traffic for
81
+ * rapid keystrokes while keeping latency under 100ms.
82
+ *
83
+ * @param update - The Yjs update delta from `doc.on('update')`.
84
+ */
85
+ broadcastUpdate(update: Uint8Array): void;
86
+ /**
87
+ * Wait for the sync protocol to complete after joining.
88
+ *
89
+ * Resolves when at least one peer responds with sync-step-2, or times out
90
+ * after `syncPeerTimeoutMs` (default 3s) if no peers are available.
91
+ *
92
+ * @returns `true` if a peer responded, `false` if timed out (no peers).
93
+ */
94
+ waitForSync(): Promise<boolean>;
95
+ /**
96
+ * Handle an incoming Broadcast message from a remote peer.
97
+ *
98
+ * Dispatches to type-specific handlers and performs echo suppression
99
+ * (skip messages from our own device).
100
+ */
101
+ private handleBroadcastMessage;
102
+ /**
103
+ * Apply a remote Yjs update to the local document.
104
+ */
105
+ private handleRemoteUpdate;
106
+ /**
107
+ * Handle sync-step-1: a peer is requesting missing updates.
108
+ *
109
+ * We compute the delta between our state and their state vector,
110
+ * then send it back as sync-step-2.
111
+ */
112
+ private handleSyncStep1;
113
+ /**
114
+ * Handle sync-step-2: a peer responded to our sync-step-1 with a delta.
115
+ */
116
+ private handleSyncStep2;
117
+ /**
118
+ * Handle a chunk message — part of a large payload that was split.
119
+ *
120
+ * Buffers chunks until all parts arrive, then reassembles and processes
121
+ * the full payload as a regular message.
122
+ */
123
+ private handleChunk;
124
+ /**
125
+ * Flush all pending updates: merge, encode, and send via Broadcast.
126
+ *
127
+ * If the merged payload exceeds the max size, it is chunked.
128
+ */
129
+ private flushUpdates;
130
+ /**
131
+ * Send sync-step-1 to request missing updates from connected peers.
132
+ */
133
+ private sendSyncStep1;
134
+ /**
135
+ * Send a message via the Supabase Broadcast channel.
136
+ */
137
+ private sendMessage;
138
+ /**
139
+ * Split a large base64 payload into chunks and send each one.
140
+ */
141
+ private sendChunked;
142
+ /**
143
+ * Set up the browser BroadcastChannel for same-device tab sync.
144
+ *
145
+ * This avoids Supabase Broadcast for updates between tabs on the same device,
146
+ * which is faster and doesn't consume any network bandwidth.
147
+ */
148
+ private setupLocalChannel;
149
+ /**
150
+ * Handle a channel disconnect — attempt reconnection with exponential backoff.
151
+ */
152
+ private handleDisconnect;
153
+ /**
154
+ * Track the local user's presence on the Supabase Presence channel.
155
+ *
156
+ * Sends the user's name, avatar, color, and device ID so other collaborators
157
+ * can display cursor badges and avatar lists.
158
+ */
159
+ private trackPresence;
160
+ /**
161
+ * Update the connection state and notify the listener.
162
+ */
163
+ private setConnectionState;
164
+ }
165
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/crdt/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAQzB,OAAO,KAAK,EAMV,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAqCjB;;;;;;;GAOG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,0CAA0C;IAC1C,OAAO,CAAC,OAAO,CAAgC;IAE/C,iEAAiE;IACjE,OAAO,CAAC,YAAY,CAAiC;IAErD,gCAAgC;IAChC,OAAO,CAAC,gBAAgB,CAAuC;IAE/D,sDAAsD;IACtD,OAAO,CAAC,uBAAuB,CAAuD;IAEtF,4DAA4D;IAC5D,OAAO,CAAC,YAAY,CAAqD;IAGzE,OAAO,CAAC,cAAc,CAAoB;IAC1C,OAAO,CAAC,aAAa,CAA8C;IAGnE,OAAO,CAAC,YAAY,CAA0E;IAG9F,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,SAAS,CAAS;IAG1B,OAAO,CAAC,aAAa,CAAsC;gBAGzD,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,CAAC,CAAC,GAAG,EACV,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI;IAahE,+CAA+C;IAC/C,IAAI,eAAe,IAAI,mBAAmB,CAEzC;IAED;;;;;OAKG;IACH,eAAe,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAQjE;;;;;OAKG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAyD3B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsC5B;;;;;;;;OAQG;IACH,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAsBzC;;;;;;;OAOG;IACH,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IA8B/B;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAyB9B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAgBvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAcvB;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IAiCnB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IA4BpB;;OAEG;IACH,OAAO,CAAC,aAAa;IAYrB;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;OAEG;IACH,OAAO,CAAC,WAAW;IA6BnB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAsBzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsCxB;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAerB;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAK3B"}