@remix_labs/machine-starter 1.1924.0-dev → 1.1929.0-dev

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.
@@ -1,2 +1,2 @@
1
- var GROOVEBOX_BUILD = "1924";
1
+ var GROOVEBOX_BUILD = "1929";
2
2
  export { GROOVEBOX_BUILD }
package/index.js CHANGED
@@ -1,15 +1,308 @@
1
- function workerify(code) {
2
- let blob = new Blob([code], {type: 'application/javascript'});
3
- return new Worker(URL.createObjectURL(blob));
1
+ import { nanoid } from "nanoid";
2
+ import { GROOVEBOX_BUILD } from "./groovebox_build.js";
3
+
4
+ let terminate = function(f) {};
5
+ if (globalThis.ThisIsNode) {
6
+ terminate = function(f) {
7
+ Process.on("exit", () => {
8
+ console.log("terminating machine worker");
9
+ f()
10
+ })
11
+ }
12
+ };
13
+
14
+ function CreateVMID() {
15
+ return nanoid()
16
+ }
17
+
18
+ let initialMask = 64 + 1024; // default for web
19
+ globalThis.MixSetMask = (m => {
20
+ initialMask = m;
21
+ });
22
+
23
+ function StartWASM(hub, baseURL, org, workspace, vmID, user, token, noOutputViaMQTT) {
24
+ return StartWASM2(hub, {
25
+ baseURL: baseURL,
26
+ org: org,
27
+ workspace: workspace,
28
+ vmID: vmID,
29
+ user: user,
30
+ token: token,
31
+ noOutputViaMQTT: noOutputViaMQTT,
32
+ machType: "WASM"
33
+ })
34
+ }
35
+
36
+ function StartWASM2(hub, config) {
37
+ console.log("GROOVEBOX_BUILD (machine-starter)", GROOVEBOX_BUILD);
38
+ return hub.newChannel().then(channel => {
39
+ let bundle;
40
+ if (globalThis.ThisIsNode) {
41
+ bundle = new URL("../../machine-wasm/dist/node-bundle.js", import.meta.url);
42
+ } else if (globalThis.GROOVEBOX_URL_PREFIX) {
43
+ // FIXME: Stopgap solution until we figure out a way to bundle groovebox correctly
44
+ // already fire off the download of the wasm code:
45
+ let code_url = new URL(`${globalThis.GROOVEBOX_URL_PREFIX}/machine-wasm-code.wasm`, window.location.href);
46
+ fetch(code_url, {cache:"default"}).then(resp => { return resp.arrayBuffer() });
47
+ bundle = new URL(`${globalThis.GROOVEBOX_URL_PREFIX}/machine-wasm-core.js`, window.location.href);
48
+ } else {
49
+ // already fire off the download of the wasm code:
50
+ let code_url = new URL("/g/machine-wasm-code.wasm", window.location.href);
51
+ fetch(code_url, {cache:"default"}).then(resp => { return resp.arrayBuffer() });
52
+ bundle = new URL("/g/machine-wasm-core.js", window.location.href);
53
+ }
54
+ let localFFIs = {};
55
+ if (config.localFFIs) {
56
+ for (const [k, v] of Object.entries(config.localFFIs)) {
57
+ if (v.isRaw && (v.canFail || v.useJsonDecoder)) {
58
+ console.error("A raw, local FFI must neither be failing nor use the JSON decoder: " + k);
59
+ } else {
60
+ localFFIs[k] = {...v};
61
+ delete localFFIs[k].run;
62
+ }
63
+ }
64
+ }
65
+ let worker = new Worker(bundle);
66
+ let config_msg =
67
+ { "_rmx_type": "msg_vm_configure",
68
+ "baseURL": config.baseURL,
69
+ "org": config.org,
70
+ "workspace": config.workspace,
71
+ "vmID": config.vmID,
72
+ "user": config.user,
73
+ "token": config.token,
74
+ "hub": channel.port,
75
+ "outputViaMQTT": !(config.noOutputViaMQTT),
76
+ "machType": config.machType,
77
+ "debugMask": initialMask,
78
+ "localFFIs": localFFIs,
79
+ "grooveboxUrlPrefix": globalThis.GROOVEBOX_URL_PREFIX,
80
+ "allowInsecureHttp": globalThis.GROOVEBOX_ALLOW_INSECURE_HTTP,
81
+ "interceptFFI": config.interceptFFI,
82
+ "interceptDynloadFFI": config.interceptDynloadFFI,
83
+ };
84
+ worker.postMessage(config_msg, [ channel.port ]);
85
+ terminate(() => worker.terminate());
86
+ globalThis.MixSetMask = (m => {
87
+ worker.postMessage({ "_rmx_type": "msg_vm_logMask",
88
+ "mask": m
89
+ })
90
+ });
91
+ if (config.localFFIs)
92
+ setupLocalFFIs(hub, config); // don't await!
93
+ return worker;
94
+ })
95
+ }
96
+
97
+ class Token {
98
+ constructor(data, enabled, extras) {
99
+ // data: must be an Uint8Array
100
+ // enabled: a number with the bitset of enabled operations
101
+ // extras: a map
102
+ if (!data instanceof Uint8Array)
103
+ throw new Error("Token: data must be an Uint8Array");
104
+ if (typeof(enabled) != "number")
105
+ throw new Error("Token: enabled_ops must be a number");
106
+ this.data = data;
107
+ this.enabled = enabled;
108
+ this.extras = extras;
109
+ }
110
+ encode() {
111
+ return new Map([
112
+ [ "_rmx_type", "token" ],
113
+ [ "data", this.data ],
114
+ [ "enabled", this.enabled ],
115
+ [ "extras", this.extras ]
116
+ ]);
117
+ }
4
118
  }
5
119
 
6
- import MachineWASMWorker from './machine-wasm.worker.js';
120
+ function decode_Token(m) {
121
+ return new Token(m.get("data"), m.get("enabled"), m.get("extras"))
122
+ }
123
+
124
+ class Case {
125
+ constructor(name, arg) {
126
+ if (typeof(name) != "string")
127
+ throw new Error("Case: name must be a string");
128
+ this.name = name;
129
+ this.arg = arg;
130
+ }
131
+ encode() {
132
+ return new Map([
133
+ [ "_rmx_type", "case" ],
134
+ [ "name", this.name ],
135
+ [ "arg", this.arg ],
136
+ ]);
137
+ }
138
+ }
7
139
 
8
- function GetMachineWASMWorker() {
9
- return workerify(MachineWASMWorker);
140
+ function decode_Case(m) {
141
+ return new Case(m.get("name"), m.get("arg"))
10
142
  }
11
143
 
12
- globalThis.GetMachineWASMWorker = GetMachineWASMWorker;
144
+ class Opaque {
145
+ constructor(buf) {
146
+ if (!(buf instanceof Uint32Array))
147
+ throw new Error("Opaque: must be a Uint32Array");
148
+ this.data = buf;
149
+ }
150
+ encode() {
151
+ return new Map([
152
+ [ "_rmx_type", "opaque" ],
153
+ [ "data", this.data ],
154
+ ]);
155
+ }
156
+ }
157
+
158
+ function decode_Opaque(m) {
159
+ return new Opaque(m.get("data"))
160
+ }
161
+
162
+ function encode(val) {
163
+ switch (typeof(val)) {
164
+ case "object":
165
+ if (val === null) return val;
166
+ if (Array.isArray(val)) {
167
+ // array: map the array elements
168
+ val = val.map(encode)
169
+ } else if (Object.getPrototypeOf(val) === Object.prototype) {
170
+ // simple object: map the object elements
171
+ val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, encode(elem) ]))
172
+ } else if (val.encode instanceof Function) {
173
+ // map after encode(): map the map elements
174
+ val = new Map(Array.from(val.encode().entries()).map(([ key, elem ]) => [ key, encode(elem) ]))
175
+ };
176
+ return val;
177
+ default:
178
+ return val;
179
+ }
180
+ }
181
+
182
+ function decode(val) {
183
+ switch (typeof(val)) {
184
+ case "object":
185
+ if (val === null) return val;
186
+ if (Array.isArray(val)) {
187
+ // array: map the array elements
188
+ val = val.map(decode)
189
+ } else if (Object.getPrototypeOf(val) === Object.prototype) {
190
+ // simple object: map the object elements
191
+ val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, decode(elem) ]))
192
+ } else if (val instanceof Map) {
193
+ val = new Map(Array.from(val.entries()).map(([ key, elem ]) => [ key, decode(elem) ]))
194
+ let ty = val.get("_rmx_type");
195
+ switch (ty) {
196
+ case "token":
197
+ return decode_Token(val);
198
+ case "case":
199
+ return decode_Case(val);
200
+ case "opaque":
201
+ return decode_Opaque(val);
202
+ default:
203
+ throw new Error("decode: bad _rmx_type: " + ty);
204
+ }
205
+ };
206
+ return val;
207
+ default:
208
+ return val;
209
+ }
210
+ }
211
+
212
+ async function setupLocalFFIs(hub, config) {
213
+ let channel = await hub.newChannel();
214
+ let comm = new FFIComm(hub, channel);
215
+ let localFFIs = config.localFFIs;
216
+ await channel.setLocalSubTopic("/local/ffi/call");
217
+ await channel.setLocalPubTopic("/local/ffi/return");
218
+ let sub = await channel.subscribe("/local/ffi/call");
219
+ while (true) {
220
+ let msg = await sub.next();
221
+ let name = msg.payload.name;
222
+ let call_id = msg.payload.call_id;
223
+ let args = decode(msg.payload.args);
224
+ let fun = localFFIs[name];
225
+ try {
226
+ if (!fun)
227
+ throw new Error("no such local FFI: " + name);
228
+ let connector =
229
+ { call_id: call_id,
230
+ hub: hub,
231
+ channel: channel,
232
+ state: config.state,
233
+ };
234
+ let r = fun instanceof Function ? fun(connector, args) : fun.run(connector, args);
235
+ if (r instanceof Promise) {
236
+ await comm.later(call_id);
237
+ r.then(
238
+ (value) => comm.returnOrFail(fun, call_id, value),
239
+ (reason) => comm.error(call_id, reason.message, reason.stack));
240
+ } else {
241
+ await comm.returnOrFail(fun, call_id, r);
242
+ }
243
+ } catch (reason) {
244
+ await comm.error(call_id, reason.message, reason.stack);
245
+ }
246
+ }
247
+ }
248
+
249
+ class FFIComm {
250
+ constructor(hub, channel) {
251
+ this.hub = hub;
252
+ this.channel = channel;
253
+ }
254
+ later(call_id) {
255
+ let r_payload =
256
+ { call_id: call_id,
257
+ };
258
+ let response = this.hub.newLocalMessage("msg_ffi_later", "starter", r_payload);
259
+ return this.channel.publish("/local/ffi/return", response, false);
260
+ }
261
+ return_(fun, call_id, result) {
262
+ let r_payload =
263
+ { call_id: call_id,
264
+ value: fun.useJsonDecoder ? result : encode(result)
265
+ };
266
+ let response = this.hub.newLocalMessage("msg_ffi_return", "starter", r_payload);
267
+ return this.channel.publish("/local/ffi/return", response, false);
268
+ }
269
+ error(call_id, message, stack) {
270
+ let r_payload =
271
+ { call_id: call_id,
272
+ message: message,
273
+ stack: stack === undefined ? [] : stack,
274
+ };
275
+ let response = this.hub.newLocalMessage("msg_ffi_error", "starter", r_payload);
276
+ this.channel.publish("/local/ffi/return", response, false);
277
+ }
278
+ returnOrFail(fun, call_id, result) {
279
+ if (fun.canFail) {
280
+ var name, arg;
281
+ if (result instanceof Case) {
282
+ name = result.name;
283
+ arg = result.arg;
284
+ } else if (fun.useJsonDecoder && result._rmx_type && result._rmx_type.startsWith("{tag}case:")) {
285
+ name = result._rmx_type.substring(10);
286
+ arg = result._rmx_value;
287
+ } else {
288
+ return this.error(call_id, "expected result from local FFI that can fail");
289
+ }
290
+ switch (name) {
291
+ case "ok":
292
+ return this.return_(fun, call_id, arg);
293
+ case "error":
294
+ return this.error(call_id, arg);
295
+ default:
296
+ return this.error(call_id, "expected result from local FFI that can fail, found case value " + name);
297
+ }
298
+ } else {
299
+ return this.return_(fun, call_id, result);
300
+ }
301
+ }
302
+ }
13
303
 
