swarpc 0.10.0 → 0.12.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.
package/README.md CHANGED
@@ -1,153 +1,240 @@
1
- <div align=center>
2
- <h1>
3
- <img src="./logo.svg" alt="sw&rpc" />
4
- </h1>
5
-
6
- RPC for Service Workers -- move that heavy computation off of your UI thread!
7
-
8
- </div>
9
-
10
- * * *
11
-
12
- ## Installation
13
-
14
- ```bash
15
- npm add swarpc arktype
16
- ```
17
-
18
- ## Usage
19
-
20
- ### 1. Declare your procedures in a shared file
21
-
22
- ```typescript
23
- import type { ProceduresMap } from "swarpc"
24
- import { type } from "arktype"
25
-
26
- export const procedures = {
27
- searchIMDb: {
28
- // Input for the procedure
29
- input: type({ query: "string", "pageSize?": "number" }),
30
- // Function to be called whenever you can update progress while the procedure is running -- long computations are a first-class concern here. Examples include using the fetch-progress NPM package.
31
- progress: type({ transferred: "number", total: "number" }),
32
- // Output of a successful procedure call
33
- success: type({
34
- id: "string",
35
- primary_title: "string",
36
- genres: "string[]",
37
- }).array(),
38
- },
39
- } as const satisfies ProceduresMap
40
- ```
41
-
42
- ### 2. Register your procedures in the service worker
43
-
44
- In your service worker file:
45
-
46
- ```javascript
47
- import fetchProgress from "fetch-progress"
48
- import { Server } from "swarpc"
49
- import { procedures } from "./procedures.js"
50
-
51
- // 1. Give yourself a server instance
52
- const swarpc = Server(procedures)
53
-
54
- // 2. Implement your procedures
55
- swarpc.searchIMDb(async ({ query, pageSize = 10 }, onProgress) => {
56
- const queryParams = new URLSearchParams({
57
- page_size: pageSize.toString(),
58
- query,
59
- })
60
-
61
- return fetch(`https://rest.imdbapi.dev/v2/search/titles?${queryParams}`)
62
- .then(fetchProgress({ onProgress }))
63
- .then((response) => response.json())
64
- .then(({ titles } => titles)
65
- })
66
-
67
- // ...
68
-
69
- // 3. Start the event listener
70
- swarpc.start(self)
71
- ```
72
-
73
- ### 3. Call your procedures from the client
74
-
75
- Here's a Svelte example!
76
-
77
- ```svelte
78
- <script>
79
- import { Client } from "swarpc"
80
- import { procedures } from "./procedures.js"
81
-
82
- const swarpc = Client(procedures)
83
-
84
- let query = $state("")
85
- let results = $state([])
86
- let progress = $state(0)
87
- </script>
88
-
89
- <search>
90
- <input type="text" bind:value={query} placeholder="Search IMDb" />
91
- <button onclick={async () => {
92
- results = await swarpc.searchIMDb({ query }, (p) => {
93
- progress = p.transferred / p.total
94
- })
95
- }}>
96
- Search
97
- </button>
98
- </search>
99
-
100
- {#if progress > 0 && progress < 1}
101
- <progress value={progress} max="1" />
102
- {/if}
103
-
104
- <ul>
105
- {#each results as { id, primary_title, genres } (id)}
106
- <li>{primary_title} - {genres.join(", ")}</li>
107
- {/each}
108
- </ul>
109
- ```
110
-
111
- ### Make cancelable requests
112
-
113
- #### Implementation
114
-
115
- To make your procedures meaningfully cancelable, you have to make use of the [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) API. This is passed as a third argument when implementing your procedures:
116
-
117
- ```js
118
- server.searchIMDb(async ({ query }, onProgress, abort) => {
119
- // If you're doing heavy computation without fetch:
120
- let aborted = false
121
- abort?.addEventListener("abort", () => {
122
- aborted = true
123
- })
124
-
125
- // Use `aborted` to check if the request was canceled within your hot loop
126
- for (...) {
127
- /* here */ if (aborted) return
128
- ...
129
- }
130
-
131
- // When using fetch:
132
- await fetch(..., { signal: abort })
133
- })
134
- ```
135
-
136
- #### Call sites
137
-
138
- Instead of calling `await client.myProcedure()` directly, call `client.myProcedure.cancelable()`. You'll get back an object with
139
-
140
- - `async cancel(reason)`: a function to cancel the request
141
- - `request`: a Promise that resolves to the result of the procedure call. `await` it to wait for the request to finish.
142
-
143
- Example:
144
-
145
- ```js
146
- // Normal call:
147
- const result = await swarpc.searchIMDb({ query })
148
-
149
- // Cancelable call:
150
- const { request, cancel } = swarpc.searchIMDb.cancelable({ query })
151
- setTimeout(() => cancel().then(() => console.warn("Took too long!!")), 5_000)
152
- await request
153
- ```
1
+ <div align=center>
2
+ <h1>
3
+ <img src="./logo.svg" alt="sw&rpc" />
4
+ </h1>
5
+
6
+ RPC for Service Workers -- move that heavy computation off of your UI thread!
7
+
8
+ </div>
9
+
10
+ * * *
11
+
12
+ ## Features
13
+
14
+ - Fully typesafe
15
+ - Cancelable requests
16
+ - Parallelization with multiple worker instances
17
+ - Automatic [transfer](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects) of transferable values from- and to- worker code
18
+ - A way to polyfill a pre-filled `localStorage` to be accessed within the worker code
19
+ - Supports [Service workers](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker), [Shared workers](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) and [Dedicated workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker)
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm add swarpc arktype
25
+ ```
26
+
27
+ ### Bleeding edge
28
+
29
+ If you want to use the latest commit instead of a published version, you can, either by using the Git URL:
30
+
31
+ ```bash
32
+ npm add git+https://github.com/gwennlbh/swarpc.git
33
+ ```
34
+
35
+ Or by straight up cloning the repository and pointing to the local directory (very useful to hack on sw&rpc while testing out your changes on a more substantial project):
36
+
37
+ ```bash
38
+ mkdir -p vendored
39
+ git clone https://github.com/gwennlbh/swarpc.git vendored/swarpc
40
+ npm add file:vendored/swarpc
41
+ ```
42
+
43
+ This works thanks to the fact that `dist/` is published on the repository (and kept up to date with a CI workflow).
44
+
45
+ ## Usage
46
+
47
+ ### 1. Declare your procedures in a shared file
48
+
49
+ ```typescript
50
+ import type { ProceduresMap } from "swarpc";
51
+ import { type } from "arktype";
52
+
53
+ export const procedures = {
54
+ searchIMDb: {
55
+ // Input for the procedure
56
+ input: type({ query: "string", "pageSize?": "number" }),
57
+ // Function to be called whenever you can update progress while the procedure is running -- long computations are a first-class concern here. Examples include using the fetch-progress NPM package.
58
+ progress: type({ transferred: "number", total: "number" }),
59
+ // Output of a successful procedure call
60
+ success: type({
61
+ id: "string",
62
+ primary_title: "string",
63
+ genres: "string[]",
64
+ }).array(),
65
+ },
66
+ } as const satisfies ProceduresMap;
67
+ ```
68
+
69
+ ### 2. Register your procedures in the service worker
70
+
71
+ In your service worker file:
72
+
73
+ ```javascript
74
+ import fetchProgress from "fetch-progress"
75
+ import { Server } from "swarpc"
76
+ import { procedures } from "./procedures.js"
77
+
78
+ // 1. Give yourself a server instance
79
+ const swarpc = Server(procedures)
80
+
81
+ // 2. Implement your procedures
82
+ swarpc.searchIMDb(async ({ query, pageSize = 10 }, onProgress) => {
83
+ const queryParams = new URLSearchParams({
84
+ page_size: pageSize.toString(),
85
+ query,
86
+ })
87
+
88
+ return fetch(`https://rest.imdbapi.dev/v2/search/titles?${queryParams}`)
89
+ .then(fetchProgress({ onProgress }))
90
+ .then((response) => response.json())
91
+ .then(({ titles } => titles)
92
+ })
93
+
94
+ // ...
95
+
96
+ // 3. Start the event listener
97
+ swarpc.start(self)
98
+ ```
99
+
100
+ ### 3. Call your procedures from the client
101
+
102
+ Here's a Svelte example!
103
+
104
+ ```svelte
105
+ <script>
106
+ import { Client } from "swarpc"
107
+ import { procedures } from "./procedures.js"
108
+
109
+ const swarpc = Client(procedures)
110
+
111
+ let query = $state("")
112
+ let results = $state([])
113
+ let progress = $state(0)
114
+ </script>
115
+
116
+ <search>
117
+ <input type="text" bind:value={query} placeholder="Search IMDb" />
118
+ <button onclick={async () => {
119
+ results = await swarpc.searchIMDb({ query }, (p) => {
120
+ progress = p.transferred / p.total
121
+ })
122
+ }}>
123
+ Search
124
+ </button>
125
+ </search>
126
+
127
+ {#if progress > 0 && progress < 1}
128
+ <progress value={progress} max="1" />
129
+ {/if}
130
+
131
+ <ul>
132
+ {#each results as { id, primary_title, genres } (id)}
133
+ <li>{primary_title} - {genres.join(", ")}</li>
134
+ {/each}
135
+ </ul>
136
+ ```
137
+
138
+ ### Configure parallelism
139
+
140
+ By default, when a `worker` is passed to the `Client`'s options, the client will automatically spin up `navigator.hardwareConcurrency` worker instances and distribute requests among them. You can customize this behavior by setting the `Client:options.nodes` option to control the number of _nodes_ (worker instances).
141
+
142
+ When `Client:options.worker` is not set, the client will use the Service worker (and thus only a single instance).
143
+
144
+ #### Send to multiple nodes
145
+
146
+ Use `Client#(method name).broadcast` to send the same request to all nodes at once. This method returns a Promise that resolves to an array of `PromiseSettledResult` (with an additional property, `node`, the ID of the node the request was sent to), one per node the request was sent to.
147
+
148
+ For example:
149
+
150
+ ```ts
151
+ const client = Client(procedures, {
152
+ worker: "./worker.js",
153
+ nodes: 4,
154
+ });
155
+
156
+ for (const result of await client.initDB.broadcast("localhost:5432")) {
157
+ if (result.status === "rejected") {
158
+ console.error(
159
+ `Could not initialize database on node ${result.node}`,
160
+ result.reason,
161
+ );
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Make cancelable requests
167
+
168
+ #### Implementation
169
+
170
+ To make your procedures meaningfully cancelable, you have to make use of the [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) API. This is passed as a third argument when implementing your procedures:
171
+
172
+ ```js
173
+ server.searchIMDb(async ({ query }, onProgress, abort) => {
174
+ // If you're doing heavy computation without fetch:
175
+ let aborted = false
176
+ abort?.addEventListener("abort", () => {
177
+ aborted = true
178
+ })
179
+
180
+ // Use `aborted` to check if the request was canceled within your hot loop
181
+ for (...) {
182
+ /* here */ if (aborted) return
183
+ ...
184
+ }
185
+
186
+ // When using fetch:
187
+ await fetch(..., { signal: abort })
188
+ })
189
+ ```
190
+
191
+ #### Call sites
192
+
193
+ Instead of calling `await client.myProcedure()` directly, call `client.myProcedure.cancelable()`. You'll get back an object with
194
+
195
+ - `async cancel(reason)`: a function to cancel the request
196
+ - `request`: a Promise that resolves to the result of the procedure call. `await` it to wait for the request to finish.
197
+
198
+ Example:
199
+
200
+ ```js
201
+ // Normal call:
202
+ const result = await swarpc.searchIMDb({ query });
203
+
204
+ // Cancelable call:
205
+ const { request, cancel } = swarpc.searchIMDb.cancelable({ query });
206
+ setTimeout(() => cancel().then(() => console.warn("Took too long!!")), 5_000);
207
+ await request;
208
+ ```
209
+
210
+ ### Polyfill a `localStorage` for the Server to access
211
+
212
+ You might call third-party code that accesses on `localStorage` from within your procedures.
213
+
214
+ Some workers don't have access to the browser's `localStorage`, so you'll get an error.
215
+
216
+ You can work around this by specifying to swarpc localStorage items to define on the Server, and it'll create a polyfilled `localStorage` with your data.
217
+
218
+ An example use case is using Paraglide, a i18n library, with [the `localStorage` strategy](https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy#localstorage):
219
+
220
+ ```js
221
+ // In the client
222
+ import { getLocale } from "./paraglide/runtime.js";
223
+
224
+ const swarpc = Client(procedures, {
225
+ localStorage: {
226
+ PARAGLIDE_LOCALE: getLocale(),
227
+ },
228
+ });
229
+
230
+ await swarpc.myProcedure(1, 0);
231
+
232
+ // In the server
233
+ import { m } from "./paraglide/runtime.js";
234
+ const swarpc = Server(procedures);
235
+
236
+ swarpc.myProcedure(async (a, b) => {
237
+ if (b === 0) throw new Error(m.cannot_divide_by_zero());
238
+ return a / b;
239
+ });
240
+ ```
package/dist/client.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * @module
3
3
  * @mergeModuleWith <project>
4
4
  */
5
- import { type Logger, type LogLevel } from "./log.js";
6
- import { ClientMethod, Hooks, Payload, zProcedures, type ProceduresMap } from "./types.js";
5
+ import { RequestBoundLogger, type Logger, type LogLevel } from "./log.js";
6
+ import { ClientMethod, Hooks, Payload, WorkerConstructor, zProcedures, type ProceduresMap } from "./types.js";
7
7
  /**
8
8
  * The sw&rpc client instance, which provides {@link ClientMethod | methods to call procedures}.
9
9
  * Each property of the procedures map will be a method, that accepts an input, an optional onProgress callback and an optional request ID.
@@ -14,27 +14,55 @@ export type SwarpcClient<Procedures extends ProceduresMap> = {
14
14
  } & {
15
15
  [F in keyof Procedures]: ClientMethod<Procedures[F]>;
16
16
  };
17
+ /**
18
+ * Context for passing around data useful for requests
19
+ */
20
+ type Context<Procedures extends ProceduresMap> = {
21
+ /** A logger, bound to the client */
22
+ logger: Logger;
23
+ /** The node to use */
24
+ node: Worker | SharedWorker | undefined;
25
+ /** The ID of the node to use */
26
+ nodeId: string | undefined;
27
+ /** Hooks defined by the client */
28
+ hooks: Hooks<Procedures>;
29
+ /** Local storage data defined by the client for the faux local storage */
30
+ localStorage: Record<string, any>;
31
+ };
32
+ export type PendingRequest = {
33
+ /** ID of the node the request was sent to. udefined if running on a service worker */
34
+ nodeId?: string;
35
+ functionName: string;
36
+ reject: (err: Error) => void;
37
+ onProgress: (progress: any) => void;
38
+ resolve: (result: any) => void;
39
+ };
40
+ export type ClientOptions = Parameters<typeof Client>[1];
17
41
  /**
18
42
  *
19
43
  * @param procedures procedures the client will be able to call, see {@link ProceduresMap}
20
44
  * @param options various options
21
- * @param options.worker The instantiated worker object. If not provided, the client will use the service worker.
22
- * Example: `new Worker("./worker.js")`
45
+ * @param options.worker The worker class, **not instantiated**, or a path to the source code. If not provided, the client will use the service worker. If a string is provided, it'll instantiate a regular `Worker`, not a `SharedWorker`.
46
+ * Example: `"./worker.js"`
23
47
  * See {@link Worker} (used by both dedicated workers and service workers), {@link SharedWorker}, and
24
48
  * the different [worker types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API#worker_types) that exist
25
49
  * @param options.hooks Hooks to run on messages received from the server. See {@link Hooks}
26
50
  * @param options.loglevel Maximum log level to use, defaults to "debug" (shows everything). "info" will not show debug messages, "warn" will only show warnings and errors, "error" will only show errors.
27
51
  * @param options.restartListener If true, will force the listener to restart even if it has already been started. You should probably leave this to false, unless you are testing and want to reset the client state.
52
+ * @param options.localStorage Define a in-memory localStorage with the given key-value pairs. Allows code called on the server to access localStorage (even though SharedWorkers don't have access to the browser's real localStorage)
53
+ * @param options.nodes the number of workers to use for the server, defaults to {@link navigator.hardwareConcurrency}.
28
54
  * @returns a sw&rpc client instance. Each property of the procedures map will be a method, that accepts an input and an optional onProgress callback, see {@link ClientMethod}
29
55
  *
30
56
  * An example of defining and using a client:
31
57
  * {@includeCode ../example/src/routes/+page.svelte}
32
58
  */
33
- export declare function Client<Procedures extends ProceduresMap>(procedures: Procedures, { worker, loglevel, restartListener, hooks, }?: {
34
- worker?: Worker | SharedWorker;
59
+ export declare function Client<Procedures extends ProceduresMap>(procedures: Procedures, { worker, nodes: nodeCount, loglevel, restartListener, hooks, localStorage, }?: {
60
+ worker?: WorkerConstructor | string;
61
+ nodes?: number;
35
62
  hooks?: Hooks<Procedures>;
36
63
  loglevel?: LogLevel;
37
64
  restartListener?: boolean;
65
+ localStorage?: Record<string, any>;
38
66
  }): SwarpcClient<Procedures>;
39
67
  /**
40
68
  * A quicker version of postMessage that does not try to start the client listener, await the service worker, etc.
@@ -44,18 +72,18 @@ export declare function Client<Procedures extends ProceduresMap>(procedures: Pro
44
72
  * @param message
45
73
  * @param options
46
74
  */
47
- export declare function postMessageSync<Procedures extends ProceduresMap>(l: Logger, worker: Worker | SharedWorker | undefined, message: Payload<Procedures>, options?: StructuredSerializeOptions): void;
75
+ export declare function postMessageSync<Procedures extends ProceduresMap>(l: RequestBoundLogger, worker: Worker | SharedWorker | undefined, message: Payload<Procedures>, options?: StructuredSerializeOptions): void;
48
76
  /**
49
77
  * Starts the client listener, which listens for messages from the sw&rpc server.
50
- * @param worker if provided, the client will use this worker to listen for messages, instead of using the service worker
51
- * @param force if true, will force the listener to restart even if it has already been started
78
+ * @param ctx.worker if provided, the client will use this worker to listen for messages, instead of using the service worker
52
79
  * @returns
53
80
  */
54
- export declare function startClientListener<Procedures extends ProceduresMap>(l: Logger, worker?: Worker | SharedWorker, hooks?: Hooks<Procedures>): Promise<void>;
81
+ export declare function startClientListener<Procedures extends ProceduresMap>(ctx: Context<Procedures>): Promise<void>;
55
82
  /**
56
83
  * Generate a random request ID, used to identify requests between client and server.
57
84
  * @source
58
85
  * @returns a 6-character hexadecimal string
59
86
  */
60
87
  export declare function makeRequestId(): string;
88
+ export {};
61
89
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAgB,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AACnE,OAAO,EACL,YAAY,EACZ,KAAK,EACL,OAAO,EAEP,WAAW,EACX,KAAK,aAAa,EACnB,MAAM,YAAY,CAAA;AAGnB;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,CAAC,WAAW,CAAC,EAAE,UAAU,CAAA;CAC1B,GAAG;KACD,CAAC,IAAI,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACrD,CAAA;AAkBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EACE,MAAM,EACN,QAAkB,EAClB,eAAuB,EACvB,KAAU,GACX,GAAE;IACD,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;IAC9B,KAAK,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;CACrB,GACL,YAAY,CAAC,UAAU,CAAC,CAsG1B;AAiCD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,UAAU,SAAS,aAAa,EAC9D,CAAC,EAAE,MAAM,EACT,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,EACzC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,EAC5B,OAAO,CAAC,EAAE,0BAA0B,GACnC,IAAI,CAiBN;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,SAAS,aAAa,EACxE,CAAC,EAAE,MAAM,EACT,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,EAC9B,KAAK,GAAE,KAAK,CAAC,UAAU,CAAM,iBAmE9B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEL,kBAAkB,EAClB,KAAK,MAAM,EACX,KAAK,QAAQ,EACd,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,YAAY,EACZ,KAAK,EACL,OAAO,EAEP,iBAAiB,EACjB,WAAW,EACX,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAGpB;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;CAC3B,GAAG;KACD,CAAC,IAAI,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACrD,CAAC;AAEF;;GAEG;AACH,KAAK,OAAO,CAAC,UAAU,SAAS,aAAa,IAAI;IAC/C,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IACxC,gCAAgC;IAChC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,kCAAkC;IAClC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IACzB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACnC,CAAC;AAQF,MAAM,MAAM,cAAc,GAAG;IAC3B,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC7B,UAAU,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAChC,CAAC;AAKF,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EACE,MAAM,EACN,KAAK,EAAE,SAAS,EAChB,QAAkB,EAClB,eAAuB,EACvB,KAAU,EACV,YAAiB,GAClB,GAAE;IACD,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B,GACL,YAAY,CAAC,UAAU,CAAC,CAgK1B;AAiCD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,UAAU,SAAS,aAAa,EAC9D,CAAC,EAAE,kBAAkB,EACrB,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,EACzC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,EAC5B,OAAO,CAAC,EAAE,0BAA0B,GACnC,IAAI,CAiBN;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,SAAS,aAAa,EACxE,GAAG,EAAE,OAAO,CAAC,UAAU,CAAC,iBAoFzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}