swarpc 0.11.0 → 0.13.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,171 +1,246 @@
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
- ### Bleeding edge
19
-
20
- If you want to use the latest commit instead of a published version, you can, either by using the Git URL:
21
-
22
- ```bash
23
- npm add git+https://github.com/gwennlbh/swarpc.git
24
- ```
25
-
26
- 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):
27
-
28
- ```bash
29
- mkdir -p vendored
30
- git clone https://github.com/gwennlbh/swarpc.git vendored/swarpc
31
- npm add file:vendored/swarpc
32
- ```
33
-
34
- This works thanks to the fact that `dist/` is published on the repository (and kept up to date with a CI workflow).
35
-
36
- ## Usage
37
-
38
- ### 1. Declare your procedures in a shared file
39
-
40
- ```typescript
41
- import type { ProceduresMap } from "swarpc"
42
- import { type } from "arktype"
43
-
44
- export const procedures = {
45
- searchIMDb: {
46
- // Input for the procedure
47
- input: type({ query: "string", "pageSize?": "number" }),
48
- // 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.
49
- progress: type({ transferred: "number", total: "number" }),
50
- // Output of a successful procedure call
51
- success: type({
52
- id: "string",
53
- primary_title: "string",
54
- genres: "string[]",
55
- }).array(),
56
- },
57
- } as const satisfies ProceduresMap
58
- ```
59
-
60
- ### 2. Register your procedures in the service worker
61
-
62
- In your service worker file:
63
-
64
- ```javascript
65
- import fetchProgress from "fetch-progress"
66
- import { Server } from "swarpc"
67
- import { procedures } from "./procedures.js"
68
-
69
- // 1. Give yourself a server instance
70
- const swarpc = Server(procedures)
71
-
72
- // 2. Implement your procedures
73
- swarpc.searchIMDb(async ({ query, pageSize = 10 }, onProgress) => {
74
- const queryParams = new URLSearchParams({
75
- page_size: pageSize.toString(),
76
- query,
77
- })
78
-
79
- return fetch(`https://rest.imdbapi.dev/v2/search/titles?${queryParams}`)
80
- .then(fetchProgress({ onProgress }))
81
- .then((response) => response.json())
82
- .then(({ titles } => titles)
83
- })
84
-
85
- // ...
86
-
87
- // 3. Start the event listener
88
- swarpc.start(self)
89
- ```
90
-
91
- ### 3. Call your procedures from the client
92
-
93
- Here's a Svelte example!
94
-
95
- ```svelte
96
- <script>
97
- import { Client } from "swarpc"
98
- import { procedures } from "./procedures.js"
99
-
100
- const swarpc = Client(procedures)
101
-
102
- let query = $state("")
103
- let results = $state([])
104
- let progress = $state(0)
105
- </script>
106
-
107
- <search>
108
- <input type="text" bind:value={query} placeholder="Search IMDb" />
109
- <button onclick={async () => {
110
- results = await swarpc.searchIMDb({ query }, (p) => {
111
- progress = p.transferred / p.total
112
- })
113
- }}>
114
- Search
115
- </button>
116
- </search>
117
-
118
- {#if progress > 0 && progress < 1}
119
- <progress value={progress} max="1" />
120
- {/if}
121
-
122
- <ul>
123
- {#each results as { id, primary_title, genres } (id)}
124
- <li>{primary_title} - {genres.join(", ")}</li>
125
- {/each}
126
- </ul>
127
- ```
128
-
129
- ### Make cancelable requests
130
-
131
- #### Implementation
132
-
133
- 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:
134
-
135
- ```js
136
- server.searchIMDb(async ({ query }, onProgress, abort) => {
137
- // If you're doing heavy computation without fetch:
138
- let aborted = false
139
- abort?.addEventListener("abort", () => {
140
- aborted = true
141
- })
142
-
143
- // Use `aborted` to check if the request was canceled within your hot loop
144
- for (...) {
145
- /* here */ if (aborted) return
146
- ...
147
- }
148
-
149
- // When using fetch:
150
- await fetch(..., { signal: abort })
151
- })
152
- ```
153
-
154
- #### Call sites
155
-
156
- Instead of calling `await client.myProcedure()` directly, call `client.myProcedure.cancelable()`. You'll get back an object with
157
-
158
- - `async cancel(reason)`: a function to cancel the request
159
- - `request`: a Promise that resolves to the result of the procedure call. `await` it to wait for the request to finish.
160
-
161
- Example:
162
-
163
- ```js
164
- // Normal call:
165
- const result = await swarpc.searchIMDb({ query })
166
-
167
- // Cancelable call:
168
- const { request, cancel } = swarpc.searchIMDb.cancelable({ query })
169
- setTimeout(() => cancel().then(() => console.warn("Took too long!!")), 5_000)
170
- await request
171
- ```
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
+ <details>
28
+ <summary>
29
+ Bleeding edge
30
+
31
+ </summary>
32
+
33
+ If you want to use the latest commit instead of a published version, you can, either by using the Git URL:
34
+
35
+ ```bash
36
+ npm add git+https://github.com/gwennlbh/swarpc.git
37
+ ```
38
+
39
+ 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):
40
+
41
+ ```bash
42
+ mkdir -p vendored
43
+ git clone https://github.com/gwennlbh/swarpc.git vendored/swarpc
44
+ npm add file:vendored/swarpc
45
+ ```
46
+
47
+ This works thanks to the fact that `dist/` is published on the repository (and kept up to date with a CI workflow).
48
+
49
+ </details>
50
+
51
+ ## Usage
52
+
53
+ ### 1. Declare your procedures in a shared file
54
+
55
+ ```typescript
56
+ import type { ProceduresMap } from "swarpc";
57
+ import { type } from "arktype";
58
+
59
+ export const procedures = {
60
+ searchIMDb: {
61
+ // Input for the procedure
62
+ input: type({ query: "string", "pageSize?": "number" }),
63
+ // 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.
64
+ progress: type({ transferred: "number", total: "number" }),
65
+ // Output of a successful procedure call
66
+ success: type({
67
+ id: "string",
68
+ primary_title: "string",
69
+ genres: "string[]",
70
+ }).array(),
71
+ },
72
+ } as const satisfies ProceduresMap;
73
+ ```
74
+
75
+ ### 2. Register your procedures in the service worker
76
+
77
+ In your service worker file:
78
+
79
+ ```javascript
80
+ import fetchProgress from "fetch-progress"
81
+ import { Server } from "swarpc"
82
+ import { procedures } from "./procedures.js"
83
+
84
+ // 1. Give yourself a server instance
85
+ const swarpc = Server(procedures)
86
+
87
+ // 2. Implement your procedures
88
+ swarpc.searchIMDb(async ({ query, pageSize = 10 }, onProgress) => {
89
+ const queryParams = new URLSearchParams({
90
+ page_size: pageSize.toString(),
91
+ query,
92
+ })
93
+
94
+ return fetch(`https://rest.imdbapi.dev/v2/search/titles?${queryParams}`)
95
+ .then(fetchProgress({ onProgress }))
96
+ .then((response) => response.json())
97
+ .then(({ titles } => titles)
98
+ })
99
+
100
+ // ...
101
+
102
+ // 3. Start the event listener
103
+ swarpc.start(self)
104
+ ```
105
+
106
+ ### 3. Call your procedures from the client
107
+
108
+ Here's a Svelte example!
109
+
110
+ ```svelte
111
+ <script>
112
+ import { Client } from "swarpc"
113
+ import { procedures } from "./procedures.js"
114
+
115
+ const swarpc = Client(procedures)
116
+
117
+ let query = $state("")
118
+ let results = $state([])
119
+ let progress = $state(0)
120
+ </script>
121
+
122
+ <search>
123
+ <input type="text" bind:value={query} placeholder="Search IMDb" />
124
+ <button onclick={async () => {
125
+ results = await swarpc.searchIMDb({ query }, (p) => {
126
+ progress = p.transferred / p.total
127
+ })
128
+ }}>
129
+ Search
130
+ </button>
131
+ </search>
132
+
133
+ {#if progress > 0 && progress < 1}
134
+ <progress value={progress} max="1" />
135
+ {/if}
136
+
137
+ <ul>
138
+ {#each results as { id, primary_title, genres } (id)}
139
+ <li>{primary_title} - {genres.join(", ")}</li>
140
+ {/each}
141
+ </ul>
142
+ ```
143
+
144
+ ### Configure parallelism
145
+
146
+ 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).
147
+
148
+ When `Client:options.worker` is not set, the client will use the Service worker (and thus only a single instance).
149
+
150
+ #### Send to multiple nodes
151
+
152
+ 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.
153
+
154
+ For example:
155
+
156
+ ```ts
157
+ const client = Client(procedures, {
158
+ worker: "./worker.js",
159
+ nodes: 4,
160
+ });
161
+
162
+ for (const result of await client.initDB.broadcast("localhost:5432")) {
163
+ if (result.status === "rejected") {
164
+ console.error(
165
+ `Could not initialize database on node ${result.node}`,
166
+ result.reason,
167
+ );
168
+ }
169
+ }
170
+ ```
171
+
172
+ ### Make cancelable requests
173
+
174
+ #### Implementation
175
+
176
+ 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:
177
+
178
+ ```js
179
+ server.searchIMDb(async ({ query }, onProgress, abort) => {
180
+ // If you're doing heavy computation without fetch:
181
+ let aborted = false
182
+ abort?.addEventListener("abort", () => {
183
+ aborted = true
184
+ })
185
+
186
+ // Use `aborted` to check if the request was canceled within your hot loop
187
+ for (...) {
188
+ /* here */ if (aborted) return
189
+ ...
190
+ }
191
+
192
+ // When using fetch:
193
+ await fetch(..., { signal: abort })
194
+ })
195
+ ```
196
+
197
+ #### Call sites
198
+
199
+ Instead of calling `await client.myProcedure()` directly, call `client.myProcedure.cancelable()`. You'll get back an object with
200
+
201
+ - `async cancel(reason)`: a function to cancel the request
202
+ - `request`: a Promise that resolves to the result of the procedure call. `await` it to wait for the request to finish.
203
+
204
+ Example:
205
+
206
+ ```js
207
+ // Normal call:
208
+ const result = await swarpc.searchIMDb({ query });
209
+
210
+ // Cancelable call:
211
+ const { request, cancel } = swarpc.searchIMDb.cancelable({ query });
212
+ setTimeout(() => cancel().then(() => console.warn("Took too long!!")), 5_000);
213
+ await request;
214
+ ```
215
+
216
+ ### Polyfill a `localStorage` for the Server to access
217
+
218
+ You might call third-party code that accesses on `localStorage` from within your procedures.
219
+
220
+ Some workers don't have access to the browser's `localStorage`, so you'll get an error.
221
+
222
+ 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.
223
+
224
+ An example use case is using Paraglide, a i18n library, with [the `localStorage` strategy](https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy#localstorage):
225
+
226
+ ```js
227
+ // In the client
228
+ import { getLocale } from "./paraglide/runtime.js";
229
+
230
+ const swarpc = Client(procedures, {
231
+ localStorage: {
232
+ PARAGLIDE_LOCALE: getLocale(),
233
+ },
234
+ });
235
+
236
+ await swarpc.myProcedure(1, 0);
237
+
238
+ // In the server
239
+ import { m } from "./paraglide/runtime.js";
240
+ const swarpc = Server(procedures);
241
+
242
+ swarpc.myProcedure(async (a, b) => {
243
+ if (b === 0) throw new Error(m.cannot_divide_by_zero());
244
+ return a / b;
245
+ });
246
+ ```
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.
@@ -20,32 +20,45 @@ export type SwarpcClient<Procedures extends ProceduresMap> = {
20
20
  type Context<Procedures extends ProceduresMap> = {
21
21
  /** A logger, bound to the client */
22
22
  logger: Logger;
23
- /** The worker instance to use */
24
- worker: Worker | SharedWorker | undefined;
23
+ /** The node to use */
24
+ node: Worker | SharedWorker | undefined;
25
+ /** The ID of the node to use */
26
+ nodeId: string | undefined;
25
27
  /** Hooks defined by the client */
26
28
  hooks: Hooks<Procedures>;
27
29
  /** Local storage data defined by the client for the faux local storage */
28
30
  localStorage: Record<string, any>;
29
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];
30
41
  /**
31
42
  *
32
43
  * @param procedures procedures the client will be able to call, see {@link ProceduresMap}
33
44
  * @param options various options
34
- * @param options.worker The instantiated worker object. If not provided, the client will use the service worker.
35
- * 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"`
36
47
  * See {@link Worker} (used by both dedicated workers and service workers), {@link SharedWorker}, and
37
48
  * the different [worker types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API#worker_types) that exist
38
49
  * @param options.hooks Hooks to run on messages received from the server. See {@link Hooks}
39
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.
40
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.
41
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}.
42
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}
43
55
  *
44
56
  * An example of defining and using a client:
45
57
  * {@includeCode ../example/src/routes/+page.svelte}
46
58
  */
47
- export declare function Client<Procedures extends ProceduresMap>(procedures: Procedures, { worker, loglevel, restartListener, hooks, localStorage, }?: {
48
- 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;
49
62
  hooks?: Hooks<Procedures>;
50
63
  loglevel?: LogLevel;
51
64
  restartListener?: boolean;
@@ -59,7 +72,7 @@ export declare function Client<Procedures extends ProceduresMap>(procedures: Pro
59
72
  * @param message
60
73
  * @param options
61
74
  */
62
- 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;
63
76
  /**
64
77
  * Starts the client listener, which listens for messages from the sw&rpc server.
65
78
  * @param ctx.worker if provided, the client will use this worker to listen for messages, instead of using the service worker
@@ -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;AAED;;GAEG;AACH,KAAK,OAAO,CAAC,UAAU,SAAS,aAAa,IAAI;IAC/C,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAA;IACzC,kCAAkC;IAClC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;IACxB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAClC,CAAA;AAkBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EACE,MAAM,EACN,QAAkB,EAClB,eAAuB,EACvB,KAAU,EACV,YAAiB,GAClB,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;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC9B,GACL,YAAY,CAAC,UAAU,CAAC,CA2G1B;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;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,SAAS,aAAa,EACxE,GAAG,EAAE,OAAO,CAAC,UAAU,CAAC,iBAoFzB;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,iBAsFzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}