@livequery/rest 2.0.91 → 2.0.96

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/README.md CHANGED
@@ -1,18 +1,40 @@
1
1
  # @livequery/rest
2
2
 
3
- REST transporter for [livequery](https://github.com/livequery) connects your livequery client to a REST API backend with optional real-time WebSocket support.
3
+ `@livequery/rest` is a REST + WebSocket transporter for `@livequery/core`.
4
+
5
+ It adapts the `LivequeryTransporter` interface to:
6
+
7
+ - HTTP requests for `query`, `add`, `update`, `delete`, and `trigger`
8
+ - optional WebSocket subscriptions for realtime collection updates
9
+ - request/response hooks for auth, caching, logging, or mocking
10
+
11
+ This package does not implement local cache or collection state management. That stays in `@livequery/core`. This package is only the transport layer between a livequery client and your backend.
4
12
 
5
13
  ## Installation
6
14
 
7
15
  ```bash
8
16
  npm install @livequery/rest
9
- # or
17
+ ```
18
+
19
+ ```bash
10
20
  bun add @livequery/rest
11
21
  ```
12
22
 
13
- ## Usage
23
+ ## What It Implements
14
24
 
15
- ### Basic REST
25
+ `RestTransporter` implements the `LivequeryTransporter` contract from `@livequery/core`:
26
+
27
+ - `query()` returns an `Observable<Partial<LivequeryQueryResult>>`
28
+ - `add()` sends `POST`
29
+ - `update()` sends `PATCH`
30
+ - `delete()` sends `DELETE`
31
+ - `trigger()` sends `POST /<ref>/~<action>`
32
+
33
+ When a WebSocket endpoint is configured, `query()` can also attach a realtime subscription and merge server-pushed `DataChangeEvent`s into the observable stream.
34
+
35
+ ## Quick Start
36
+
37
+ ### REST only
16
38
 
17
39
  ```ts
18
40
  import { RestTransporter } from '@livequery/rest'
@@ -22,88 +44,430 @@ const transporter = new RestTransporter({
22
44
  })
23
45
  ```
24
46
 
25
- ### With WebSocket (real-time updates)
47
+ ### REST + realtime
26
48
 
27
49
  ```ts
50
+ import { RestTransporter } from '@livequery/rest'
51
+
52
+ const transporter = new RestTransporter({
53
+ api: 'https://api.example.com',
54
+ ws: 'wss://api.example.com/ws'
55
+ })
56
+ ```
57
+
58
+ ### With `@livequery/core`
59
+
60
+ ```ts
61
+ import { LivequeryCore } from '@livequery/core'
62
+ import { RestTransporter } from '@livequery/rest'
63
+
28
64
  const transporter = new RestTransporter({
29
65
  api: 'https://api.example.com',
30
66
  ws: 'wss://api.example.com/ws'
31
67
  })
68
+
69
+ const core = new LivequeryCore({
70
+ storage,
71
+ transporters: {
72
+ rest: transporter
73
+ }
74
+ })
32
75
  ```
33
76
 
34
- ### Request / Response Hooks
77
+ ## Constructor
78
+
79
+ ```ts
80
+ type RestTransporterConfig = {
81
+ api: string
82
+ ws?: string
83
+ onRequest?: (
84
+ request: RestTransporterRequest & { ref: string }
85
+ ) =>
86
+ | void
87
+ | Partial<RestTransporterRequest & { response?: LivequeryResult<any> }>
88
+ | Promise<void | Partial<RestTransporterRequest & { response?: LivequeryResult<any> }>>
89
+ onResponse?: (
90
+ request: RestTransporterRequest & { ref: string },
91
+ response: LivequeryResult<any>
92
+ ) => void | Promise<void>
93
+ }
94
+ ```
95
+
96
+ ### Options
97
+
98
+ | Option | Type | Description |
99
+ | --- | --- | --- |
100
+ | `api` | `string` | Base HTTP URL used for all REST calls. |
101
+ | `ws` | `string` | Optional WebSocket endpoint for realtime sync. |
102
+ | `onRequest` | `function` | Optional interceptor before `fetch()`. Can override request fields or return a fake response. |
103
+ | `onResponse` | `function` | Optional hook called after the response is resolved. |
35
104
 
36
- Use `onRequest` to modify or intercept outgoing requests (e.g. inject auth headers), and `onResponse` to inspect responses.
105
+ ## Request Model
106
+
107
+ Outgoing requests use this shape internally:
108
+
109
+ ```ts
110
+ type RestTransporterRequest = {
111
+ url: string
112
+ method: string
113
+ query?: Record<string, any>
114
+ body?: Record<string, any> | string
115
+ headers?: Record<string, string | undefined>
116
+ }
117
+ ```
118
+
119
+ The transporter builds URLs like this:
120
+
121
+ ```text
122
+ <api>/<ref>
123
+ <api>/<ref>?<query>
124
+ <api>/<ref>/~<action>
125
+ ```
126
+
127
+ Examples:
128
+
129
+ ```text
130
+ GET /users
131
+ GET /users/123
132
+ POST /users
133
+ PATCH /users/123
134
+ DELETE /users/123
135
+ POST /users/~ban
136
+ ```
137
+
138
+ ## Hooks
139
+
140
+ ### `onRequest`
141
+
142
+ Use `onRequest` to:
143
+
144
+ - inject auth headers
145
+ - override request body or URL
146
+ - short-circuit requests from cache
147
+ - mock server responses in tests
37
148
 
38
149
  ```ts
39
150
  const transporter = new RestTransporter({
40
151
  api: 'https://api.example.com',
41
152
  ws: 'wss://api.example.com/ws',
42
-
43
- onRequest: async ({ url, method, headers, ref }) => {
153
+ onRequest: async ({ headers }) => {
44
154
  const token = await getAccessToken()
155
+
45
156
  return {
46
- headers: { Authorization: `Bearer ${token}` }
157
+ headers: {
158
+ ...headers,
159
+ Authorization: `Bearer ${token}`
160
+ }
47
161
  }
48
- },
162
+ }
163
+ })
164
+ ```
165
+
166
+ To skip the network completely, return a `response`:
167
+
168
+ ```ts
169
+ const transporter = new RestTransporter({
170
+ api: 'https://api.example.com',
171
+ onRequest: ({ ref }) => {
172
+ const cached = cache.get(ref)
173
+ if (!cached) return
174
+
175
+ return {
176
+ response: {
177
+ data: cached
178
+ }
179
+ }
180
+ }
181
+ })
182
+ ```
49
183
 
184
+ ### `onResponse`
185
+
186
+ Use `onResponse` for logging, metrics, or centralized error inspection:
187
+
188
+ ```ts
189
+ const transporter = new RestTransporter({
190
+ api: 'https://api.example.com',
50
191
  onResponse: async (request, response) => {
51
- if (response.error) console.error('API error', response.error)
192
+ if (response.error) {
193
+ console.error('Livequery REST error', {
194
+ url: request.url,
195
+ method: request.method,
196
+ error: response.error
197
+ })
198
+ }
52
199
  }
53
200
  })
54
201
  ```
55
202
 
56
- You can also short-circuit a request by returning a `response` from `onRequest` — useful for caching or mocking:
203
+ ## REST Response Contract
204
+
205
+ The transporter expects your backend to return a `LivequeryResult<T>` envelope:
57
206
 
58
207
  ```ts
59
- onRequest: ({ ref }) => {
60
- const cached = cache.get(ref)
61
- if (cached) return { response: { data: cached } }
208
+ type LivequeryResult<T> = {
209
+ data: T
210
+ error?: {
211
+ code: string
212
+ message: string
213
+ }
62
214
  }
63
215
  ```
64
216
 
65
- ## API
217
+ ### Collection query response
66
218
 
67
- ### `RestTransporter`
219
+ For collection reads, `data` should look like:
220
+
221
+ ```ts
222
+ type LivequeryCollectionResponse<T> = {
223
+ summary?: Record<string, any>
224
+ items: T[]
225
+ subscription_token?: string
226
+ count?: {
227
+ prev: number
228
+ next: number
229
+ total: number
230
+ current: number
231
+ }
232
+ has?: {
233
+ prev: boolean
234
+ next: boolean
235
+ }
236
+ cursor?: {
237
+ first: string
238
+ last: string
239
+ }
240
+ }
241
+ ```
68
242
 
69
- #### Constructor options (`RestTransporterConfig`)
243
+ Example:
244
+
245
+ ```json
246
+ {
247
+ "data": {
248
+ "items": [
249
+ { "id": "u1", "name": "Ada" },
250
+ { "id": "u2", "name": "Linus" }
251
+ ],
252
+ "summary": {
253
+ "active": 2
254
+ },
255
+ "count": {
256
+ "prev": 0,
257
+ "next": 20,
258
+ "current": 2,
259
+ "total": 22
260
+ },
261
+ "has": {
262
+ "prev": false,
263
+ "next": true
264
+ },
265
+ "cursor": {
266
+ "first": "cursor-1",
267
+ "last": "cursor-2"
268
+ },
269
+ "subscription_token": "rt_abc123"
270
+ }
271
+ }
272
+ ```
70
273
 
71
- | Option | Type | Description |
72
- |---|---|---|
73
- | `api` | `string` | Base URL of your REST API |
74
- | `ws` | `string` (optional) | WebSocket endpoint for real-time updates |
75
- | `onRequest` | function (optional) | Interceptor called before each request. Return partial request overrides or a fake response. |
76
- | `onResponse` | function (optional) | Called after each response. |
274
+ ### Single document response
77
275
 
78
- #### Methods
276
+ For document reads, `data` should contain `item`:
79
277
 
80
- These follow the `LivequeryTransporter` interface from `@livequery/core`:
278
+ ```json
279
+ {
280
+ "data": {
281
+ "item": {
282
+ "id": "u1",
283
+ "name": "Ada"
284
+ }
285
+ }
286
+ }
287
+ ```
81
288
 
82
- | Method | Description |
83
- |---|---|
84
- | `query(ref, filters)` | Query a collection or document. Returns an Observable. |
85
- | `add(ref, data)` | Create a new document (`POST`) |
86
- | `update(ref, id, data)` | Update a document (`PATCH`) |
87
- | `delete(ref, id)` | Delete a document (`DELETE`) |
88
- | `trigger({ ref, action, payload })` | Trigger a custom action (`POST /ref/~action`) |
289
+ ### Create response
89
290
 
90
- ### `Socket`
291
+ `add()` accepts either of these backend shapes:
91
292
 
92
- Manages the WebSocket connection lifecycle automatically — reconnects on failure, sends heartbeat pings, and routes server-pushed `DataChangeEvent`s to the appropriate collection streams.
293
+ ```json
294
+ {
295
+ "data": {
296
+ "item": {
297
+ "id": "u1",
298
+ "name": "Ada"
299
+ }
300
+ }
301
+ }
302
+ ```
303
+
304
+ ```json
305
+ {
306
+ "data": {
307
+ "id": "u1",
308
+ "name": "Ada"
309
+ }
310
+ }
311
+ ```
312
+
313
+ ## Query Output Shape
314
+
315
+ `query()` converts server data into the shape expected by `@livequery/core`:
316
+
317
+ ```ts
318
+ type LivequeryQueryResult = {
319
+ changes: DataChangeEvent[]
320
+ summary: Record<string, any>
321
+ paging: {
322
+ total: number
323
+ current: number
324
+ next?: { count: number; cursor: string }
325
+ prev?: { count: number; cursor: string }
326
+ }
327
+ source: 'query' | 'realtime' | 'action'
328
+ error: { code: string; message: string }
329
+ }
330
+ ```
331
+
332
+ Collection responses are converted to `added` events for each returned item. Document responses are converted to a single `added` event for the returned document.
333
+
334
+ ## Realtime
335
+
336
+ If `ws` is provided, the transporter creates a `Socket` instance and adds these headers to REST calls:
337
+
338
+ - `socket_id`
339
+ - `x-lcid`
340
+ - `x-lgid`
341
+
342
+ This lets your backend bind the HTTP query to the active realtime session.
343
+
344
+ ### Realtime flow
345
+
346
+ 1. A collection query returns `subscription_token`.
347
+ 2. The transporter forwards that token to the socket with a `subscribe` event.
348
+ 3. The socket listens for server `sync` messages.
349
+ 4. Incoming changes are emitted as `DataChangeEvent`s with `source: 'realtime'`.
350
+
351
+ Realtime listening is only attached for standard collection queries. It is skipped when:
352
+
353
+ - no `ws` endpoint is configured
354
+ - the request is a cursor query using `:after`
355
+ - the request is a cursor query using `:before`
356
+ - the request is an around query using `:around`
357
+
358
+ ### WebSocket protocol expected by `Socket`
359
+
360
+ When the socket opens, it sends:
361
+
362
+ ```json
363
+ { "event": "start", "data": { "id": "<client_id>" } }
364
+ ```
365
+
366
+ It also sends a heartbeat every 60 seconds:
367
+
368
+ ```json
369
+ { "event": "ping" }
370
+ ```
371
+
372
+ To subscribe a collection query, it sends:
373
+
374
+ ```json
375
+ { "event": "subscribe", "data": { "realtime_token": "<token>" } }
376
+ ```
377
+
378
+ The server should respond with a hello message containing the gateway id:
379
+
380
+ ```json
381
+ { "event": "hello", "gid": "gateway-1" }
382
+ ```
383
+
384
+ Realtime sync messages should look like:
385
+
386
+ ```json
387
+ {
388
+ "event": "sync",
389
+ "data": {
390
+ "changes": [
391
+ {
392
+ "ref": "users",
393
+ "id": "u1",
394
+ "type": "modified",
395
+ "data": { "name": "Ada Lovelace" }
396
+ }
397
+ ]
398
+ }
399
+ }
400
+ ```
401
+
402
+ Each sync change is routed to `listen(ref)` subscribers and normalized to:
403
+
404
+ ```ts
405
+ type DataChangeEvent = {
406
+ collection_ref: string
407
+ id: string
408
+ type: 'added' | 'removed' | 'modified'
409
+ data?: Record<string, any>
410
+ }
411
+ ```
412
+
413
+ ## Public API
414
+
415
+ ### `RestTransporter`
416
+
417
+ ```ts
418
+ import { RestTransporter } from '@livequery/rest'
419
+ ```
420
+
421
+ Methods:
422
+
423
+ - `query({ ref, filters })`
424
+ - `add(ref, data)`
425
+ - `update(collectionRef, id, data)`
426
+ - `delete(collectionRef, id)`
427
+ - `trigger({ ref, action, payload })`
428
+
429
+ ### `Socket`
93
430
 
94
431
  ```ts
95
432
  import { Socket } from '@livequery/rest'
433
+ ```
96
434
 
435
+ Subpath import is also available:
436
+
437
+ ```ts
438
+ import { Socket } from '@livequery/rest/Socket'
439
+ ```
440
+
441
+ The socket class is exported for low-level integrations and debugging.
442
+
443
+ Example:
444
+
445
+ ```ts
97
446
  const socket = new Socket('wss://api.example.com/ws')
98
- socket.listen('users/123').subscribe(change => console.log(change))
99
- socket.stop() // close connection
447
+
448
+ socket.listen('users').subscribe(change => {
449
+ console.log(change)
450
+ })
451
+
452
+ socket.stop()
100
453
  ```
101
454
 
102
- ## Real-time Flow
455
+ ## Error Handling
103
456
 
104
- 1. On `query()`, a `subscription_token` returned by the server is forwarded to the socket.
105
- 2. The socket subscribes to the token and listens for `sync` events from the server.
106
- 3. Incoming changes are emitted as `DataChangeEvent`s with `source: "realtime"`.
457
+ If `fetch()` throws or the backend returns an error envelope, the transporter surfaces it as:
458
+
459
+ ```ts
460
+ {
461
+ error: {
462
+ code: string,
463
+ message: string
464
+ }
465
+ }
466
+ ```
467
+
468
+ For `query()`, errors are emitted as an observable result with `source: 'query'`.
469
+
470
+ For `add()`, `update()`, `delete()`, and `trigger()`, errors are thrown as rejected promises.
107
471
 
108
472
  ## Build
109
473
 
@@ -111,7 +475,18 @@ socket.stop() // close connection
111
475
  bun run build
112
476
  ```
113
477
 
114
- Outputs ESM + type declarations to `dist/`.
478
+ Build steps:
479
+
480
+ - clean `dist/`
481
+ - emit Node.js ESM files with TypeScript (`module: NodeNext`)
482
+ - generate `.js`, `.d.ts`, `.js.map`, and `.d.ts.map`
483
+
484
+ The published package is strict ESM (`"type": "module"`) and exposes these entrypoints:
485
+
486
+ - `@livequery/rest`
487
+ - `@livequery/rest/RestTransporter`
488
+ - `@livequery/rest/Socket`
489
+ - `@livequery/rest/helpers/parseJson`
115
490
 
116
491
  ## License
117
492
 
@@ -0,0 +1,158 @@
1
+ import { of, firstValueFrom, EMPTY, from } from 'rxjs';
2
+ import { catchError, filter, first, map, mergeMap, take } from 'rxjs/operators';
3
+ import { merge } from 'rxjs';
4
+ import { Socket } from './Socket.js';
5
+ import { parseJson } from './helpers/parseJson.js';
6
+ export class RestTransporter {
7
+ config;
8
+ socket;
9
+ constructor(config) {
10
+ this.config = config;
11
+ if (config.ws) {
12
+ this.socket = new Socket(config.ws);
13
+ }
14
+ }
15
+ async #call(req) {
16
+ const url = `${await this.config.api}/${req.ref}${req.action ? `/~${req.action}` : ''}${Object.keys(req.query || {}).length > 0 ? `?${new URLSearchParams(req.query).toString()}` : ''}`;
17
+ const base_headers = {
18
+ ...req.body ? {
19
+ 'Content-Type': 'application/json'
20
+ } : {},
21
+ ...this.socket ? {
22
+ socket_id: this.socket.client_id,
23
+ 'x-lcid': this.socket.client_id,
24
+ 'x-lgid': await firstValueFrom(this.socket.$gateway)
25
+ } : {}
26
+ };
27
+ const original_request = {
28
+ url,
29
+ method: req.method,
30
+ body: req.body,
31
+ headers: base_headers,
32
+ query: req.query,
33
+ ref: req.ref
34
+ };
35
+ const { response: fake_response, headers, ...modified } = await this.config.onRequest?.(original_request) || {};
36
+ if (fake_response) {
37
+ this.config.onResponse && await this.config.onResponse(original_request, fake_response);
38
+ if (fake_response.error)
39
+ throw fake_response.error;
40
+ return fake_response.data;
41
+ }
42
+ const request = {
43
+ ref: req.ref,
44
+ url,
45
+ method: req.method,
46
+ ...req.body ? { body: typeof req.body === 'string' ? req.body : JSON.stringify(req.body) } : {},
47
+ ...modified,
48
+ headers: {
49
+ ...req.body && typeof req.body != 'string' ? { 'Content-Type': 'application/json' } : {},
50
+ ...base_headers,
51
+ ...headers
52
+ },
53
+ };
54
+ const response = fake_response ? fake_response : await (async () => {
55
+ try {
56
+ const result = await fetch(request.url, request);
57
+ return parseJson(await result.text()) || {};
58
+ }
59
+ catch (e) {
60
+ return {
61
+ error: {
62
+ code: e instanceof Error ? e.name : 'UnknownError',
63
+ message: e instanceof Error ? e.message : 'An unknown error occurred'
64
+ }
65
+ };
66
+ }
67
+ })();
68
+ this.config.onResponse && await this.config.onResponse(request, response);
69
+ if (response.error)
70
+ throw response.error;
71
+ return response.data;
72
+ }
73
+ query({ ref, filters }) {
74
+ const ready$ = from(this.socket ? (this.socket.pipe(filter(s => !!s.connected), map(() => Date.now()))) : of(1)).pipe(first());
75
+ const watch$ = (!this.socket || !filters || filters[':after'] || filters[':before'] || filters[':around']) ? EMPTY : this.socket.listen(ref);
76
+ const refs = ref.split('/');
77
+ const collection_ref = refs.length % 2 == 0 ? refs.slice(0, -1).join('/') : ref;
78
+ return merge(ready$.pipe(take(1), mergeMap(() => (from(this.#call({
79
+ ref,
80
+ method: 'GET',
81
+ query: filters
82
+ })).pipe(map(collection => {
83
+ collection.subscription_token && this.socket?.subscribeWith(collection.subscription_token);
84
+ // If collection
85
+ if (collection.items) {
86
+ const items = Array.isArray(collection.items) ? collection.items : [];
87
+ const length = items.length;
88
+ return {
89
+ summary: collection.summary,
90
+ paging: {
91
+ current: collection?.count?.current ?? length,
92
+ total: collection?.count?.total ?? length,
93
+ next: collection?.has?.next ? {
94
+ count: collection?.count?.next || 0,
95
+ cursor: collection?.cursor?.last
96
+ } : undefined,
97
+ prev: collection?.has?.prev ? {
98
+ count: collection?.count?.prev || 0,
99
+ cursor: collection?.cursor?.first
100
+ } : undefined
101
+ },
102
+ changes: items.map(data => ({
103
+ data,
104
+ type: 'added',
105
+ id: data.id,
106
+ collection_ref
107
+ })),
108
+ source: "query"
109
+ };
110
+ }
111
+ // If document
112
+ return {
113
+ summary: collection.summary,
114
+ changes: [{
115
+ data: collection.item,
116
+ type: 'added',
117
+ id: collection.item.id,
118
+ collection_ref
119
+ }],
120
+ source: "query"
121
+ };
122
+ }), catchError(e => {
123
+ const error = e instanceof Error ? { code: e.name, message: e.message } : { code: e.code || 'UnknownError', message: e.message || 'An unknown error occurred' };
124
+ return of({ error, source: "query" });
125
+ }))))), watch$.pipe(map((change) => {
126
+ const id = change.data?.id;
127
+ if (id) {
128
+ const e = {
129
+ changes: [
130
+ {
131
+ ...change,
132
+ collection_ref,
133
+ id
134
+ }
135
+ ],
136
+ source: "realtime"
137
+ };
138
+ return e;
139
+ }
140
+ }), filter(Boolean)));
141
+ }
142
+ async add(ref, data) {
143
+ const r = await this.#call({ method: 'POST', ref, body: data, query: {} });
144
+ if (r.id)
145
+ return r;
146
+ return r.item;
147
+ }
148
+ update(collection_ref, id, data) {
149
+ return this.#call({ method: 'PATCH', ref: collection_ref + '/' + id, body: data, query: {} });
150
+ }
151
+ delete(collection_ref, id) {
152
+ return this.#call({ method: 'DELETE', ref: collection_ref + '/' + id, body: undefined, query: {} });
153
+ }
154
+ trigger({ ref, action, payload }) {
155
+ return this.#call({ method: 'POST', ref, action, body: payload, query: {} });
156
+ }
157
+ }
158
+ //# sourceMappingURL=RestTransporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RestTransporter.js","sourceRoot":"","sources":["../src/RestTransporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAA;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAmCnD,MAAM,OAAO,eAAe;IAKZ;IAHJ,MAAM,CAAoB;IAElC,YACY,MAA6B;QAA7B,WAAM,GAAN,MAAM,CAAuB;QAErC,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,EAAG,CAAC,CAAA;QACxC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK,CAAI,GAA2E;QACtF,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;QACxL,MAAM,YAAY,GAAG;YACjB,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;gBACV,cAAc,EAAE,kBAAkB;aACrC,CAAC,CAAC,CAAC,EAAE;YACN,GAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;gBACd,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;gBAChC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;gBAC/B,QAAQ,EAAE,MAAM,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;aACvD,CAAC,CAAC,CAAC,EAAE;SACT,CAAA;QACD,MAAM,gBAAgB,GAA6C;YAC/D,GAAG;YACH,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,YAAY;YACrB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,GAAG,EAAE,GAAG,CAAC,GAAG;SACf,CAAA;QACD,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAA;QAC/G,IAAI,aAAa,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAA;YACvF,IAAI,aAAa,CAAC,KAAK;gBAAE,MAAM,aAAa,CAAC,KAAK,CAAA;YAClD,OAAO,aAAa,CAAC,IAAS,CAAA;QAClC,CAAC;QACD,MAAM,OAAO,GAAG;YACZ,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,GAAG;YACH,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;YAC/F,GAAG,QAAqB;YACxB,OAAO,EAAE;gBACL,GAAG,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE;gBACxF,GAAG,YAAY;gBACf,GAAG,OAAO;aACb;SACJ,CAAA;QACD,MAAM,QAAQ,GAAuB,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;YACnF,IAAI,CAAC;gBACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBACjD,OAAO,SAAS,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAA;YAC/C,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,OAAO;oBACH,KAAK,EAAE;wBACH,IAAI,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc;wBAClD,OAAO,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,2BAA2B;qBACxE;iBACJ,CAAA;YACL,CAAC;QACL,CAAC,CAAC,EAAE,CAAC;QACL,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QACzE,IAAI,QAAQ,CAAC,KAAK;YAAE,MAAM,QAAQ,CAAC,KAAK,CAAA;QACxC,OAAO,QAAQ,CAAC,IAAI,CAAA;IACxB,CAAC;IAED,KAAK,CAAgB,EAAE,GAAG,EAAE,OAAO,EAAiD;QAChF,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;QAC9H,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5I,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC3B,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;QAE/E,OAAO,KAAK,CAGR,MAAM,CAAC,IAAI,CACP,IAAI,CAAC,CAAC,CAAC,EACP,QAAQ,CAAC,GAAG,EAAE,CAAC,CACX,IAAI,CAAC,IAAI,CAAC,KAAK,CAAiC;YAC5C,GAAG;YACH,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,OAAO;SACjB,CAAC,CAAC,CAAC,IAAI,CACJ,GAAG,CAAC,UAAU,CAAC,EAAE;YACb,UAAU,CAAC,kBAAkB,IAAI,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAA;YAC1F,gBAAgB;YAChB,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;gBACnB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;gBACrE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;gBAC3B,OAAO;oBACH,OAAO,EAAE,UAAU,CAAC,OAAO;oBAC3B,MAAM,EAAE;wBACJ,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,IAAI,MAAM;wBAC7C,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,IAAI,MAAM;wBACzC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;4BAC1B,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC;4BACnC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI;yBACnC,CAAC,CAAC,CAAC,SAAS;wBACb,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;4BAC1B,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC;4BACnC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK;yBACpC,CAAC,CAAC,CAAC,SAAS;qBAChB;oBACD,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;wBACxB,IAAI;wBACJ,IAAI,EAAE,OAAO;wBACb,EAAE,EAAE,IAAI,CAAC,EAAE;wBACX,cAAc;qBACjB,CAAC,CAAC;oBACH,MAAM,EAAE,OAAO;iBACe,CAAA;YACtC,CAAC;YAED,gBAAgB;YAChB,OAAO;gBACH,OAAO,EAAE,UAAU,CAAC,OAAO;gBAC3B,OAAO,EAAE,CAAC;wBACN,IAAI,EAAE,UAAU,CAAC,IAAI;wBACrB,IAAI,EAAE,OAAO;wBACb,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE;wBACtB,cAAc;qBACjB,CAAC;gBACF,MAAM,EAAE,OAAO;aACe,CAAA;QACtC,CAAC,CAAC,EACF,UAAU,CAAC,CAAC,CAAC,EAAE;YACX,MAAM,KAAK,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,2BAA2B,EAAE,CAAA;YAC/J,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAmC,CAAC,CAAA;QAC1E,CAAC,CAAC,CAEL,CAAC,CAAC,CACV,EAED,MAAM,CAAC,IAAI,CACP,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YACX,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,CAAA;YAC1B,IAAI,EAAE,EAAE,CAAC;gBACL,MAAM,CAAC,GAAkC;oBACrC,OAAO,EAAE;wBACL;4BACI,GAAG,MAAM;4BACT,cAAc;4BACd,EAAE;yBACL;qBACJ;oBACD,MAAM,EAAE,UAAU;iBACrB,CAAA;gBACD,OAAO,CAAC,CAAA;YACZ,CAAC;QACL,CAAC,CAAC,EACF,MAAM,CAAC,OAAO,CAAC,CAClB,CACJ,CAAA;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAgB,GAAW,EAAE,IAA4B;QAC9D,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAA0B,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;QACnG,IAAI,CAAC,CAAC,EAAE;YAAE,OAAO,CAAa,CAAA;QAC9B,OAAO,CAAC,CAAC,IAAI,CAAA;IACjB,CAAC;IAED,MAAM,CAAgB,cAAsB,EAAE,EAAU,EAAE,IAAgB;QACtE,OAAO,IAAI,CAAC,KAAK,CAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,GAAG,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;IACpG,CAAC;IAED,MAAM,CAAgB,cAAsB,EAAE,EAAU;QACpD,OAAO,IAAI,CAAC,KAAK,CAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,cAAc,GAAG,GAAG,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;IAC1G,CAAC;IAGD,OAAO,CAAI,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAmB;QAChD,OAAO,IAAI,CAAC,KAAK,CAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;IACnF,CAAC;CACJ"}