14
- export * from './common.js';
304
+ // use worker.terminate() to shut a worker down!
15
305
 
306
+ export { CreateVMID, StartWASM, StartWASM2,
307
+ Token, Case, Opaque
308
+ }
package/node.js CHANGED
@@ -1,31 +1,7 @@
1
+ import Worker from "web-worker";
2
+ global.Worker = Worker;
3
+ import WT from "worker_threads";
4
+ global.MessageChannel = WT.MessageChannel;
1
5
  import Process from "process";
2
6
  global.Process = Process;
3
-
4
- // NB: The only way I've made this work in node+webpack is if:
5
- //
6
- // * we use Worker from worker_threads directly
7
- // * we build each worker's module with webpack as an ESM .mjs bundle
8
- // * we put all the bundles in one directory
9
- // * we load the worker with a URL('./foo.mjs')
10
- //
11
- // Other ways to package/load might work, but definitely seems like
12
- // using worker_threads.Worker directly is needed (rather than through,
13
- // say, the web-worker packages that wraps either worker_threads.Worker
14
- // or the browser Worker to give the same interface). Webpack says ESM
15
- // is necessary but I'm not sure if this is true for either the main
16
- // script or the workers.
17
- import { Worker } from "worker_threads";
18
-
19
- function GetMachineWASMWorker() {
20
- return new Worker(new URL("./machine-wasm.mjs", import.meta.url));
21
- }
22
- global.GetMachineWASMWorker = GetMachineWASMWorker;
23
-
24
- // Can't just `export *` because we need hub-client below in common.js
25
- import * as Hub from "@remix_labs/hub-client";
26
- export { Hub };
27
-
28
- import { fileURLToPath } from 'url';
29
- global.__filename = fileURLToPath(import.meta.url);
30
-
31
- export * from "./common.js";
7
+ export * from "./index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix_labs/machine-starter",
3
- "version": "1.1924.0-dev",
3
+ "version": "1.1929.0-dev",
4
4
  "description": "start the groove",
