@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 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: 96777505 bytes
4
- sha256: e072b30da32b5353eb9241c34badb3b3cf941418c99353d189a31a9620f0381e
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.2",
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
- "engines": {
24
- "node": ">=20.19"
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
- "publishConfig": {
30
- "access": "public"
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
- let queue = Promise.resolve();
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
- const result = queue.then(run, run);
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
- const result = queue.then(run, run);
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
@@ -132,6 +132,8 @@ export interface CompletionItem {
132
132
  label: string;
133
133
  kind: number;
134
134
  insertText?: string;
135
+ detail?: string;
136
+ documentation?: string;
135
137
  }
136
138
  export interface DefinitionResult {
137
139
  location: DefinitionLocation | null;