-
Notifications
You must be signed in to change notification settings - Fork 1
/
mod.ts
230 lines (200 loc) Β· 5.92 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import { dirname, join } from "https://deno.land/[email protected]/path/mod.ts";
type AsyncFunction<S extends unknown[], T> = (...params: S) => Promise<T>;
type MaybeAsyncFunction<S extends unknown[], T> = (
...params: S
) => T | Promise<T>;
type DenoWorkerOptions = true | {
namespace?: boolean;
/** Set to `"none"` to disable all the permissions in the worker. */
permissions?: "inherit" | "none" | {
env?: "inherit" | boolean;
hrtime?: "inherit" | boolean;
/** The format of the net access list must be `hostname[:port]`
* in order to be resolved.
*
* ```
* net: ["https://deno.land", "localhost:8080"],
* ```
* */
net?: "inherit" | boolean | string[];
plugin?: "inherit" | boolean;
read?: "inherit" | boolean | Array<string | URL>;
run?: "inherit" | boolean;
write?: "inherit" | boolean | Array<string | URL>;
};
};
/** An error thrown by a parry function or Worker */
export class ParryError extends Error {
constructor(message: string = "") {
super(message);
this.name = this.constructor.name;
this.stack;
}
}
/** The global parry instance and ParryFunction spawner */
export interface Parry {
<S extends unknown[], T extends unknown>(
/** Creates a new ParryFunction function */
original?: (...params: S) => T | Promise<T>,
deno?: DenoWorkerOptions,
): ParryFunction<S, T>;
/** Closes all parry Workers */
close(): void;
}
/** A callable function in it's own Worker */
export interface ParryFunction<S extends unknown[], T extends unknown>
extends AsyncFunction<S, T> {
/** The id of the functions Worker */
id: number;
/** Is the Worker closed? */
closed: boolean;
/** Closes the current Worker */
close: () => void;
/** Sets the current Workers function */
set: (f: MaybeAsyncFunction<S, T>) => void;
/** Runs a single function inside the Worker */
run: <U extends unknown[], V extends unknown>(
f: MaybeAsyncFunction<U, V>,
...params: U
) => Promise<V>;
/** Declares a global variable to specified value */
declare: (ident: string, value: unknown) => void;
/** Adds a global function with the specified identifier */
define: (ident: string, func: Function) => void;
}
// All of the current parry functions
const funcs: Map<number, ParryFunction<any, any>> = new Map();
let funcsIndex: number = 0;
/** Move a function into it's own Worker */
export const parry: Parry = <S extends unknown[], T extends unknown>(
original?: (...params: S) => T | Promise<T>,
deno?: DenoWorkerOptions,
): ParryFunction<S, T> => {
let id = 0;
const promises: {
[id: number]: [
(value?: T | PromiseLike<T> | any | PromiseLike<any>) => void,
(reason?: any) => void,
];
} = {};
const worker = new Worker(
join(dirname(import.meta.url), "worker.js"), // Allows the Worker to be run both locally and imported from an URL
{ type: "module", deno },
);
worker.onmessage = (event) => {
const { type, id, data } = event.data; // All parry Worker messages use this object structure
switch (type) {
case "resolve":
promises[id][0](data);
delete promises[id];
break;
case "reject":
promises[id][1](data);
delete promises[id];
break;
case "error":
throw new ParryError(data);
break;
default:
throw new ParryError(`Unknown message type "${type}"`);
}
};
// Throw errors when they are encountered in the worker (only works for certain error though)
worker.onerror = (e) => {
throw new ParryError(e.message);
};
worker.onmessageerror = () => {
throw new ParryError("Worker message error");
};
// Create the ParryFunction that is returned
const func: ParryFunction<S, T> = (...params: S): Promise<T> => {
if (func.closed) {
throw new ParryError("Cannot call closed Worker");
}
return new Promise((resolve, reject) => {
promises[id] = [resolve, reject];
worker.postMessage({
type: "call",
data: params,
id,
});
id++;
});
};
func.id = funcsIndex++; // This id is the global id of the worker
func.closed = false;
// A method for closing the parry Worker
func.close = (): void => {
if (func.closed) {
throw new ParryError("Cannot close already closed Worker");
}
worker.terminate();
func.closed = true;
funcs.delete(func.id);
};
// A method for setting the parry Workers function
func.set = (f: MaybeAsyncFunction<S, T>): void => {
if (func.closed) {
throw new ParryError("Cannot set closed Worker");
}
worker.postMessage({
type: "set",
data: f.toString(),
});
};
// A method for running a function in the parry Worker
func.run = <U extends unknown[], V extends unknown>(
f: MaybeAsyncFunction<U, V>,
...params: U
): Promise<V> => {
if (func.closed) {
throw new ParryError("Cannot run closed Worker");
}
return new Promise((resolve, reject) => {
promises[id] = [resolve, reject];
worker.postMessage({
type: "run",
data: {
func: f.toString(),
params,
},
id,
});
id++;
});
};
// Declares a global variable
func.declare = (ident: string, value: unknown) => {
worker.postMessage({
type: "declare",
data: {
ident,
value,
},
});
};
// Defines a global function
func.define = (ident: string, func: Function) => {
worker.postMessage({
type: "define",
data: {
ident,
func: func.toString(),
},
});
};
// Sets the Workers main function if specified
if (original !== undefined) {
func.set(original);
}
// Adds the parry function to the global list of parry functions (for global parry stuff like closing all)
funcs.set(func.id, func);
return func;
};
/** Closes all Workers */
parry.close = (): void => {
for (const [id, func] of funcs) {
func.close();
}
};
export default parry;