@syncular/client 0.0.6-125 → 0.0.6-135

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/src/migrate.ts CHANGED
@@ -2,195 +2,9 @@
2
2
  * @syncular/client - Sync migrations (SQLite reference)
3
3
  */
4
4
 
5
- import { type Kysely, sql } from 'kysely';
5
+ import type { Kysely } from 'kysely';
6
6
  import type { SyncClientDb } from './schema';
7
7
 
8
- type SyncInternalTable =
9
- | 'sync_subscription_state'
10
- | 'sync_outbox_commits'
11
- | 'sync_conflicts';
12
-
13
- function toErrorMessage(error: unknown): string {
14
- return error instanceof Error ? error.message : String(error);
15
- }
16
-
17
- function isMissingTableError(message: string): boolean {
18
- const normalized = message.toLowerCase();
19
- return (
20
- normalized.includes('no such table') ||
21
- (normalized.includes('relation') && normalized.includes('does not exist'))
22
- );
23
- }
24
-
25
- function isMissingColumnError(message: string): boolean {
26
- const normalized = message.toLowerCase();
27
- return (
28
- normalized.includes('no such column') ||
29
- (normalized.includes('column') && normalized.includes('does not exist'))
30
- );
31
- }
32
-
33
- function isDuplicateColumnError(message: string): boolean {
34
- const normalized = message.toLowerCase();
35
- return (
36
- normalized.includes('duplicate column name') ||
37
- (normalized.includes('column') && normalized.includes('already exists'))
38
- );
39
- }
40
-
41
- async function getColumnNames<DB extends SyncClientDb>(
42
- db: Kysely<DB>,
43
- tableName: SyncInternalTable
44
- ): Promise<Set<string> | null> {
45
- try {
46
- const sqlite = await sql<{ name: string }>`
47
- select name from pragma_table_info(${sql.val(tableName)})
48
- `.execute(db);
49
- return new Set(sqlite.rows.map((row) => String(row.name)));
50
- } catch {
51
- // Not SQLite or pragma unavailable.
52
- }
53
-
54
- try {
55
- const postgres = await sql<{ name: string }>`
56
- select column_name as name
57
- from information_schema.columns
58
- where table_name = ${sql.val(tableName)}
59
- `.execute(db);
60
- return new Set(postgres.rows.map((row) => String(row.name)));
61
- } catch {
62
- // Introspection unavailable; caller falls back to probing.
63
- }
64
-
65
- return null;
66
- }
67
-
68
- async function hasColumn<DB extends SyncClientDb>(
69
- db: Kysely<DB>,
70
- tableName: SyncInternalTable,
71
- columnName: string
72
- ): Promise<boolean> {
73
- const columns = await getColumnNames(db, tableName);
74
- if (columns) {
75
- return columns.has(columnName);
76
- }
77
-
78
- try {
79
- await sql`select ${sql.ref(columnName)} from ${sql.table(tableName)} limit 1`.execute(
80
- db
81
- );
82
- return true;
83
- } catch (error) {
84
- const message = toErrorMessage(error);
85
- if (isMissingTableError(message) || isMissingColumnError(message)) {
86
- return false;
87
- }
88
- throw error;
89
- }
90
- }
91
-
92
- async function addColumnIfMissing<DB extends SyncClientDb>(
93
- db: Kysely<DB>,
94
- tableName: SyncInternalTable,
95
- columnName: string,
96
- addColumn: () => Promise<void>
97
- ): Promise<void> {
98
- if (await hasColumn(db, tableName, columnName)) {
99
- return;
100
- }
101
- try {
102
- await addColumn();
103
- } catch (error) {
104
- const message = toErrorMessage(error);
105
- if (isDuplicateColumnError(message)) {
106
- return;
107
- }
108
- throw error;
109
- }
110
- }
111
-
112
- async function ensureClientSyncSchemaCompat<DB extends SyncClientDb>(
113
- db: Kysely<DB>
114
- ): Promise<void> {
115
- const hasTableColumn = await hasColumn(
116
- db,
117
- 'sync_subscription_state',
118
- 'table'
119
- );
120
- if (
121
- !hasTableColumn &&
122
- (await hasColumn(db, 'sync_subscription_state', 'shape'))
123
- ) {
124
- try {
125
- await sql`alter table ${sql.table('sync_subscription_state')} rename column ${sql.ref('shape')} to ${sql.ref('table')}`.execute(
126
- db
127
- );
128
- } catch {
129
- await addColumnIfMissing(
130
- db,
131
- 'sync_subscription_state',
132
- 'table',
133
- async () => {
134
- await db.schema
135
- .alterTable('sync_subscription_state')
136
- .addColumn('table', 'text', (col) => col.notNull().defaultTo(''))
137
- .execute();
138
- }
139
- );
140
- await sql`update ${sql.table('sync_subscription_state')}
141
- set ${sql.ref('table')} = ${sql.ref('shape')}
142
- where ${sql.ref('table')} = ${sql.val('')}`.execute(db);
143
- }
144
- }
145
-
146
- await addColumnIfMissing(
147
- db,
148
- 'sync_subscription_state',
149
- 'bootstrap_state_json',
150
- async () => {
151
- await db.schema
152
- .alterTable('sync_subscription_state')
153
- .addColumn('bootstrap_state_json', 'text')
154
- .execute();
155
- }
156
- );
157
-
158
- await addColumnIfMissing(
159
- db,
160
- 'sync_outbox_commits',
161
- 'schema_version',
162
- async () => {
163
- await db.schema
164
- .alterTable('sync_outbox_commits')
165
- .addColumn('schema_version', 'integer', (col) =>
166
- col.notNull().defaultTo(1)
167
- )
168
- .execute();
169
- }
170
- );
171
-
172
- await addColumnIfMissing(db, 'sync_conflicts', 'resolved_at', async () => {
173
- await db.schema
174
- .alterTable('sync_conflicts')
175
- .addColumn('resolved_at', 'bigint')
176
- .execute();
177
- });
178
-
179
- await addColumnIfMissing(db, 'sync_conflicts', 'resolution', async () => {
180
- await db.schema
181
- .alterTable('sync_conflicts')
182
- .addColumn('resolution', 'text')
183
- .execute();
184
- });
185
-
186
- await db.schema
187
- .createIndex('idx_sync_outbox_commits_status_updated_at')
188
- .ifNotExists()
189
- .on('sync_outbox_commits')
190
- .columns(['status', 'updated_at', 'created_at'])
191
- .execute();
192
- }
193
-
194
8
  /**
195
9
  * Ensures the client sync schema exists in the database.
196
10
  * Safe to call multiple times (idempotent).
@@ -249,9 +63,6 @@ export async function ensureClientSyncSchema<DB extends SyncClientDb>(
249
63
  .addColumn('resolution', 'text')
250
64
  .execute();
251
65
 
252
- // Apply framework-managed compatibility upgrades for legacy sync tables.
253
- await ensureClientSyncSchemaCompat(db);
254
-
255
66
  await db.schema
256
67
  .createIndex('idx_sync_subscription_state_state_sub')
257
68
  .ifNotExists()
package/src/mutations.ts CHANGED
@@ -168,7 +168,7 @@ function coerceBaseVersion(value: unknown): number | null {
168
168
  if (value === null || value === undefined) return null;
169
169
  const n = typeof value === 'number' ? value : Number(value);
170
170
  if (!Number.isFinite(n)) return null;
171
- if (n <= 0) return null;
171
+ if (n < 0) return null;
172
172
  return n;
173
173
  }
174
174
 
@@ -498,6 +498,9 @@ export function createOutboxCommit<DB extends SyncClientDb>(
498
498
  },
499
499
 
500
500
  async insertMany(rows) {
501
+ if (rows.length === 0) {
502
+ throw new Error('insertMany requires at least one row');
503
+ }
501
504
  const ids: string[] = [];
502
505
  const toInsert: Record<string, unknown>[] = [];
503
506
 
@@ -672,6 +675,7 @@ export function createOutboxCommit<DB extends SyncClientDb>(
672
675
  get(_target, prop) {
673
676
  if (prop === 'then') return undefined;
674
677
  if (typeof prop !== 'string') return undefined;
678
+ validateTableName(prop);
675
679
  return makeTxTable(prop);
676
680
  },
677
681
  }
@@ -777,6 +781,9 @@ export function createPushCommit<DB = AnyDb>(
777
781
  },
778
782
 
779
783
  async insertMany(rows) {
784
+ if (rows.length === 0) {
785
+ throw new Error('insertMany requires at least one row');
786
+ }
780
787
  const ids: string[] = [];
781
788
  const toUpsert: Record<string, unknown>[] = [];
782
789
 
@@ -885,6 +892,7 @@ export function createPushCommit<DB = AnyDb>(
885
892
  get(_target, prop) {
886
893
  if (prop === 'then') return undefined;
887
894
  if (typeof prop !== 'string') return undefined;
895
+ validateTableName(prop);
888
896
  return makeTxTable(prop);
889
897
  },
890
898
  }
package/src/sync-loop.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type {
8
+ SyncCombinedResponse,
8
9
  SyncPullResponse,
9
10
  SyncPullSubscriptionResponse,
10
11
  SyncPushRequest,
@@ -244,25 +245,34 @@ async function syncOnceCombined<DB extends SyncClientDb>(
244
245
  }
245
246
  }
246
247
 
247
- const combined = await transport.sync({
248
- clientId,
249
- ...(pushRequest && !wsPushResponse
250
- ? {
251
- push: {
252
- clientCommitId: pushRequest.clientCommitId,
253
- operations: pushRequest.operations,
254
- schemaVersion: pushRequest.schemaVersion,
255
- },
256
- }
257
- : {}),
258
- pull: {
259
- limitCommits: pullState.request.limitCommits,
260
- limitSnapshotRows: pullState.request.limitSnapshotRows,
261
- maxSnapshotPages: pullState.request.maxSnapshotPages,
262
- dedupeRows: pullState.request.dedupeRows,
263
- subscriptions: pullState.request.subscriptions,
264
- },
265
- });
248
+ let combined: SyncCombinedResponse;
249
+ try {
250
+ combined = await transport.sync({
251
+ clientId,
252
+ ...(pushRequest && !wsPushResponse
253
+ ? {
254
+ push: {
255
+ clientCommitId: pushRequest.clientCommitId,
256
+ operations: pushRequest.operations,
257
+ schemaVersion: pushRequest.schemaVersion,
258
+ },
259
+ }
260
+ : {}),
261
+ pull: {
262
+ limitCommits: pullState.request.limitCommits,
263
+ limitSnapshotRows: pullState.request.limitSnapshotRows,
264
+ maxSnapshotPages: pullState.request.maxSnapshotPages,
265
+ dedupeRows: pullState.request.dedupeRows,
266
+ subscriptions: pullState.request.subscriptions,
267
+ },
268
+ });
269
+ } catch (err) {
270
+ if (outbox) {
271
+ const message = err instanceof Error ? err.message : 'Unknown error';
272
+ await markOutboxCommitPending(db, { id: outbox.id, error: message });
273
+ }
274
+ throw err;
275
+ }
266
276
 
267
277
  // Process push response
268
278
  let pushedCommits = 0;
@@ -1,345 +0,0 @@
1
- /**
2
- * @syncular/client - Client-side blob manager
3
- *
4
- * Handles blob upload/download with:
5
- * - Local caching for offline access
6
- * - Upload queue for offline uploads
7
- * - SHA-256 hash computation
8
- * - Optional client-side encryption
9
- */
10
- import type { BlobTransport } from '@syncular/core';
11
- import type { Kysely } from 'kysely';
12
- import type { BlobUploadStatus, SyncBlobClientDb } from './types';
13
- export type { BlobTransport } from '@syncular/core';
14
- interface BlobEncryption {
15
- /**
16
- * Encrypt blob content.
17
- * Returns encrypted bytes and the key ID used.
18
- */
19
- encrypt(data: Uint8Array, options?: {
20
- keyId?: string;
21
- }): Promise<{
22
- encrypted: Uint8Array;
23
- keyId: string;
24
- }>;
25
- /**
26
- * Decrypt blob content.
27
- */
28
- decrypt(data: Uint8Array, keyId: string): Promise<Uint8Array>;
29
- }
30
- export interface ClientBlobManagerOptions {
31
- /** Kysely database instance */
32
- db: Kysely<SyncBlobClientDb>;
33
- /** Blob transport for server communication */
34
- transport: BlobTransport;
35
- /** Optional encryption handler */
36
- encryption?: BlobEncryption;
37
- /** Maximum cache size in bytes. Default: 100MB */
38
- maxCacheSize?: number;
39
- /** Maximum retry attempts for uploads. Default: 3 */
40
- maxUploadRetries?: number;
41
- /** Custom fetch function for blob uploads/downloads. Default: globalThis.fetch */
42
- fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
43
- }
44
- export interface UploadOptions {
45
- /** Encrypt the blob before uploading */
46
- encrypt?: boolean;
47
- /** Specific encryption key ID to use */
48
- keyId?: string;
49
- /** Skip queuing and upload immediately (blocks until complete) */
50
- immediate?: boolean;
51
- }
52
- export interface DownloadOptions {
53
- /** Skip cache lookup and always fetch from server */
54
- skipCache?: boolean;
55
- /** Update last_accessed_at in cache */
56
- updateAccessTime?: boolean;
57
- }
58
- /**
59
- * Create a client-side blob manager.
60
- *
61
- * @example
62
- * ```typescript
63
- * const blobManager = createClientBlobManager({
64
- * db,
65
- * transport: {
66
- * async initiateUpload(args) {
67
- * const res = await fetch('/api/sync/blobs/upload', {
68
- * method: 'POST',
69
- * body: JSON.stringify(args),
70
- * });
71
- * return res.json();
72
- * },
73
- * async completeUpload(hash) {
74
- * const res = await fetch(`/api/sync/blobs/${hash}/complete`, { method: 'POST' });
75
- * return res.json();
76
- * },
77
- * async getDownloadUrl(hash) {
78
- * const res = await fetch(`/api/sync/blobs/${hash}/url`);
79
- * return res.json();
80
- * },
81
- * },
82
- * });
83
- *
84
- * // Upload a file
85
- * const blobRef = await blobManager.upload(file);
86
- *
87
- * // Download a blob
88
- * const blob = await blobManager.download(blobRef);
89
- * ```
90
- */
91
- export declare function createClientBlobManager(options: ClientBlobManagerOptions): {
92
- /**
93
- * Upload a blob to the server.
94
- *
95
- * If `immediate` is false (default), the blob is queued for background upload.
96
- * If `immediate` is true, the upload blocks until complete.
97
- */
98
- upload(data: Blob | File | Uint8Array<ArrayBufferLike>, opts?: UploadOptions | undefined): Promise<{
99
- hash: string;
100
- size: number;
101
- mimeType: string;
102
- encrypted?: boolean | undefined;
103
- keyId?: string | undefined;
104
- }>;
105
- /**
106
- * Download a blob.
107
- *
108
- * First checks the local cache, then fetches from server if needed.
109
- * Automatically decrypts if the blob was encrypted.
110
- */
111
- download(ref: {
112
- hash: string;
113
- size: number;
114
- mimeType: string;
115
- encrypted?: boolean | undefined;
116
- keyId?: string | undefined;
117
- }, opts?: DownloadOptions | undefined): Promise<Uint8Array<ArrayBufferLike>>;
118
- /**
119
- * Check if a blob is cached locally.
120
- */
121
- isCached(hash: string): Promise<boolean>;
122
- /**
123
- * Get a blob URL for display.
124
- *
125
- * Returns a blob: URL if cached locally, or fetches and creates one.
126
- */
127
- getBlobUrl(ref: {
128
- hash: string;
129
- size: number;
130
- mimeType: string;
131
- encrypted?: boolean | undefined;
132
- keyId?: string | undefined;
133
- }): Promise<string>;
134
- /**
135
- * Preload blobs into the cache.
136
- */
137
- preload(refs: {
138
- hash: string;
139
- size: number;
140
- mimeType: string;
141
- encrypted?: boolean | undefined;
142
- keyId?: string | undefined;
143
- }[]): Promise<void>;
144
- /**
145
- * Process pending uploads in the outbox.
146
- *
147
- * Call this periodically or when online to sync pending uploads.
148
- * Returns the number of blobs processed.
149
- */
150
- processUploadQueue(): Promise<{
151
- uploaded: number;
152
- failed: number;
153
- errors: {
154
- hash: string;
155
- error: string;
156
- }[];
157
- }>;
158
- /**
159
- * Get the status of a pending upload.
160
- */
161
- getUploadStatus(hash: string): Promise<{
162
- status: BlobUploadStatus;
163
- error?: string | undefined;
164
- } | null>;
165
- /**
166
- * Clear failed uploads from the outbox.
167
- */
168
- clearFailedUploads(): Promise<number>;
169
- /**
170
- * Retry a failed upload.
171
- */
172
- retryUpload(hash: string): Promise<boolean>;
173
- /**
174
- * Prune the cache to stay under maxCacheSize.
175
- * Uses LRU (least recently used) eviction.
176
- */
177
- pruneCache(): Promise<{
178
- evicted: number;
179
- freedBytes: number;
180
- }>;
181
- /**
182
- * Clear the entire cache.
183
- */
184
- clearCache(): Promise<number>;
185
- /**
186
- * Get cache statistics.
187
- */
188
- getCacheStats(): Promise<{
189
- count: number;
190
- totalSize: number;
191
- maxSize: number;
192
- }>;
193
- /**
194
- * Get upload queue statistics.
195
- */
196
- getUploadQueueStats(): Promise<{
197
- pending: number;
198
- uploading: number;
199
- failed: number;
200
- total: number;
201
- }>;
202
- };
203
- type ClientBlobManager = ReturnType<typeof createClientBlobManager>;
204
- interface BlobCachePruneSchedulerOptions {
205
- /** Client blob manager instance */
206
- blobManager: ClientBlobManager;
207
- /** Interval between prune runs in milliseconds. Default: 300000 (5 minutes) */
208
- intervalMs?: number;
209
- /** Optional: Called after each prune run */
210
- onPrune?: (result: {
211
- evicted: number;
212
- freedBytes: number;
213
- error?: Error;
214
- }) => void;
215
- }
216
- /**
217
- * Create a cache pruning scheduler for the client blob manager.
218
- *
219
- * Periodically prunes the cache to stay under maxCacheSize using LRU eviction.
220
- *
221
- * @example
222
- * ```typescript
223
- * const scheduler = createBlobCachePruneScheduler({
224
- * blobManager,
225
- * intervalMs: 5 * 60 * 1000, // 5 minutes
226
- * onPrune: (result) => {
227
- * if (result.evicted > 0) {
228
- * console.log(`Cache pruned: ${result.evicted} blobs, ${result.freedBytes} bytes freed`);
229
- * }
230
- * },
231
- * });
232
- *
233
- * // Start the scheduler
234
- * scheduler.start();
235
- *
236
- * // Stop when unmounting/shutting down
237
- * scheduler.stop();
238
- * ```
239
- */
240
- export declare function createBlobCachePruneScheduler(options: BlobCachePruneSchedulerOptions): {
241
- /**
242
- * Start the prune scheduler.
243
- * Optionally runs an immediate prune before starting the interval.
244
- */
245
- start(options?: {
246
- immediate?: boolean | undefined;
247
- } | undefined): void;
248
- /**
249
- * Stop the prune scheduler.
250
- */
251
- stop(): void;
252
- /**
253
- * Run a single prune manually.
254
- */
255
- runOnce(): Promise<{
256
- evicted: number;
257
- freedBytes: number;
258
- error?: Error | undefined;
259
- }>;
260
- /**
261
- * Check if the scheduler is currently active.
262
- */
263
- readonly active: boolean;
264
- /**
265
- * Check if a prune is currently in progress.
266
- */
267
- readonly running: boolean;
268
- };
269
- interface BlobUploadQueueSchedulerOptions {
270
- /** Client blob manager instance */
271
- blobManager: ClientBlobManager;
272
- /** Interval between processing runs in milliseconds. Default: 30000 (30 seconds) */
273
- intervalMs?: number;
274
- /** Optional: Called after each processing run */
275
- onProcess?: (result: {
276
- uploaded: number;
277
- failed: number;
278
- errors: Array<{
279
- hash: string;
280
- error: string;
281
- }>;
282
- error?: Error;
283
- }) => void;
284
- }
285
- /**
286
- * Create an upload queue processor scheduler for the client blob manager.
287
- *
288
- * Periodically processes pending uploads when online.
289
- *
290
- * @example
291
- * ```typescript
292
- * const scheduler = createBlobUploadQueueScheduler({
293
- * blobManager,
294
- * intervalMs: 30 * 1000, // 30 seconds
295
- * onProcess: (result) => {
296
- * if (result.uploaded > 0) {
297
- * console.log(`Uploaded ${result.uploaded} blobs`);
298
- * }
299
- * if (result.failed > 0) {
300
- * console.warn(`Failed to upload ${result.failed} blobs`);
301
- * }
302
- * },
303
- * });
304
- *
305
- * // Start when online
306
- * scheduler.start();
307
- *
308
- * // Stop when offline or shutting down
309
- * scheduler.stop();
310
- * ```
311
- */
312
- export declare function createBlobUploadQueueScheduler(options: BlobUploadQueueSchedulerOptions): {
313
- /**
314
- * Start the upload queue processor.
315
- * Optionally runs an immediate processing before starting the interval.
316
- */
317
- start(options?: {
318
- immediate?: boolean | undefined;
319
- } | undefined): void;
320
- /**
321
- * Stop the upload queue processor.
322
- */
323
- stop(): void;
324
- /**
325
- * Run a single processing manually.
326
- */
327
- runOnce(): Promise<{
328
- uploaded: number;
329
- failed: number;
330
- errors: {
331
- hash: string;
332
- error: string;
333
- }[];
334
- error?: Error | undefined;
335
- }>;
336
- /**
337
- * Check if the processor is currently active.
338
- */
339
- readonly active: boolean;
340
- /**
341
- * Check if processing is currently in progress.
342
- */
343
- readonly running: boolean;
344
- };
345
- //# sourceMappingURL=manager.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/blobs/manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAW,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGlE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAMpD,UAAU,cAAc;IACtB;;;OAGG;IACH,OAAO,CACL,IAAI,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3B,OAAO,CAAC;QAAE,SAAS,EAAE,UAAU,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAErD;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,wBAAwB;IACvC,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC7B,8CAA8C;IAC9C,SAAS,EAAE,aAAa,CAAC;IACzB,kCAAkC;IAClC,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC7E;AAED,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB;IAWrE;;;;;OAKG;;;;;;;;IA+FH;;;;;OAKG;;;;;;;;IAoFH;;OAEG;;IAUH;;;;OAIG;;;;;;;;IASH;;OAEG;;;;;;;;IAKH;;;;;OAKG;;;;;;;;;IAmHH;;OAEG;;;;;IAcH;;OAEG;;IASH;;OAEG;;IAgBH;;;OAGG;;;;;IAwCH;;OAEG;;IAMH;;OAEG;;;;;;IAqBH;;OAEG;;;;;;;EAkIN;AAED,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAMpE,UAAU,8BAA8B;IACtC,mCAAmC;IACnC,WAAW,EAAE,iBAAiB,CAAC;IAC/B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,KAAK,CAAC;KACf,KAAK,IAAI,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,8BAA8B;IAoCrC;;;OAGG;;;;IAeH;;OAEG;;IAQH;;OAEG;;;;;;IASH;;OAEG;;IAKH;;OAEG;;EAKN;AAMD,UAAU,+BAA+B;IACvC,mCAAmC;IACnC,WAAW,EAAE,iBAAiB,CAAC;IAC/B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC/C,KAAK,CAAC,EAAE,KAAK,CAAC;KACf,KAAK,IAAI,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B;IAsCtC;;;OAGG;;;;IAeH;;OAEG;;IAQH;;OAEG;;;;;;;;;;IAUH;;OAEG;;IAKH;;OAEG;;EAKN"}