@kirill.konshin/worker 0.0.2
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/.ctirc +20 -0
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-test.log +97 -0
- package/CHANGELOG.md +7 -0
- package/README.md +91 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/worker.d.ts +98 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +226 -0
- package/dist/worker.js.map +1 -0
- package/dist/worker.test.d.ts +2 -0
- package/dist/worker.test.d.ts.map +1 -0
- package/dist/workerUtils.d.ts +3 -0
- package/dist/workerUtils.d.ts.map +1 -0
- package/dist/workerUtils.js +31 -0
- package/dist/workerUtils.js.map +1 -0
- package/dist/wrpc.d.ts +45 -0
- package/dist/wrpc.d.ts.map +1 -0
- package/dist/wrpc.fixture.d.ts +26 -0
- package/dist/wrpc.fixture.d.ts.map +1 -0
- package/dist/wrpc.js +241 -0
- package/dist/wrpc.js.map +1 -0
- package/dist/wrpc.test.d.ts +1 -0
- package/dist/wrpc.test.d.ts.map +1 -0
- package/package.json +41 -0
- package/src/index.ts +3 -0
- package/src/worker.test.ts +197 -0
- package/src/worker.ts +347 -0
- package/src/workerUtils.ts +32 -0
- package/src/wrpc.fixture.ts +106 -0
- package/src/wrpc.test.ts +225 -0
- package/src/wrpc.ts +377 -0
- package/tsconfig.json +10 -0
- package/turbo.json +10 -0
- package/vite.config.ts +2 -0
package/.ctirc
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"options": [
|
|
3
|
+
{
|
|
4
|
+
"mode": "create",
|
|
5
|
+
"project": "tsconfig.json",
|
|
6
|
+
"include": "src/**/*.{ts,tsx}",
|
|
7
|
+
"exclude": [
|
|
8
|
+
"**/*.stories.*",
|
|
9
|
+
"**/*.test.*",
|
|
10
|
+
"**/*.fixture.*"
|
|
11
|
+
],
|
|
12
|
+
"startFrom": "src",
|
|
13
|
+
"backup": false,
|
|
14
|
+
"overwrite": true,
|
|
15
|
+
"generationStyle": "default-alias-named-star",
|
|
16
|
+
"output": "src",
|
|
17
|
+
"verbose": true
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[36mvite v7.0.6 [32mbuilding SSR bundle for production...[36m[39m
|
|
2
|
+
- ctix 'create' mode start, ...
|
|
3
|
+
✔ /home/runner/work/utils/utils/packages/worker/tsconfig.json loading complete!
|
|
4
|
+
✔ analysis export statements completed!
|
|
5
|
+
- build "index.ts" file start
|
|
6
|
+
- output file exists check, ...
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
✔ ctix 'create' mode complete!
|
|
10
|
+
transforming...
|
|
11
|
+
[32m✓[39m 4 modules transformed.
|
|
12
|
+
rendering chunks...
|
|
13
|
+
|
|
14
|
+
[vite:dts] Start generate declaration files...
|
|
15
|
+
[2mdist/[22m[36mindex.js [39m[1m[2m0.38 kB[22m[1m[22m[2m │ map: 0.09 kB[22m
|
|
16
|
+
[2mdist/[22m[36mworkerUtils.js [39m[1m[2m0.97 kB[22m[1m[22m[2m │ map: 1.83 kB[22m
|
|
17
|
+
[2mdist/[22m[36mworker.js [39m[1m[2m7.06 kB[22m[1m[22m[2m │ map: 16.69 kB[22m
|
|
18
|
+
[2mdist/[22m[36mwrpc.js [39m[1m[2m8.73 kB[22m[1m[22m[2m │ map: 20.98 kB[22m
|
|
19
|
+
[vite:dts] Declaration files built in 1924ms.
|
|
20
|
+
|
|
21
|
+
[32m✓ built in 5.20s[39m
|
|
22
|
+
Updated package.json with exports
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
- ctix 'create' mode start, ...
|
|
2
|
+
✔ /home/runner/work/utils/utils/packages/worker/tsconfig.json loading complete!
|
|
3
|
+
✔ analysis export statements completed!
|
|
4
|
+
- build "index.ts" file start
|
|
5
|
+
- output file exists check, ...
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
✔ ctix 'create' mode complete!
|
|
9
|
+
|
|
10
|
+
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/home/runner/work/utils/utils/packages/worker[39m
|
|
11
|
+
[2mCoverage enabled with [22m[33mv8[39m
|
|
12
|
+
|
|
13
|
+
[90mstderr[2m | src/wrpc.test.ts
|
|
14
|
+
[22m[39mUsing an object as a third argument is deprecated. Vitest 4 will throw an error if the third argument is not a timeout number. Please use the second argument for options. See more at https://vitest.dev/guide/migration
|
|
15
|
+
|
|
16
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of return yield
|
|
17
|
+
[22m[39mDATA [33m1[39m
|
|
18
|
+
|
|
19
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of return yield
|
|
20
|
+
[22m[39mDATA [33m2[39m
|
|
21
|
+
|
|
22
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of return yield
|
|
23
|
+
[22m[39mDATA [33m3[39m
|
|
24
|
+
|
|
25
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of return yield
|
|
26
|
+
[22m[39mDATA { value: [33m1[39m, done: [33mfalse[39m }
|
|
27
|
+
|
|
28
|
+
[32m✓[39m src/worker.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 14[2mms[22m[39m
|
|
29
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of return yield
|
|
30
|
+
[22m[39mDATA { value: [33m2[39m, done: [33mfalse[39m }
|
|
31
|
+
|
|
32
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of return yield
|
|
33
|
+
[22m[39mDATA { value: [33m3[39m, done: [33mfalse[39m }
|
|
34
|
+
|
|
35
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of return yield
|
|
36
|
+
[22m[39mDATA { value: [90mundefined[39m, done: [33mtrue[39m }
|
|
37
|
+
|
|
38
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of separate return & yield
|
|
39
|
+
[22m[39mDATA { progress: [33m0[39m }
|
|
40
|
+
|
|
41
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of separate return & yield
|
|
42
|
+
[22m[39mDATA { progress: [33m0.5[39m }
|
|
43
|
+
|
|
44
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mfor await ... of separate return & yield
|
|
45
|
+
[22m[39mDATA { progress: [33m1[39m, aborted: [33mfalse[39m }
|
|
46
|
+
|
|
47
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mdo ... while
|
|
48
|
+
[22m[39mDATA { value: { progress: [33m0[39m }, done: [33mfalse[39m }
|
|
49
|
+
|
|
50
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mdo ... while
|
|
51
|
+
[22m[39mDATA { value: { progress: [33m0.5[39m }, done: [33mfalse[39m }
|
|
52
|
+
|
|
53
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mdo ... while
|
|
54
|
+
[22m[39mDATA { value: { progress: [33m1[39m, aborted: [33mfalse[39m }, done: [33mfalse[39m }
|
|
55
|
+
|
|
56
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mdo ... while
|
|
57
|
+
[22m[39mDATA { value: [32m'foo'[39m, done: [33mtrue[39m }
|
|
58
|
+
|
|
59
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mbreak
|
|
60
|
+
[22m[39mDATA { progress: [33m0[39m }
|
|
61
|
+
|
|
62
|
+
[90mstdout[2m | src/wrpc.test.ts[2m > [22m[2mWRPC[2m > [22m[2mabort
|
|
63
|
+
[22m[39mDATA { progress: [33m0[39m }
|
|
64
|
+
|
|
65
|
+
[32m✓[39m src/wrpc.test.ts [2m([22m[2m13 tests[22m[2m | [22m[33m1 skipped[39m[2m)[22m[33m 705[2mms[22m[39m
|
|
66
|
+
[33m[2m✓[22m[39m WRPC[2m > [22mstop [33m 503[2mms[22m[39m
|
|
67
|
+
Failed to convert coverage for file:///home/runner/work/utils/utils/packages/worker/src/wrpc.ts.
|
|
68
|
+
TypeError: Cannot read properties of undefined (reading 'endCol')
|
|
69
|
+
at range.sliceRange (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:556:55)
|
|
70
|
+
at CovSource.offsetToOriginalRelative (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:1987:20)
|
|
71
|
+
at V8ToIstanbul._maybeRemapStartColEndCol (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2396:93)
|
|
72
|
+
at file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2305:61
|
|
73
|
+
at Array.forEach (<anonymous>)
|
|
74
|
+
at file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2303:21
|
|
75
|
+
at Array.forEach (<anonymous>)
|
|
76
|
+
at V8ToIstanbul.applyCoverage (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2302:13)
|
|
77
|
+
at V8CoverageProvider.v8ToIstanbul (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2732:14)
|
|
78
|
+
at async file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2825:23
|
|
79
|
+
|
|
80
|
+
[2m Test Files [22m [1m[32m2 passed[39m[22m[90m (2)[39m
|
|
81
|
+
[2m Tests [22m [1m[32m20 passed[39m[22m[2m | [22m[33m1 skipped[39m[90m (21)[39m
|
|
82
|
+
[2m Start at [22m 03:26:56
|
|
83
|
+
[2m Duration [22m 1.27s[2m (transform 170ms, setup 0ms, collect 229ms, tests 720ms, environment 1ms, prepare 228ms)[22m
|
|
84
|
+
|
|
85
|
+
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
86
|
+
-----------------|---------|----------|---------|---------|-------------------
|
|
87
|
+
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
88
|
+
-----------------|---------|----------|---------|---------|-------------------
|
|
89
|
+
All files | 97.49 | 91.74 | 92.68 | 97.49 |
|
|
90
|
+
index.ts | 0 | 0 | 0 | 0 | 1-3
|
|
91
|
+
worker.ts | 100 | 98.71 | 100 | 100 | 57
|
|
92
|
+
workerUtils.ts | 100 | 75 | 100 | 100 | 17,22,26,29
|
|
93
|
+
wrpc.fixture.ts | 81.81 | 78.57 | 75 | 81.81 | 70,73-76,95-99
|
|
94
|
+
wrpc.ts | 100 | 100 | 100 | 100 |
|
|
95
|
+
-----------------|---------|----------|---------|---------|-------------------
|
|
96
|
+
Updated package.json with exports
|
|
97
|
+
Updated package.json with exports
|
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# WRPC
|
|
2
|
+
|
|
3
|
+
TODO
|
|
4
|
+
|
|
5
|
+
- [ ] Add to readme https://stackoverflow.com/questions/77727664/how-to-get-returned-value-from-async-generator-when-using-for-await
|
|
6
|
+
- [ ] Value passed to next are ignored https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/next#sending_values_to_the_generator
|
|
7
|
+
|
|
8
|
+
```mermaid
|
|
9
|
+
sequenceDiagram
|
|
10
|
+
box Main
|
|
11
|
+
participant Code
|
|
12
|
+
participant Caller
|
|
13
|
+
end
|
|
14
|
+
box Worker
|
|
15
|
+
participant Worker
|
|
16
|
+
participant Resolver
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
%% autonumber
|
|
20
|
+
|
|
21
|
+
Code->>Caller: caller.generator(payload)
|
|
22
|
+
|
|
23
|
+
Caller-->>Code: iterator & promise
|
|
24
|
+
|
|
25
|
+
Code->>Code: Start main CONSUMER loop
|
|
26
|
+
|
|
27
|
+
Code->>Caller: iterator[Symbol]()
|
|
28
|
+
|
|
29
|
+
Caller->>Caller: Start replica GENERATOR loop
|
|
30
|
+
|
|
31
|
+
Code->>Caller: iterator.next()
|
|
32
|
+
|
|
33
|
+
Caller->>Worker: postMessage(payload)
|
|
34
|
+
|
|
35
|
+
Worker->>Resolver: resolver(payload)
|
|
36
|
+
|
|
37
|
+
Resolver-->>Worker: iterator
|
|
38
|
+
|
|
39
|
+
Worker->>Worker: Start replica CONSUMER loop
|
|
40
|
+
|
|
41
|
+
Worker->>Resolver: iterator[Symbol]()
|
|
42
|
+
|
|
43
|
+
Resolver->>Resolver: Start GENERATOR loop
|
|
44
|
+
|
|
45
|
+
rect rgb(232, 244, 248)
|
|
46
|
+
|
|
47
|
+
loop Synchronized iteration
|
|
48
|
+
Worker->>+Resolver: iterator.next(payload)
|
|
49
|
+
|
|
50
|
+
Resolver-->>-Worker: yield value
|
|
51
|
+
|
|
52
|
+
Worker-->>Caller: postMessage(value, done=false)
|
|
53
|
+
|
|
54
|
+
Note over Worker: Wait for ACK or ABORT
|
|
55
|
+
|
|
56
|
+
Caller-->>Code: yield value
|
|
57
|
+
|
|
58
|
+
Code->>Caller: iterator.next(nextPayload)
|
|
59
|
+
|
|
60
|
+
Caller->>Worker: postMessage(ack, nextPayload)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
break Break or AbortSignal (maybe)
|
|
66
|
+
Code-->>Code: Loop exits
|
|
67
|
+
Code->>Caller: signal.abort()
|
|
68
|
+
|
|
69
|
+
Caller-->>Caller: Loop exits
|
|
70
|
+
|
|
71
|
+
Caller->>Worker: postMessage(abort)
|
|
72
|
+
|
|
73
|
+
Worker-->>Worker: Loop exits via Break
|
|
74
|
+
|
|
75
|
+
Resolver-->>Resolver: Loop exits
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
opt Occurs on Error
|
|
79
|
+
Resolver-->>Worker: throw Error
|
|
80
|
+
|
|
81
|
+
Worker-->>Caller: postMessage(error, done=true)
|
|
82
|
+
|
|
83
|
+
Caller-->>Code: throw Error
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
Resolver-->>Worker: return value
|
|
87
|
+
|
|
88
|
+
Worker-->>Caller: postMessage(value, done=true)
|
|
89
|
+
|
|
90
|
+
Caller-->>Code: return value
|
|
91
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,QAAQ,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RespondersBase, WorkerDialog } from "./worker.js";
|
|
2
|
+
import { getTransferrable, isTransferable } from "./workerUtils.js";
|
|
3
|
+
import { LogLevel, deriveController, rejectOnSignal, wrpc } from "./wrpc.js";
|
|
4
|
+
export {
|
|
5
|
+
LogLevel,
|
|
6
|
+
RespondersBase,
|
|
7
|
+
WorkerDialog,
|
|
8
|
+
deriveController,
|
|
9
|
+
getTransferrable,
|
|
10
|
+
isTransferable,
|
|
11
|
+
rejectOnSignal,
|
|
12
|
+
wrpc
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
type WebWorker = Worker | (Window & typeof globalThis) | (WorkerGlobalScope & typeof globalThis);
|
|
2
|
+
type MethodReturn<R, M extends keyof R> = R[M] extends (...args: any[]) => any ? Partial<Awaited<ReturnType<R[M]>>> : never;
|
|
3
|
+
type MethodArg<R, M extends keyof R> = R[M] extends (arg: infer A, ...args: any[]) => any ? Partial<A> : never;
|
|
4
|
+
export type Ctx<R, K extends keyof R> = {
|
|
5
|
+
id: string;
|
|
6
|
+
parentId?: string;
|
|
7
|
+
message: K;
|
|
8
|
+
done: boolean;
|
|
9
|
+
};
|
|
10
|
+
type Data<R, M extends keyof R> = MethodReturn<R, M> | MethodArg<R, M> | Error;
|
|
11
|
+
type Event<R, M extends keyof R> = MessageEvent<{
|
|
12
|
+
data: Data<R, M>;
|
|
13
|
+
ctx: Ctx<R, M>;
|
|
14
|
+
}>;
|
|
15
|
+
type Listener<R, M extends keyof R, D extends Data<R, M> = Data<R, M>, S extends Context<R, M> = Context<R, M>> = (data: D, self: S, event: Event<R, M>) => void | Promise<void>;
|
|
16
|
+
export declare class WorkerDialog<R extends RespondersBase<any>> {
|
|
17
|
+
worker: WebWorker;
|
|
18
|
+
responders: R;
|
|
19
|
+
name: string;
|
|
20
|
+
debug: number;
|
|
21
|
+
readonly contexts: Set<Context<R, keyof R>>;
|
|
22
|
+
protected closed: boolean;
|
|
23
|
+
protected currentId: number;
|
|
24
|
+
constructor(worker: WebWorker, responders: R, name: string, debug?: number);
|
|
25
|
+
getID(): string;
|
|
26
|
+
close(): void;
|
|
27
|
+
withMessage<M extends keyof R>(message: M): RequestContext<R, M>;
|
|
28
|
+
}
|
|
29
|
+
export declare class RespondersBase<R> {
|
|
30
|
+
create<M extends keyof R, F extends (input: any, context: ResponseContext<R, M>) => any>(message: M, responder: F): F;
|
|
31
|
+
}
|
|
32
|
+
declare abstract class Context<R, M extends keyof R> {
|
|
33
|
+
protected readonly dialog: WorkerDialog<any>;
|
|
34
|
+
message: M;
|
|
35
|
+
id: string | undefined;
|
|
36
|
+
parent: Context<R, keyof R> | undefined | null;
|
|
37
|
+
protected readonly unsub: Set<() => void>;
|
|
38
|
+
protected closed: boolean;
|
|
39
|
+
constructor(dialog: WorkerDialog<any>, message: M, id: string | undefined, parent?: Context<R, keyof R> | undefined | null);
|
|
40
|
+
abstract withMessage<M2 extends keyof R>(message: M2, id?: string): Context<R, M2>;
|
|
41
|
+
close(reason?: string): void;
|
|
42
|
+
get info(): string;
|
|
43
|
+
get debugInfo(): {
|
|
44
|
+
id?: string;
|
|
45
|
+
parentId?: string;
|
|
46
|
+
message: string | number | symbol;
|
|
47
|
+
closed: boolean;
|
|
48
|
+
type: string;
|
|
49
|
+
listeners: number;
|
|
50
|
+
};
|
|
51
|
+
abstract type: 'request' | 'response' | string;
|
|
52
|
+
protected postMessage(data?: Data<R, M>, done?: boolean): Data<R, M> | undefined;
|
|
53
|
+
send(data?: Data<R, M>, done?: boolean): Data<R, M> | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Does not automatically close, assumes consumer will close
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* dialog.request('x').fetch((c) => {
|
|
59
|
+
* c.response('x').listen(() => { ... }); // listens to "x", will be closed by upper level fetch
|
|
60
|
+
* c.request('x').fetcH(y, (c) => { ... }); // closes
|
|
61
|
+
* }) // closes
|
|
62
|
+
*
|
|
63
|
+
* const un = dialog.context('x').listen((e) => { ... }); // listens to 'x'
|
|
64
|
+
* un(); // closes
|
|
65
|
+
*
|
|
66
|
+
* const un = dialog.context('x').listen((e) => { ... }); // listens to 'x'
|
|
67
|
+
* un(); // closes
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
listen(callback: Listener<R, M>, done?: boolean): () => void;
|
|
71
|
+
}
|
|
72
|
+
declare class RequestContext<R, M extends keyof R> extends Context<R, M> {
|
|
73
|
+
type: string;
|
|
74
|
+
withMessage<M2 extends keyof R>(message: M2, id?: string): RequestContext<R, M2>;
|
|
75
|
+
send(data?: MethodArg<R, M>, done?: boolean): MethodArg<R, M>;
|
|
76
|
+
listen(callback: Listener<R, M, MethodReturn<R, M>, RequestContext<R, M>>, done?: boolean): () => void;
|
|
77
|
+
/**
|
|
78
|
+
* Returns response
|
|
79
|
+
* If you need to work with ctx, use callback
|
|
80
|
+
* Closed automatically on response
|
|
81
|
+
*
|
|
82
|
+
* dialog.fetch('x', (c) => {
|
|
83
|
+
* c.listen(() => { ... }); // will be closed by upper level fetch
|
|
84
|
+
* c.fetcH(y, (c) => { ... }); // closes c
|
|
85
|
+
* c.context().fetcH(y, (c) => { ... }); // closes sub, not closes c, will be closed by upper level fetch
|
|
86
|
+
* })
|
|
87
|
+
*/
|
|
88
|
+
fetch(data?: MethodArg<R, M>, callback?: (context: RequestContext<R, M>) => void): Promise<MethodReturn<R, M>>;
|
|
89
|
+
protected expect(): Promise<MethodReturn<R, M>>;
|
|
90
|
+
}
|
|
91
|
+
declare class ResponseContext<R, M extends keyof R> extends Context<R, M> {
|
|
92
|
+
type: string;
|
|
93
|
+
withMessage<M2 extends keyof R>(message: M2, id?: string): ResponseContext<R, M2>;
|
|
94
|
+
listen(callback: Listener<R, M, MethodArg<R, M>, ResponseContext<R, M>>, done?: boolean): () => void;
|
|
95
|
+
send(data?: MethodReturn<R, M>, done?: boolean): MethodReturn<R, M>;
|
|
96
|
+
}
|
|
97
|
+
export {};
|
|
98
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { getTransferrable } from "./workerUtils.js";
|
|
2
|
+
const ALL = "*";
|
|
3
|
+
const RESPONDER = Symbol("responder");
|
|
4
|
+
function checkClosed(obj, label = "external") {
|
|
5
|
+
if (obj?.closed) throw new Error(`Context ${obj.id} is closed (${label})`, { cause: obj });
|
|
6
|
+
}
|
|
7
|
+
function checkMessage(obj, message) {
|
|
8
|
+
if (!message) throw new Error("New context must have message", { cause: obj });
|
|
9
|
+
}
|
|
10
|
+
class WorkerDialog {
|
|
11
|
+
// IDs are intentionally not global
|
|
12
|
+
constructor(worker, responders, name, debug = 0) {
|
|
13
|
+
this.worker = worker;
|
|
14
|
+
this.responders = responders;
|
|
15
|
+
this.name = name;
|
|
16
|
+
this.debug = debug;
|
|
17
|
+
if (!this.worker) return;
|
|
18
|
+
for (const message in this.responders) {
|
|
19
|
+
if (!this.responders?.[message]?.[RESPONDER]) continue;
|
|
20
|
+
const rootContext = new ResponseContext(this, message, ALL);
|
|
21
|
+
rootContext.listen(async (data, context, event) => {
|
|
22
|
+
const responseContext = context.withMessage(message, event.data.ctx.id);
|
|
23
|
+
if (this.debug)
|
|
24
|
+
console.log("Root Listener", rootContext.info, {
|
|
25
|
+
rootContext: context,
|
|
26
|
+
data,
|
|
27
|
+
responseContext,
|
|
28
|
+
event,
|
|
29
|
+
responder: this.responders[message]
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
responseContext.send(await this.responders[message](data, responseContext), true);
|
|
33
|
+
responseContext.close("root.listener");
|
|
34
|
+
} catch (e) {
|
|
35
|
+
if (this.debug) console.error(`Error in responder ${responseContext.info}`, { e });
|
|
36
|
+
responseContext.send(e, true);
|
|
37
|
+
}
|
|
38
|
+
}, true);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
contexts = /* @__PURE__ */ new Set();
|
|
42
|
+
closed = false;
|
|
43
|
+
currentId = 0;
|
|
44
|
+
getID() {
|
|
45
|
+
return (++this.currentId).toString();
|
|
46
|
+
}
|
|
47
|
+
close() {
|
|
48
|
+
for (const context of this.contexts) context.close("root");
|
|
49
|
+
this.contexts.clear();
|
|
50
|
+
}
|
|
51
|
+
withMessage(message) {
|
|
52
|
+
return new RequestContext(this, message, this.getID());
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
class RespondersBase {
|
|
56
|
+
create(message, responder) {
|
|
57
|
+
responder[RESPONDER] = true;
|
|
58
|
+
return responder;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
class Context {
|
|
62
|
+
constructor(dialog, message, id, parent = null) {
|
|
63
|
+
this.dialog = dialog;
|
|
64
|
+
this.message = message;
|
|
65
|
+
this.id = id;
|
|
66
|
+
this.parent = parent;
|
|
67
|
+
checkClosed(dialog, "newContext");
|
|
68
|
+
checkClosed(parent, "newContext");
|
|
69
|
+
checkMessage(this, message);
|
|
70
|
+
this.id = id ?? parent?.id;
|
|
71
|
+
if (!this.id) throw new Error("ID is required", { cause: this });
|
|
72
|
+
this.dialog.contexts.add(this);
|
|
73
|
+
}
|
|
74
|
+
unsub = /* @__PURE__ */ new Set();
|
|
75
|
+
closed = false;
|
|
76
|
+
close(reason = "external") {
|
|
77
|
+
if (this.dialog.debug >= 2) console.log("Closing", this.info, reason, this);
|
|
78
|
+
this.dialog.contexts.delete(this);
|
|
79
|
+
for (const unsub of this.unsub) unsub();
|
|
80
|
+
this.unsub.clear();
|
|
81
|
+
this.dialog.contexts.forEach((context) => {
|
|
82
|
+
if (context.parent?.id === this.id) context.close("parent");
|
|
83
|
+
});
|
|
84
|
+
this.closed = true;
|
|
85
|
+
}
|
|
86
|
+
get info() {
|
|
87
|
+
return `${this.dialog.name}:${this.parent?.id ?? ALL}/${this.id}:${this.message.toString()}[${this.closed ? "closed" : "open"},${this.type}]`;
|
|
88
|
+
}
|
|
89
|
+
get debugInfo() {
|
|
90
|
+
return {
|
|
91
|
+
id: this.id,
|
|
92
|
+
parentId: this.parent?.id,
|
|
93
|
+
message: this.message,
|
|
94
|
+
closed: this.closed,
|
|
95
|
+
type: this.type,
|
|
96
|
+
listeners: this.unsub.size
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
postMessage(data, done = false) {
|
|
100
|
+
checkClosed(this, "postMessage");
|
|
101
|
+
const transfer = getTransferrable(data);
|
|
102
|
+
const ctx = {
|
|
103
|
+
done,
|
|
104
|
+
id: this.id,
|
|
105
|
+
parentId: this.parent?.id,
|
|
106
|
+
message: this.message
|
|
107
|
+
};
|
|
108
|
+
if (this.dialog.debug >= 3) console.log("postMessage", this.info, { ctx, data, transfer }, this);
|
|
109
|
+
this.dialog.worker.postMessage({ data, ctx }, transfer);
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
send(data, done = false) {
|
|
113
|
+
return this.postMessage(data, done);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Does not automatically close, assumes consumer will close
|
|
117
|
+
*
|
|
118
|
+
* ```ts
|
|
119
|
+
* dialog.request('x').fetch((c) => {
|
|
120
|
+
* c.response('x').listen(() => { ... }); // listens to "x", will be closed by upper level fetch
|
|
121
|
+
* c.request('x').fetcH(y, (c) => { ... }); // closes
|
|
122
|
+
* }) // closes
|
|
123
|
+
*
|
|
124
|
+
* const un = dialog.context('x').listen((e) => { ... }); // listens to 'x'
|
|
125
|
+
* un(); // closes
|
|
126
|
+
*
|
|
127
|
+
* const un = dialog.context('x').listen((e) => { ... }); // listens to 'x'
|
|
128
|
+
* un(); // closes
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
listen(callback, done = false) {
|
|
132
|
+
checkClosed(this, "listen");
|
|
133
|
+
const controller = new AbortController();
|
|
134
|
+
this.dialog.worker.addEventListener(
|
|
135
|
+
"message",
|
|
136
|
+
async (event) => {
|
|
137
|
+
try {
|
|
138
|
+
const {
|
|
139
|
+
data: { ctx, data }
|
|
140
|
+
} = event;
|
|
141
|
+
if (!ctx || this.id !== ALL && this.id !== ctx.id || this.message !== ctx.message || done && !ctx.done)
|
|
142
|
+
return;
|
|
143
|
+
if (this.dialog.debug) console.log("Listener event", this.info, event, this);
|
|
144
|
+
await callback(data, this, event);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
if (this.dialog.debug) console.error(`Error in listener ${this.info}`, this, { event, e });
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{ signal: controller.signal }
|
|
150
|
+
);
|
|
151
|
+
const unsub = () => {
|
|
152
|
+
if (this.dialog.debug >= 2) console.log("Stop listening", this.info, this);
|
|
153
|
+
controller.abort();
|
|
154
|
+
this.unsub.delete(unsub);
|
|
155
|
+
};
|
|
156
|
+
this.unsub.add(unsub);
|
|
157
|
+
return unsub;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
class RequestContext extends Context {
|
|
161
|
+
type = "request";
|
|
162
|
+
withMessage(message, id) {
|
|
163
|
+
return new RequestContext(this.dialog, message, id, this);
|
|
164
|
+
}
|
|
165
|
+
send(data, done = false) {
|
|
166
|
+
return super.send(data, done);
|
|
167
|
+
}
|
|
168
|
+
listen(callback, done = false) {
|
|
169
|
+
return super.listen(callback, done);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Returns response
|
|
173
|
+
* If you need to work with ctx, use callback
|
|
174
|
+
* Closed automatically on response
|
|
175
|
+
*
|
|
176
|
+
* dialog.fetch('x', (c) => {
|
|
177
|
+
* c.listen(() => { ... }); // will be closed by upper level fetch
|
|
178
|
+
* c.fetcH(y, (c) => { ... }); // closes c
|
|
179
|
+
* c.context().fetcH(y, (c) => { ... }); // closes sub, not closes c, will be closed by upper level fetch
|
|
180
|
+
* })
|
|
181
|
+
*/
|
|
182
|
+
async fetch(data, callback) {
|
|
183
|
+
checkClosed(this, "fetch");
|
|
184
|
+
const res = await Promise.all([
|
|
185
|
+
callback?.(this),
|
|
186
|
+
this.expect(),
|
|
187
|
+
// will be closed in expect -> listener -> unsub
|
|
188
|
+
this.send(data, true)
|
|
189
|
+
]);
|
|
190
|
+
if (this.dialog.debug >= 2) console.log("Fetch", this.info, this);
|
|
191
|
+
this.close("fetch");
|
|
192
|
+
return res[1];
|
|
193
|
+
}
|
|
194
|
+
async expect() {
|
|
195
|
+
checkClosed(this, "expect");
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
const responseListener = this.listen(async (data) => {
|
|
198
|
+
try {
|
|
199
|
+
if (data instanceof Error) throw data;
|
|
200
|
+
responseListener();
|
|
201
|
+
if (this.dialog.debug >= 3) console.log("Expect", this.info, data, this);
|
|
202
|
+
resolve(data);
|
|
203
|
+
} catch (e) {
|
|
204
|
+
reject(e);
|
|
205
|
+
}
|
|
206
|
+
}, true);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
class ResponseContext extends Context {
|
|
211
|
+
type = "response";
|
|
212
|
+
withMessage(message, id) {
|
|
213
|
+
return new ResponseContext(this.dialog, message, id, this);
|
|
214
|
+
}
|
|
215
|
+
listen(callback, done = false) {
|
|
216
|
+
return super.listen(callback, done);
|
|
217
|
+
}
|
|
218
|
+
send(data, done = false) {
|
|
219
|
+
return super.send(data, done);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export {
|
|
223
|
+
RespondersBase,
|
|
224
|
+
WorkerDialog
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sources":["../src/worker.ts"],"sourcesContent":["import { getTransferrable } from './workerUtils';\n\n// Types\n\ntype WebWorker = Worker | (Window & typeof globalThis) | (WorkerGlobalScope & typeof globalThis);\n\ntype MethodReturn<R, M extends keyof R> = R[M] extends (...args: any[]) => any\n ? Partial<Awaited<ReturnType<R[M]>>>\n : never;\n\ntype MethodArg<R, M extends keyof R> = R[M] extends (arg: infer A, ...args: any[]) => any ? Partial<A> : never;\n\nexport type Ctx<R, K extends keyof R> = {\n id: string;\n parentId?: string;\n message: K;\n done: boolean;\n};\n\ntype Data<R, M extends keyof R> = MethodReturn<R, M> | MethodArg<R, M> | Error;\n\ntype Event<R, M extends keyof R> = MessageEvent<{ data: Data<R, M>; ctx: Ctx<R, M> }>;\n\ntype Listener<R, M extends keyof R, D extends Data<R, M> = Data<R, M>, S extends Context<R, M> = Context<R, M>> = (\n data: D,\n self: S,\n event: Event<R, M>,\n) => void | Promise<void>;\n\n// Constants\n\nconst ALL = '*';\n\nconst RESPONDER = Symbol('responder');\n\nfunction checkClosed(obj: any, label: string = 'external') {\n if (obj?.closed) throw new Error(`Context ${obj.id} is closed (${label})`, { cause: obj });\n}\n\nfunction checkMessage(obj: any, message: any) {\n if (!message) throw new Error('New context must have message', { cause: obj });\n}\n\n// Classes\n\nexport class WorkerDialog<R extends RespondersBase<any>> {\n readonly contexts: Set<Context<R, keyof R>> = new Set();\n protected closed = false;\n protected currentId = 0; // IDs are intentionally not global\n\n constructor(\n public worker: WebWorker,\n public responders: R,\n public name: string,\n public debug: number = 0, // 0 - none, 1 - general messages, 2 - sub/unsub, 3 - transport TODO Object?\n ) {\n if (!this.worker) return; // Next.js SSR\n\n for (const message in this.responders) {\n if (!this.responders?.[message]?.[RESPONDER]) continue;\n\n // root context with id=all\n const rootContext = new ResponseContext<R, typeof message>(this, message, ALL);\n\n //TODO: unsub(); ? Probably not needed, root must be always listening until closed by WorkerDialog\n rootContext.listen(async (data, context, event) => {\n // create sub context with ID\n const responseContext = context.withMessage(message, event.data.ctx.id);\n\n /* v8 ignore start */\n if (this.debug)\n console.log('Root Listener', rootContext.info, {\n rootContext: context,\n data,\n responseContext,\n event,\n responder: this.responders[message],\n });\n /* v8 ignore stop */\n\n try {\n responseContext.send(await (this.responders[message] as any)(data, responseContext), true);\n responseContext.close('root.listener');\n } catch (e) {\n /* v8 ignore next */\n if (this.debug) console.error(`Error in responder ${responseContext.info}`, { e });\n responseContext.send(e, true);\n }\n }, true);\n }\n }\n\n getID(): string {\n return (++this.currentId).toString();\n }\n\n close(): void {\n for (const context of this.contexts) context.close('root');\n this.contexts.clear();\n }\n\n withMessage<M extends keyof R>(message: M): RequestContext<R, M> {\n return new RequestContext<R, M>(this, message, this.getID()); // always new context\n }\n}\n\nexport class RespondersBase<R> {\n create<M extends keyof R, F extends (input: any, context: ResponseContext<R, M>) => any>(\n message: M,\n responder: F,\n ): F {\n responder[RESPONDER] = true;\n return responder as any;\n }\n}\n\n// can only be used to create actual contexts, carries the context data\nabstract class Context<R, M extends keyof R> {\n protected readonly unsub: Set<() => void> = new Set();\n protected closed = false;\n\n constructor(\n protected readonly dialog: WorkerDialog<any>,\n public message: M,\n public id: string | undefined,\n public parent: Context<R, keyof R> | undefined | null = null,\n ) {\n checkClosed(dialog, 'newContext');\n checkClosed(parent, 'newContext');\n checkMessage(this, message);\n this.id = id ?? parent?.id;\n if (!this.id) throw new Error('ID is required', { cause: this });\n this.dialog.contexts.add(this);\n }\n\n abstract withMessage<M2 extends keyof R>(message: M2, id?: string): Context<R, M2>;\n\n close(reason: string = 'external'): void {\n /* v8 ignore next */\n if (this.dialog.debug >= 2) console.log('Closing', this.info, reason, this);\n\n this.dialog.contexts.delete(this);\n\n // Unsubscribe from all listeners\n for (const unsub of this.unsub) unsub();\n this.unsub.clear();\n\n // Remove all child contexts\n this.dialog.contexts.forEach((context) => {\n if (context.parent?.id === this.id) context.close('parent');\n });\n\n this.closed = true;\n }\n\n get info() {\n return `${this.dialog.name}:${this.parent?.id ?? ALL}/${this.id}:${this.message.toString()}[${this.closed ? 'closed' : 'open'},${this.type}]`;\n }\n\n get debugInfo(): {\n id?: string;\n parentId?: string;\n message: string | number | symbol;\n closed: boolean;\n type: string;\n listeners: number;\n } {\n return {\n id: this.id,\n parentId: this.parent?.id,\n message: this.message,\n closed: this.closed,\n type: this.type,\n listeners: this.unsub.size,\n };\n }\n\n abstract type: 'request' | 'response' | string;\n\n protected postMessage(data?: Data<R, M>, done = false): Data<R, M> | undefined {\n checkClosed(this, 'postMessage');\n\n const transfer = getTransferrable(data);\n\n const ctx = {\n done,\n id: this.id,\n parentId: this.parent?.id,\n message: this.message,\n };\n\n /* v8 ignore next */\n if (this.dialog.debug >= 3) console.log('postMessage', this.info, { ctx, data, transfer }, this);\n\n this.dialog.worker.postMessage({ data, ctx }, transfer);\n\n return data;\n }\n\n send(data?: Data<R, M>, done = false): Data<R, M> | undefined {\n return this.postMessage(data, done);\n }\n\n /**\n * Does not automatically close, assumes consumer will close\n *\n * ```ts\n * dialog.request('x').fetch((c) => {\n * c.response('x').listen(() => { ... }); // listens to \"x\", will be closed by upper level fetch\n * c.request('x').fetcH(y, (c) => { ... }); // closes\n * }) // closes\n *\n * const un = dialog.context('x').listen((e) => { ... }); // listens to 'x'\n * un(); // closes\n *\n * const un = dialog.context('x').listen((e) => { ... }); // listens to 'x'\n * un(); // closes\n * ```\n */\n listen(callback: Listener<R, M>, done = false): () => void {\n checkClosed(this, 'listen');\n\n const controller = new AbortController();\n\n this.dialog.worker.addEventListener(\n 'message',\n async (event: Event<R, M>) => {\n try {\n const {\n data: { ctx, data },\n } = event;\n\n if (\n !ctx ||\n (this.id !== ALL && this.id !== ctx.id) ||\n this.message !== ctx.message ||\n (done && !ctx.done)\n )\n return; // unknown message from devtools etc., or different context\n\n /* v8 ignore next */\n if (this.dialog.debug) console.log('Listener event', this.info, event, this);\n\n await callback(data, this, event); // Requests can come here, but they're typed in responders\n } catch (e) {\n /* v8 ignore next */\n if (this.dialog.debug) console.error(`Error in listener ${this.info}`, this, { event, e });\n //TODO Respond with error?\n }\n },\n { signal: controller.signal },\n );\n\n const unsub = () => {\n /* v8 ignore next */\n if (this.dialog.debug >= 2) console.log('Stop listening', this.info, this);\n controller.abort();\n this.unsub.delete(unsub);\n };\n\n this.unsub.add(unsub);\n\n return unsub;\n }\n}\n\nclass RequestContext<R, M extends keyof R> extends Context<R, M> {\n type = 'request';\n\n withMessage<M2 extends keyof R>(message: M2, id?: string): RequestContext<R, M2> {\n return new RequestContext<R, M2>(this.dialog, message, id, this);\n }\n\n send(data?: MethodArg<R, M>, done = false): MethodArg<R, M> {\n return super.send(data, done) as any;\n }\n\n listen(callback: Listener<R, M, MethodReturn<R, M>, RequestContext<R, M>>, done = false): () => void {\n return super.listen(callback, done);\n }\n\n /**\n * Returns response\n * If you need to work with ctx, use callback\n * Closed automatically on response\n *\n * dialog.fetch('x', (c) => {\n * c.listen(() => { ... }); // will be closed by upper level fetch\n * c.fetcH(y, (c) => { ... }); // closes c\n * c.context().fetcH(y, (c) => { ... }); // closes sub, not closes c, will be closed by upper level fetch\n * })\n */\n async fetch(\n data?: MethodArg<R, M>,\n callback?: (context: RequestContext<R, M>) => void,\n ): Promise<MethodReturn<R, M>> {\n checkClosed(this, 'fetch');\n\n const res = await Promise.all([\n callback?.(this),\n this.expect(), // will be closed in expect -> listener -> unsub\n this.send(data, true),\n ]);\n\n /* v8 ignore next */\n if (this.dialog.debug >= 2) console.log('Fetch', this.info, this);\n\n this.close('fetch');\n\n return res[1];\n }\n\n protected async expect(): Promise<MethodReturn<R, M>> {\n checkClosed(this, 'expect');\n\n //FIXME Manual promise type\n return new Promise((resolve, reject) => {\n const responseListener = this.listen(async (data) => {\n try {\n if ((data as any) instanceof Error) throw data;\n responseListener();\n /* v8 ignore next */\n if (this.dialog.debug >= 3) console.log('Expect', this.info, data, this);\n resolve(data as any);\n } catch (e) {\n reject(e);\n }\n }, true);\n });\n }\n}\n\nclass ResponseContext<R, M extends keyof R> extends Context<R, M> {\n type = 'response';\n\n withMessage<M2 extends keyof R>(message: M2, id?: string): ResponseContext<R, M2> {\n return new ResponseContext<R, M2>(this.dialog, message, id, this);\n }\n\n listen(callback: Listener<R, M, MethodArg<R, M>, ResponseContext<R, M>>, done = false): () => void {\n return super.listen(callback, done);\n }\n\n send(data?: MethodReturn<R, M>, done = false): MethodReturn<R, M> {\n return super.send(data, done) as any;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.test.d.ts","sourceRoot":"","sources":["../src/worker.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workerUtils.d.ts","sourceRoot":"","sources":["../src/workerUtils.ts"],"names":[],"mappings":"AAoBA,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,GAAG,GAAG,IAAI,CAE1D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,GAAQ,GAAG,YAAY,EAAE,CAO/D"}
|