@manifest-configuration-language/browser 0.3.2 → 0.3.6
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 +1 -1
- package/dist/mcl.wasm +2 -2
- package/package.json +10 -7
- package/src/index.d.ts +2 -0
- package/src/index.js +92 -26
- package/src/protocol.d.ts +2 -0
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ try {
|
|
|
19
19
|
}
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Call `runtime.warmUp()` to load the WebAssembly runtime before the first project operation. Pass `signal` and `timeoutMs` to warm-up or an operation to cancel it or set an upper bound for that call. Warm-up's timeout and `initializationTimeoutMs` bound worker startup; an operation's `timeoutMs` bounds execution after startup. Cancelling or timing out worker startup terminates that worker; the next queued operation can create a fresh worker. Cancelling or timing out an operation terminates its worker. Operations and warm-up are serialized because the current Go runtime uses one in-memory project root.
|
|
22
|
+
Call `runtime.warmUp()` to load the WebAssembly runtime before the first project operation. Pass `signal` and `timeoutMs` to warm-up or an operation to cancel it or set an upper bound for that call. Warm-up's timeout and `initializationTimeoutMs` bound worker startup; an operation's `timeoutMs` bounds execution after startup. Cancelling or timing out worker startup terminates that worker; the next queued operation can create a fresh worker. Cancelling or timing out an operation terminates its worker. Operations and warm-up are serialized because the current Go runtime uses one in-memory project root. Set `priority: "interactive"` for user-triggered work that should move ahead of queued requests, or `priority: "background"` for work that can wait behind default-priority requests. Pending completion, hover, and lint requests supersede older requests of the same project and priority; higher-priority pending requests are retained.
|
|
23
23
|
|
|
24
24
|
Project files can be UTF-8 strings or `Uint8Array` values. UTF-8 files remain inline in project updates. The Browser SDK copies each changed `Uint8Array` and transfers its buffer to the Worker; the Worker sends only `{ "encoding": "binary" }` metadata in the update JSON and passes a path-to-`Uint8Array` map to the Go/WASM project session. The Go runtime copies those bytes into its bounded project filesystem. The SDK-owned and caller-owned arrays remain reusable after each update.
|
|
25
25
|
|
package/dist/mcl.wasm
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/mcl.wasm
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 96803351 bytes
|
|
4
|
+
sha256: 0be9e04fb79c3f19a60cf9e3bd017d1e544db1329a7a9f4b38bb78cb09d9a17e
|
package/package.json
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manifest-configuration-language/browser",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Run MCL projects in a browser Web Worker using the Go WebAssembly runtime.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
8
|
"url": "https://github.com/manifest-configuration-language/mcl.git",
|
|
8
9
|
"directory": "packages/browser"
|
|
9
10
|
},
|
|
10
|
-
"type": "module",
|
|
11
|
-
"license": "Apache-2.0",
|
|
12
11
|
"files": [
|
|
13
12
|
"dist",
|
|
14
13
|
"src",
|
|
15
14
|
"README.md"
|
|
16
15
|
],
|
|
16
|
+
"type": "module",
|
|
17
17
|
"exports": {
|
|
18
18
|
".": {
|
|
19
19
|
"types": "./src/index.d.ts",
|
|
20
20
|
"import": "./src/index.js"
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
|
-
"
|
|
24
|
-
"
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"p-queue": "9.1.2"
|
|
25
28
|
},
|
|
26
29
|
"devDependencies": {
|
|
27
30
|
"json-schema-to-typescript": "16.0.0"
|
|
28
31
|
},
|
|
29
|
-
"
|
|
30
|
-
"
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20.19"
|
|
31
34
|
},
|
|
32
35
|
"scripts": {
|
|
33
36
|
"build:wasm": "node ./scripts/build-wasm.mjs",
|
package/src/index.d.ts
CHANGED
|
@@ -159,6 +159,8 @@ export interface MclProject {
|
|
|
159
159
|
export interface RequestOptions {
|
|
160
160
|
signal?: AbortSignal;
|
|
161
161
|
timeoutMs?: number;
|
|
162
|
+
/** Queue requests ahead of default-priority work or yield to interactive requests. */
|
|
163
|
+
priority?: "interactive" | "background";
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
export interface ProjectOperationOptions extends RequestOptions {
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import PQueue from "p-queue";
|
|
1
2
|
import {
|
|
2
3
|
maxProjectBytes,
|
|
3
4
|
maxProjectFileBytes,
|
|
@@ -61,6 +62,21 @@ export function isValidProjectFileSet(paths) {
|
|
|
61
62
|
|
|
62
63
|
const defaultInitializationTimeoutMs = 30_000;
|
|
63
64
|
const defaultOperationTimeoutMs = 10_000;
|
|
65
|
+
const operationPriorities = {
|
|
66
|
+
render: 30,
|
|
67
|
+
evaluate: 30,
|
|
68
|
+
check: 30,
|
|
69
|
+
parse: 20,
|
|
70
|
+
format: 20,
|
|
71
|
+
rename: 20,
|
|
72
|
+
references: 20,
|
|
73
|
+
definition: 20,
|
|
74
|
+
completion: 20,
|
|
75
|
+
hover: 20,
|
|
76
|
+
lint: 10,
|
|
77
|
+
};
|
|
78
|
+
const latestOnlyOperations = new Set(["completion", "hover", "lint"]);
|
|
79
|
+
const requestPriorities = { interactive: 40, background: 0 };
|
|
64
80
|
const wasmURL = new URL("../dist/mcl.wasm", import.meta.url).href;
|
|
65
81
|
const wasmExecURL = new URL("../dist/wasm_exec.js", import.meta.url).href;
|
|
66
82
|
|
|
@@ -86,13 +102,84 @@ export function createMclRuntime(options = {}) {
|
|
|
86
102
|
let worker;
|
|
87
103
|
let ready;
|
|
88
104
|
let disposed = false;
|
|
89
|
-
|
|
105
|
+
const queue = new PQueue({ concurrency: 1 });
|
|
106
|
+
queue.on("error", () => {});
|
|
107
|
+
const queueSignals = new Set();
|
|
108
|
+
const latestPending = new Map();
|
|
90
109
|
let sequence = 0;
|
|
91
110
|
let workerGeneration = 0;
|
|
92
111
|
let workerProject;
|
|
93
112
|
let stopCurrent;
|
|
94
113
|
let stopInitialization;
|
|
95
114
|
|
|
115
|
+
function enqueue(operation, run, signal, scope = "runtime", requestedPriority) {
|
|
116
|
+
if (signal?.aborted) return Promise.reject(signal.reason ?? abortError());
|
|
117
|
+
const priority =
|
|
118
|
+
requestPriorities[requestedPriority] ?? operationPriorities[operation] ?? 0;
|
|
119
|
+
const key = (name) => JSON.stringify([scope, name]);
|
|
120
|
+
const pendingKey = key(operation);
|
|
121
|
+
const pendingGroup = () => {
|
|
122
|
+
let group = latestPending.get(pendingKey);
|
|
123
|
+
if (!group) {
|
|
124
|
+
group = new Map();
|
|
125
|
+
latestPending.set(pendingKey, group);
|
|
126
|
+
}
|
|
127
|
+
return group;
|
|
128
|
+
};
|
|
129
|
+
const removePending = (pendingPriority, pending) => {
|
|
130
|
+
const group = latestPending.get(pendingKey);
|
|
131
|
+
if (group?.get(pendingPriority) !== pending) return;
|
|
132
|
+
group.delete(pendingPriority);
|
|
133
|
+
if (group.size === 0) latestPending.delete(pendingKey);
|
|
134
|
+
};
|
|
135
|
+
const supersedePending = (name, maximumPriority, message) => {
|
|
136
|
+
const groupKey = key(name);
|
|
137
|
+
const group = latestPending.get(groupKey);
|
|
138
|
+
if (!group) return;
|
|
139
|
+
for (const [pendingPriority, pending] of group) {
|
|
140
|
+
if (pendingPriority > maximumPriority) continue;
|
|
141
|
+
group.delete(pendingPriority);
|
|
142
|
+
pending.controller.abort(new DOMException(message, "AbortError"));
|
|
143
|
+
}
|
|
144
|
+
if (group.size === 0) latestPending.delete(groupKey);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (["render", "evaluate", "check"].includes(operation))
|
|
148
|
+
supersedePending("lint", priority, "An explicit run replaced this pending lint");
|
|
149
|
+
|
|
150
|
+
const queueController = new AbortController();
|
|
151
|
+
const pending = { controller: queueController, started: false };
|
|
152
|
+
if (latestOnlyOperations.has(operation)) {
|
|
153
|
+
supersedePending(operation, priority, "A newer operation replaced this request");
|
|
154
|
+
pendingGroup().set(priority, pending);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
queueSignals.add(queueController);
|
|
158
|
+
const abortWhileQueued = () => {
|
|
159
|
+
if (!pending.started) queueController.abort(signal.reason ?? abortError());
|
|
160
|
+
};
|
|
161
|
+
signal?.addEventListener("abort", abortWhileQueued, { once: true });
|
|
162
|
+
|
|
163
|
+
return queue
|
|
164
|
+
.add(
|
|
165
|
+
async () => {
|
|
166
|
+
pending.started = true;
|
|
167
|
+
if (latestOnlyOperations.has(operation)) removePending(priority, pending);
|
|
168
|
+
return run();
|
|
169
|
+
},
|
|
170
|
+
{ priority, signal: queueController.signal },
|
|
171
|
+
)
|
|
172
|
+
.finally(() => {
|
|
173
|
+
signal?.removeEventListener("abort", abortWhileQueued);
|
|
174
|
+
queueSignals.delete(queueController);
|
|
175
|
+
if (latestOnlyOperations.has(operation)) removePending(priority, pending);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function abortError() {
|
|
180
|
+
return new DOMException("The operation was aborted", "AbortError");
|
|
181
|
+
}
|
|
182
|
+
|
|
96
183
|
function startWorker() {
|
|
97
184
|
if (disposed) return Promise.reject(new Error("MCL runtime has been disposed"));
|
|
98
185
|
if (ready) return ready;
|
|
@@ -355,19 +442,7 @@ export function createMclRuntime(options = {}) {
|
|
|
355
442
|
throw error;
|
|
356
443
|
}
|
|
357
444
|
};
|
|
358
|
-
|
|
359
|
-
queue = result.catch(() => {});
|
|
360
|
-
if (!signal) return result;
|
|
361
|
-
|
|
362
|
-
let onAbort;
|
|
363
|
-
const aborted = new Promise((_, reject) => {
|
|
364
|
-
onAbort = () => reject(new DOMException("The operation was aborted", "AbortError"));
|
|
365
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
366
|
-
if (signal.aborted) onAbort();
|
|
367
|
-
});
|
|
368
|
-
return Promise.race([result, aborted]).finally(() =>
|
|
369
|
-
signal.removeEventListener("abort", onAbort),
|
|
370
|
-
);
|
|
445
|
+
return enqueue(operation, run, signal, project?.id ?? "runtime", requestOptions.priority);
|
|
371
446
|
}
|
|
372
447
|
|
|
373
448
|
function warmUp(requestOptions = {}) {
|
|
@@ -391,18 +466,7 @@ export function createMclRuntime(options = {}) {
|
|
|
391
466
|
clearTimeout(timer);
|
|
392
467
|
}
|
|
393
468
|
};
|
|
394
|
-
|
|
395
|
-
queue = result.catch(() => {});
|
|
396
|
-
if (!signal) return result;
|
|
397
|
-
let onAbort;
|
|
398
|
-
const aborted = new Promise((_, reject) => {
|
|
399
|
-
onAbort = () => reject(new DOMException("The operation was aborted", "AbortError"));
|
|
400
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
401
|
-
if (signal.aborted) onAbort();
|
|
402
|
-
});
|
|
403
|
-
return Promise.race([result, aborted]).finally(() =>
|
|
404
|
-
signal.removeEventListener("abort", onAbort),
|
|
405
|
-
);
|
|
469
|
+
return enqueue("warmUp", run, signal, "runtime", requestOptions.priority);
|
|
406
470
|
}
|
|
407
471
|
|
|
408
472
|
function openProject(files) {
|
|
@@ -595,6 +659,8 @@ export function createMclRuntime(options = {}) {
|
|
|
595
659
|
dispose() {
|
|
596
660
|
if (disposed) return;
|
|
597
661
|
disposed = true;
|
|
662
|
+
for (const controller of queueSignals)
|
|
663
|
+
controller.abort(new Error("MCL runtime has been disposed"));
|
|
598
664
|
stopCurrent?.(new Error("MCL runtime has been disposed"));
|
|
599
665
|
stopInitialization?.(new Error("MCL runtime has been disposed"));
|
|
600
666
|
worker?.terminate();
|
package/src/protocol.d.ts
CHANGED