@manifest-configuration-language/browser 0.3.2 → 0.3.7
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 +99 -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: 3a2c487c5fa42c4e0757e6d87edafd0f36b017226737830d95e9d53e444e22c4
|
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.7",
|
|
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,91 @@ 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
|
+
} else if (priority > requestPriorities.background && operation !== "lint") {
|
|
150
|
+
supersedePending(
|
|
151
|
+
"lint",
|
|
152
|
+
requestPriorities.background,
|
|
153
|
+
"An interactive operation replaced this pending background lint",
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const queueController = new AbortController();
|
|
158
|
+
const pending = { controller: queueController, started: false };
|
|
159
|
+
if (latestOnlyOperations.has(operation)) {
|
|
160
|
+
supersedePending(operation, priority, "A newer operation replaced this request");
|
|
161
|
+
pendingGroup().set(priority, pending);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
queueSignals.add(queueController);
|
|
165
|
+
const abortWhileQueued = () => {
|
|
166
|
+
if (!pending.started) queueController.abort(signal.reason ?? abortError());
|
|
167
|
+
};
|
|
168
|
+
signal?.addEventListener("abort", abortWhileQueued, { once: true });
|
|
169
|
+
|
|
170
|
+
return queue
|
|
171
|
+
.add(
|
|
172
|
+
async () => {
|
|
173
|
+
pending.started = true;
|
|
174
|
+
if (latestOnlyOperations.has(operation)) removePending(priority, pending);
|
|
175
|
+
return run();
|
|
176
|
+
},
|
|
177
|
+
{ priority, signal: queueController.signal },
|
|
178
|
+
)
|
|
179
|
+
.finally(() => {
|
|
180
|
+
signal?.removeEventListener("abort", abortWhileQueued);
|
|
181
|
+
queueSignals.delete(queueController);
|
|
182
|
+
if (latestOnlyOperations.has(operation)) removePending(priority, pending);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function abortError() {
|
|
187
|
+
return new DOMException("The operation was aborted", "AbortError");
|
|
188
|
+
}
|
|
189
|
+
|
|
96
190
|
function startWorker() {
|
|
97
191
|
if (disposed) return Promise.reject(new Error("MCL runtime has been disposed"));
|
|
98
192
|
if (ready) return ready;
|
|
@@ -355,19 +449,7 @@ export function createMclRuntime(options = {}) {
|
|
|
355
449
|
throw error;
|
|
356
450
|
}
|
|
357
451
|
};
|
|
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
|
-
);
|
|
452
|
+
return enqueue(operation, run, signal, project?.id ?? "runtime", requestOptions.priority);
|
|
371
453
|
}
|
|
372
454
|
|
|
373
455
|
function warmUp(requestOptions = {}) {
|
|
@@ -391,18 +473,7 @@ export function createMclRuntime(options = {}) {
|
|
|
391
473
|
clearTimeout(timer);
|
|
392
474
|
}
|
|
393
475
|
};
|
|
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
|
-
);
|
|
476
|
+
return enqueue("warmUp", run, signal, "runtime", requestOptions.priority);
|
|
406
477
|
}
|
|
407
478
|
|
|
408
479
|
function openProject(files) {
|
|
@@ -595,6 +666,8 @@ export function createMclRuntime(options = {}) {
|
|
|
595
666
|
dispose() {
|
|
596
667
|
if (disposed) return;
|
|
597
668
|
disposed = true;
|
|
669
|
+
for (const controller of queueSignals)
|
|
670
|
+
controller.abort(new Error("MCL runtime has been disposed"));
|
|
598
671
|
stopCurrent?.(new Error("MCL runtime has been disposed"));
|
|
599
672
|
stopInitialization?.(new Error("MCL runtime has been disposed"));
|
|
600
673
|
worker?.terminate();
|
package/src/protocol.d.ts
CHANGED