5
5
  "main": "node.js",
6
6
  "browser": "index.js",
@@ -11,7 +11,7 @@
11
11
  "author": "Remixlabs staff",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
- "@remix_labs/hub-client": "1.1924.0-dev",
14
+ "@remix_labs/hub-client": "1.1929.0-dev",
15
15
  "nanoid": "^3.1.12",
16
16
  "web-worker": "^1.2.0"
17
17
  },
package/start.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import Process from "process";
2
- import { StartWASM2, Hub } from "./node.js";
3
-
2
+ import { StartWASM2 } from "./node.js";
3
+ import * as Hub from "@remix_labs/hub-client";
4
4
  if (Process.stdout._handle)
5
5
  Process.stdout._handle.setBlocking(true);
6
6
  if (Process.stderr._handle)
package/common.js DELETED
@@ -1,292 +0,0 @@
1
- import { nanoid } from "nanoid";
2
- import { GROOVEBOX_BUILD } from "./groovebox_build.js";
3
-
4
- let terminate = function(f) {};
5
- if (globalThis.ThisIsNode) {
6
- terminate = function(f) {
7
- Process.on("exit", () => {
8
- console.log("terminating machine worker");
9
- f()
10
- })
11
- }
12
- };
13
-
14
- function CreateVMID() {
15
- return nanoid()
16
- }
17
-
18
- let initialMask = 64 + 1024; // default for web
19
- globalThis.MixSetMask = (m => {
20
- initialMask = m;
21
- });
22
-
23
- function StartWASM(hub, baseURL, org, workspace, vmID, user, token, noOutputViaMQTT) {
24
- return StartWASM2(hub, {
25
- baseURL: baseURL,
26
- org: org,
27
- workspace: workspace,
28
- token: token,
29
- noOutputViaMQTT: noOutputViaMQTT,
30
- machType: "WASM"
31
- })
32
- }
33
-
34
- function StartWASM2(hub, config) {
35
- console.log("GROOVEBOX_BUILD (machine-starter)", GROOVEBOX_BUILD);
36
- return hub.newChannel().then(channel => {
37
- // injected by either index.js or node.js
38
- let worker = globalThis.GetMachineWASMWorker();
39
- let localFFIs = {};
40
- if (config.localFFIs) {
41
- for (const [k, v] of Object.entries(config.localFFIs)) {
42
- if (v.isRaw && (v.canFail || v.useJsonDecoder)) {
43
- console.error("A raw, local FFI must neither be failing nor use the JSON decoder: " + k);
44
- } else {
45
- localFFIs[k] = {...v};
46
- delete localFFIs[k].run;
47
- }
48
- }
49
- }
50
- let config_msg =
51
- { "_rmx_type": "msg_vm_configure",
52
- "baseURL": config.baseURL,
53
- "org": config.org,
54
- "workspace": config.workspace,
55
- "vmID": config.vmID,
56
- "user": config.user,
57
- "token": config.token,
58
- "hub": channel.port,
59
- "outputViaMQTT": !(config.noOutputViaMQTT),
60
- "machType": config.machType,
61
- "debugMask": initialMask,
62
- "localFFIs": localFFIs,
63
- "grooveboxUrlPrefix": globalThis.GROOVEBOX_URL_PREFIX,
64
- "allowInsecureHttp": globalThis.GROOVEBOX_ALLOW_INSECURE_HTTP,
65
- "interceptFFI": config.interceptFFI,
66
- "interceptDynloadFFI": config.interceptDynloadFFI,
67
- };
68
- worker.postMessage(config_msg, [ channel.port ]);
69
- terminate(() => worker.terminate());
70
- globalThis.MixSetMask = (m => {
71
- worker.postMessage({ "_rmx_type": "msg_vm_logMask",
72
- "mask": m
73
- })
74
- });
75
- if (config.localFFIs)
76
- setupLocalFFIs(hub, config); // don't await!
77
- return worker;
78
- })
79
- }
80
-
81
- class Token {
82
- constructor(data, enabled, extras) {
83
- // data: must be an Uint8Array
84
- // enabled: a number with the bitset of enabled operations
85
- // extras: a map
86
- if (!data instanceof Uint8Array)
87
- throw new Error("Token: data must be an Uint8Array");
88
- if (typeof(enabled) != "number")
89
- throw new Error("Token: enabled_ops must be a number");
90
- this.data = data;
91
- this.enabled = enabled;
92
- this.extras = extras;
93
- }
94
- encode() {
95
- return new Map([
96
- [ "_rmx_type", "token" ],
97
- [ "data", this.data ],
98
- [ "enabled", this.enabled ],
99
- [ "extras", this.extras ]
100
- ]);
101
- }
102
- }
103
-
104
- function decode_Token(m) {
105
- return new Token(m.get("data"), m.get("enabled"), m.get("extras"))
106
- }
107
-
108
- class Case {
109
- constructor(name, arg) {
110
- if (typeof(name) != "string")
111
- throw new Error("Case: name must be a string");
112
- this.name = name;
113
- this.arg = arg;
114
- }
115
- encode() {
116
- return new Map([
117
- [ "_rmx_type", "case" ],
118
- [ "name", this.name ],
119
- [ "arg", this.arg ],
120
- ]);
121
- }
122
- }
123
-
124
- function decode_Case(m) {
125
- return new Case(m.get("name"), m.get("arg"))
126
- }
127
-
128
- class Opaque {
129
- constructor(buf) {
130
- if (!(buf instanceof Uint32Array))
131
- throw new Error("Opaque: must be a Uint32Array");
132
- this.data = buf;
133
- }
134
- encode() {
135
- return new Map([
136
- [ "_rmx_type", "opaque" ],
137
- [ "data", this.data ],
138
- ]);
139
- }
140
- }
141
-
142
- function decode_Opaque(m) {
143
- return new Opaque(m.get("data"))
144
- }
145
-
146
- function encode(val) {
147
- switch (typeof(val)) {
148
- case "object":
149
- if (val === null) return val;
150
- if (Array.isArray(val)) {
151
- // array: map the array elements
152
- val = val.map(encode)
153
- } else if (Object.getPrototypeOf(val) === Object.prototype) {
154
- // simple object: map the object elements
155
- val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, encode(elem) ]))
156
- } else if (val.encode instanceof Function) {
157
- // map after encode(): map the map elements
158
- val = new Map(Array.from(val.encode().entries()).map(([ key, elem ]) => [ key, encode(elem) ]))
159
- };
160
- return val;
161
- default:
162
- return val;
163
- }
164
- }
165
-
166
- function decode(val) {
167
- switch (typeof(val)) {
168
- case "object":
169
- if (val === null) return val;
170
- if (Array.isArray(val)) {
171
- // array: map the array elements
172
- val = val.map(decode)
173
- } else if (Object.getPrototypeOf(val) === Object.prototype) {
174
- // simple object: map the object elements
175
- val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, decode(elem) ]))
176
- } else if (val instanceof Map) {
177
- val = new Map(Array.from(val.entries()).map(([ key, elem ]) => [ key, decode(elem) ]))
178
- let ty = val.get("_rmx_type");
179
- switch (ty) {
180
- case "token":
181
- return decode_Token(val);
182
- case "case":
183
- return decode_Case(val);
184
- case "opaque":
185
- return decode_Opaque(val);
186
- default:
187
- throw new Error("decode: bad _rmx_type: " + ty);
188
- }
189
- };
190
- return val;
191
- default:
192
- return val;
193
- }
194
- }
195
-
196
- async function setupLocalFFIs(hub, config) {
197
- let channel = await hub.newChannel();
198
- let comm = new FFIComm(hub, channel);
199
- let localFFIs = config.localFFIs;
200
- await channel.setLocalSubTopic("/local/ffi/call");
201
- await channel.setLocalPubTopic("/local/ffi/return");
202
- let sub = await channel.subscribe("/local/ffi/call");
203
- while (true) {
204
- let msg = await sub.next();
205
- let name = msg.payload.name;
206
- let call_id = msg.payload.call_id;
207
- let args = decode(msg.payload.args);
208
- let fun = localFFIs[name];
209
- try {
210
- if (!fun)
211
- throw new Error("no such local FFI: " + name);
212
- let connector =
213
- { call_id: call_id,
214
- hub: hub,
215
- channel: channel,
216
- state: config.state,
217
- };
218
- let r = fun instanceof Function ? fun(connector, args) : fun.run(connector, args);
219
- if (r instanceof Promise) {
220
- await comm.later(call_id);
221
- r.then(
222
- (value) => comm.returnOrFail(fun, call_id, value),
223
- (reason) => comm.error(call_id, reason.message, reason.stack));
224
- } else {
225
- await comm.returnOrFail(fun, call_id, r);
226
- }
227
- } catch (reason) {
228
- await comm.error(call_id, reason.message, reason.stack);
229
- }
230
- }
231
- }
232
-
233
- class FFIComm {
234
- constructor(hub, channel) {
235
- this.hub = hub;
236
- this.channel = channel;
237
- }
238
- later(call_id) {
239
- let r_payload =
240
- { call_id: call_id,
241
- };
242
- let response = this.hub.newLocalMessage("msg_ffi_later", "starter", r_payload);
243
- return this.channel.publish("/local/ffi/return", response, false);
244
- }
245
- return_(fun, call_id, result) {
246
- let r_payload =
247
- { call_id: call_id,
248
- value: fun.useJsonDecoder ? result : encode(result)
249
- };
250
- let response = this.hub.newLocalMessage("msg_ffi_return", "starter", r_payload);
251
- return this.channel.publish("/local/ffi/return", response, false);
252
- }
253
- error(call_id, message, stack) {
254
- let r_payload =
255
- { call_id: call_id,
256
- message: message,
257
- stack: stack === undefined ? [] : stack,
258
- };
259
- let response = this.hub.newLocalMessage("msg_ffi_error", "starter", r_payload);
260
- this.channel.publish("/local/ffi/return", response, false);
261
- }
262
- returnOrFail(fun, call_id, result) {
263
- if (fun.canFail) {
264
- var name, arg;
265
- if (result instanceof Case) {
266
- name = result.name;
267
- arg = result.arg;
268
- } else if (fun.useJsonDecoder && result._rmx_type && result._rmx_type.startsWith("{tag}case:")) {
269
- name = result._rmx_type.substring(10);
270
- arg = result._rmx_value;
271
- } else {
272
- return this.error(call_id, "expected result from local FFI that can fail");
273
- }
274
- switch (name) {
275
- case "ok":
276
- return this.return_(fun, call_id, arg);
277
- case "error":
278
- return this.error(call_id, arg);
279
- default:
280
- return this.error(call_id, "expected result from local FFI that can fail, found case value " + name);
281
- }
282
- } else {
283
- return this.return_(fun, call_id, result);
284
- }
285
- }
286
- }
287
-
288
- // use worker.terminate() to shut a worker down!
289
-
290
- export { CreateVMID, StartWASM, StartWASM2,
291
- Token, Case, Opaque
292
- }