wstp-node 0.4.5 → 0.6.0
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 +32 -2
- package/build/Release/wstp.node +0 -0
- package/index.d.ts +134 -0
- package/package.json +1 -1
- package/scripts/diagnose-windows.ps1 +4 -2
- package/scripts/wstp_dir.js +72 -50
- package/src/addon.cc +906 -172
- package/test.js +492 -30
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ const { WstpSession, WstpReader, setDiagHandler } = require('./build/Release/wst
|
|
|
30
30
|
- [`createSubsession(kernelPath?)`](#createsubsessionkernelpath) — spawn an independent parallel kernel session
|
|
31
31
|
- [`close()`](#close) — gracefully shut down the kernel and free resources
|
|
32
32
|
- [`isOpen` / `isDialogOpen`](#isopen--isdialogopen) — read-only status flags
|
|
33
|
+
- [Dynamic eval API](#dynamic-eval-api) — register expressions for automatic periodic evaluation
|
|
33
34
|
5. [`WstpReader` — kernel-pushed side channel](#wstpreader)
|
|
34
35
|
6. [`setDiagHandler(fn)`](#setdiaghandlerfn)
|
|
35
36
|
5. [Usage examples](#usage-examples)
|
|
@@ -97,7 +98,7 @@ The script automatically locates the WSTP SDK inside the default Wolfram install
|
|
|
97
98
|
node test.js
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
Expected last line: `All
|
|
101
|
+
Expected last line: `All 52 tests passed.`
|
|
101
102
|
|
|
102
103
|
A more comprehensive suite (both modes + In/Out + comparison) lives in `tmp/tests_all.js`:
|
|
103
104
|
|
|
@@ -105,7 +106,7 @@ A more comprehensive suite (both modes + In/Out + comparison) lives in `tmp/test
|
|
|
105
106
|
node tmp/tests_all.js
|
|
106
107
|
```
|
|
107
108
|
|
|
108
|
-
Expected last line: `All
|
|
109
|
+
Expected last line: `All 41 tests passed.`
|
|
109
110
|
|
|
110
111
|
### 5. Quick smoke test
|
|
111
112
|
|
|
@@ -487,6 +488,35 @@ session.isDialogOpen: boolean // true while inside a Dialog[] subsession
|
|
|
487
488
|
|
|
488
489
|
---
|
|
489
490
|
|
|
491
|
+
### Dynamic eval API
|
|
492
|
+
|
|
493
|
+
Register Wolfram Language expressions for automatic periodic evaluation during
|
|
494
|
+
cell computations. A `RunScheduledTask` in the kernel periodically calls
|
|
495
|
+
`Dialog[]`; the C++ layer intercepts each `BEGINDLGPKT`, evaluates all
|
|
496
|
+
registered expressions inline, stores the results, and closes the dialog.
|
|
497
|
+
|
|
498
|
+
| Method | Description |
|
|
499
|
+
|--------|-------------|
|
|
500
|
+
| `registerDynamic(id, expr)` | Register (or upsert) an expression for periodic evaluation |
|
|
501
|
+
| `unregisterDynamic(id)` | Remove one registration by id |
|
|
502
|
+
| `clearDynamicRegistry()` | Remove all registrations and clear results buffer |
|
|
503
|
+
| `getDynamicResults()` | Return `Record<string, DynResult>` and clear buffer |
|
|
504
|
+
| `setDynamicInterval(ms)` | Set the ScheduledTask period (0 = off) |
|
|
505
|
+
| `setDynAutoMode(auto)` | `true` (default) = C++ handles dialogs; `false` = legacy JS path |
|
|
506
|
+
| `dynamicActive` | Read-only: `true` when registry is non-empty and interval > 0 |
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
interface DynResult {
|
|
510
|
+
value: string; // string-form result
|
|
511
|
+
timestamp: number; // Unix timestamp (ms)
|
|
512
|
+
error?: string; // set if evaluation failed
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
See [API.md](API.md#dynamic-eval-api) for full documentation.
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
490
520
|
## `WstpReader`
|
|
491
521
|
|
|
492
522
|
A reader that **connects to** a named WSTP link created by the kernel (via `LinkCreate`)
|
package/build/Release/wstp.node
CHANGED
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -77,6 +77,37 @@ export interface EvalOptions {
|
|
|
77
77
|
* @param level The nesting depth that just closed.
|
|
78
78
|
*/
|
|
79
79
|
onDialogEnd?: (level: number) => void;
|
|
80
|
+
/**
|
|
81
|
+
* When `true`, any `BEGINDLGPKT` received during a non-interactive
|
|
82
|
+
* evaluation is automatically closed in C++ without informing the JS
|
|
83
|
+
* layer. Use for `VsCodeRender`, handler-install, and `sub()` calls
|
|
84
|
+
* that must never block on a Dialog[] (prevents Pattern C deadlocks).
|
|
85
|
+
*/
|
|
86
|
+
rejectDialog?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* One captured Dynamic[] result returned by `getDynamicResults()`.
|
|
91
|
+
*/
|
|
92
|
+
export interface DynResult {
|
|
93
|
+
/** String-form result returned by the kernel for this expression. */
|
|
94
|
+
value: string;
|
|
95
|
+
/** Unix timestamp (ms) when this result was evaluated. */
|
|
96
|
+
timestamp: number;
|
|
97
|
+
/** Set if evaluation failed (e.g. timeout, `$Failed`). */
|
|
98
|
+
error?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Optional options for subWhenIdle().
|
|
103
|
+
*/
|
|
104
|
+
export interface SubWhenIdleOptions {
|
|
105
|
+
/**
|
|
106
|
+
* Maximum number of milliseconds to wait in the queue before the
|
|
107
|
+
* returned Promise rejects with a timeout error.
|
|
108
|
+
* Omit or set to 0 for no timeout (wait indefinitely).
|
|
109
|
+
*/
|
|
110
|
+
timeout?: number;
|
|
80
111
|
}
|
|
81
112
|
|
|
82
113
|
/**
|
|
@@ -202,6 +233,36 @@ export class WstpSession {
|
|
|
202
233
|
*/
|
|
203
234
|
sub(expr: string): Promise<WExpr>;
|
|
204
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Queue a background evaluation that runs only when the kernel is truly idle.
|
|
238
|
+
*
|
|
239
|
+
* Unlike `sub()`, which runs *before* any queued `evaluate()` calls,
|
|
240
|
+
* `subWhenIdle()` runs only *after* ALL pending `evaluate()` and `sub()`
|
|
241
|
+
* calls have completed. This makes it safe for background queries
|
|
242
|
+
* (e.g. global-symbol coloring, auto-complete) that must not compete with
|
|
243
|
+
* active cell evaluations.
|
|
244
|
+
*
|
|
245
|
+
* If the kernel is idle at call time the query starts immediately;
|
|
246
|
+
* otherwise it is queued and executes as soon as the kernel becomes
|
|
247
|
+
* fully idle again. Multiple `subWhenIdle()` calls are executed FIFO.
|
|
248
|
+
*
|
|
249
|
+
* If the session is closed while the request is still queued the Promise
|
|
250
|
+
* rejects with `"Session is closed"`.
|
|
251
|
+
*
|
|
252
|
+
* @param expr Wolfram Language expression string to evaluate.
|
|
253
|
+
* @param opts Optional: `{ timeout?: number }` — ms before the Promise
|
|
254
|
+
* rejects with `"subWhenIdle: timeout"` if the kernel has
|
|
255
|
+
* not become idle within that window.
|
|
256
|
+
* @returns Promise that resolves with the WExpr result,
|
|
257
|
+
* identical in shape to the value returned by `sub()`.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* session.subWhenIdle('Names["Global`*"]').then(result => {
|
|
261
|
+
* // safe background query — never races with evaluate()
|
|
262
|
+
* });
|
|
263
|
+
*/
|
|
264
|
+
subWhenIdle(expr: string, opts?: SubWhenIdleOptions): Promise<WExpr>;
|
|
265
|
+
|
|
205
266
|
/**
|
|
206
267
|
* Launch an independent child kernel as a new WstpSession.
|
|
207
268
|
*
|
|
@@ -220,6 +281,79 @@ export class WstpSession {
|
|
|
220
281
|
|
|
221
282
|
/** True while the link is open and the kernel is running. */
|
|
222
283
|
readonly isOpen: boolean;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* The OS process ID of the WolframKernel child process.
|
|
287
|
+
*
|
|
288
|
+
* Useful for external monitoring or force-terminating a stale kernel
|
|
289
|
+
* after a restart. Returns `0` if the PID could not be determined
|
|
290
|
+
* at session-construction time (rare, non-fatal fallback).
|
|
291
|
+
*
|
|
292
|
+
* The PID is captured once during `new WstpSession()` and does not
|
|
293
|
+
* change; it remains set even after `close()` so callers can reference
|
|
294
|
+
* it in cleanup paths.
|
|
295
|
+
*/
|
|
296
|
+
readonly kernelPid: number;
|
|
297
|
+
|
|
298
|
+
// ── Dynamic eval API ────────────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Register (or update) a Wolfram Language expression to be evaluated
|
|
302
|
+
* automatically every time the kernel enters a Dialog[] interrupt.
|
|
303
|
+
*
|
|
304
|
+
* @param id Unique identifier for this registration (e.g. `"x"`).
|
|
305
|
+
* @param expr Wolfram Language expression string (e.g. `"ToString[x]"`).
|
|
306
|
+
*/
|
|
307
|
+
registerDynamic(id: string, expr: string): void;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Remove a previously registered Dynamic expression by id.
|
|
311
|
+
* Has no effect if the id was never registered.
|
|
312
|
+
*/
|
|
313
|
+
unregisterDynamic(id: string): void;
|
|
314
|
+
|
|
315
|
+
/** Remove all registered Dynamic expressions. */
|
|
316
|
+
clearDynamicRegistry(): void;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Consume and return all results accumulated since the last call.
|
|
320
|
+
*
|
|
321
|
+
* Each key is a registration `id`; each value is the most recent
|
|
322
|
+
* `DynResult` for that expression. Calling this clears the internal
|
|
323
|
+
* buffer so subsequent calls return only new results.
|
|
324
|
+
*
|
|
325
|
+
* @returns A plain object mapping id → DynResult.
|
|
326
|
+
*/
|
|
327
|
+
getDynamicResults(): Record<string, DynResult>;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Set the auto-interrupt period (in ms) for the Dynamic timer thread.
|
|
331
|
+
*
|
|
332
|
+
* The background thread sends `WSInterruptMessage` to the kernel every
|
|
333
|
+
* `ms` milliseconds while an evaluation is in progress and the registry
|
|
334
|
+
* is non-empty. Set to `0` to disable.
|
|
335
|
+
*
|
|
336
|
+
* @param ms Interval in milliseconds (0 = off).
|
|
337
|
+
*/
|
|
338
|
+
setDynamicInterval(ms: number): void;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Switch the Dialog[] handling mode.
|
|
342
|
+
*
|
|
343
|
+
* - `true` (default): C++ intercepts every `BEGINDLGPKT` and evaluates
|
|
344
|
+
* all registered expressions inline — no JS round-trip required.
|
|
345
|
+
* - `false`: Falls back to the legacy JS callback path
|
|
346
|
+
* (`onDialogBegin` / `dialogEval` / `exitDialog`).
|
|
347
|
+
*
|
|
348
|
+
* @param auto `true` to enable automatic C++ handling, `false` for legacy.
|
|
349
|
+
*/
|
|
350
|
+
setDynAutoMode(auto: boolean): void;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* `true` when the Dynamic registry is non-empty **and** the timer
|
|
354
|
+
* interval is greater than zero (i.e. auto-interrupts are active).
|
|
355
|
+
*/
|
|
356
|
+
readonly dynamicActive: boolean;
|
|
223
357
|
}
|
|
224
358
|
|
|
225
359
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wstp-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Native Node.js addon for Wolfram/Mathematica WSTP — kernel sessions with evaluation queue, streaming Print/messages, Dialog subsessions, and side-channel WstpReader",
|
|
5
5
|
"main": "build/Release/wstp.node",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -13,8 +13,10 @@ $files = @("wstp.h", "wstp64i4s.lib")
|
|
|
13
13
|
$searchRoots = @(
|
|
14
14
|
"C:\Program Files\Wolfram Research",
|
|
15
15
|
"C:\Program Files (x86)\Wolfram Research",
|
|
16
|
-
"$env:LOCALAPPDATA\Programs\Wolfram Research"
|
|
17
|
-
|
|
16
|
+
"$env:LOCALAPPDATA\Programs\Wolfram Research",
|
|
17
|
+
"C:\Program Files\Wolfram Research\Mathematica",
|
|
18
|
+
"C:\Program Files\Wolfram Research\Wolfram Engine"
|
|
19
|
+
) | Where-Object { (Test-Path $_) -and -not (Test-Path (Join-Path $_ "wsl.exe")) }
|
|
18
20
|
|
|
19
21
|
$found = @{}
|
|
20
22
|
|
package/scripts/wstp_dir.js
CHANGED
|
@@ -22,67 +22,89 @@ function resolve () {
|
|
|
22
22
|
|
|
23
23
|
// ── 2. Windows ──────────────────────────────────────────────────────────
|
|
24
24
|
if (process.platform === 'win32') {
|
|
25
|
-
const
|
|
25
|
+
const pf = process.env.PROGRAMFILES || 'C:\\Program Files';
|
|
26
|
+
const pf86 = process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)';
|
|
27
|
+
const local = process.env.LOCALAPPDATA || '';
|
|
26
28
|
|
|
27
|
-
//
|
|
28
|
-
const
|
|
29
|
-
'Wolfram Research
|
|
30
|
-
'Wolfram Research
|
|
31
|
-
'Wolfram Research
|
|
32
|
-
];
|
|
29
|
+
// All candidate root directories to search under
|
|
30
|
+
const searchRoots = [
|
|
31
|
+
path.join(pf, 'Wolfram Research'),
|
|
32
|
+
path.join(pf86, 'Wolfram Research'),
|
|
33
|
+
local ? path.join(local, 'Programs', 'Wolfram Research') : null,
|
|
34
|
+
].filter(Boolean);
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
36
|
+
// Product subfolder names to try
|
|
37
|
+
const productNames = ['Mathematica', 'Wolfram Engine', 'WolframEngine'];
|
|
38
|
+
|
|
39
|
+
for (const root of searchRoots) {
|
|
40
|
+
let rootEntries;
|
|
41
|
+
try { rootEntries = fs.readdirSync(root); } catch (e) { continue; }
|
|
42
|
+
|
|
43
|
+
for (const product of productNames) {
|
|
44
|
+
const productDir = path.join(root, product);
|
|
45
|
+
let entries;
|
|
46
|
+
try { entries = fs.readdirSync(productDir); } catch (e) { continue; }
|
|
47
|
+
|
|
48
|
+
// Skip if this looks like WSL rather than a Wolfram product
|
|
49
|
+
if (entries.includes('wsl.exe') || entries.includes('wslservice.exe')) {
|
|
50
|
+
process.stderr.write(
|
|
51
|
+
`wstp_dir: skipping "${productDir}" — looks like WSL, not Wolfram\n`
|
|
52
|
+
);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
56
|
+
// Find version subfolders (e.g. "14.2", "13.1") — dirs starting with a digit
|
|
57
|
+
const versions = entries
|
|
58
|
+
.filter(d => {
|
|
59
|
+
if (!/^\d/.test(d)) return false;
|
|
60
|
+
try { return fs.statSync(path.join(productDir, d)).isDirectory(); } catch (e) { return false; }
|
|
61
|
+
})
|
|
62
|
+
.sort()
|
|
63
|
+
.reverse();
|
|
64
|
+
|
|
65
|
+
if (!versions.length) {
|
|
66
|
+
process.stderr.write(
|
|
67
|
+
`wstp_dir: "${productDir}" has no version subfolder — skipping\n` +
|
|
68
|
+
` Contents: ${entries.join(', ')}\n`
|
|
69
|
+
);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const wstp = path.join(
|
|
74
|
+
productDir, versions[0],
|
|
75
|
+
'SystemFiles', 'Links', 'WSTP', 'DeveloperKit',
|
|
76
|
+
'Windows-x86-64', 'CompilerAdditions'
|
|
70
77
|
);
|
|
71
|
-
|
|
78
|
+
|
|
79
|
+
const hasHeader = fs.existsSync(path.join(wstp, 'wstp.h'));
|
|
80
|
+
const hasLib = fs.existsSync(path.join(wstp, 'wstp64i4s.lib'));
|
|
81
|
+
|
|
82
|
+
if (!hasHeader || !hasLib) {
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
`wstp_dir: found version "${versions[0]}" but WSTP DeveloperKit missing.\n` +
|
|
85
|
+
` Expected both files inside:\n` +
|
|
86
|
+
` ${wstp}\n` +
|
|
87
|
+
` wstp.h (C header) — ${hasHeader ? 'FOUND' : 'MISSING'}\n` +
|
|
88
|
+
` wstp64i4s.lib (import lib) — ${hasLib ? 'FOUND' : 'MISSING'}\n`
|
|
89
|
+
);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return wstp;
|
|
72
94
|
}
|
|
73
|
-
return wstp;
|
|
74
95
|
}
|
|
75
96
|
|
|
76
97
|
throw new Error(
|
|
77
|
-
`WSTP DeveloperKit not found.\n` +
|
|
78
|
-
`
|
|
79
|
-
`The build needs these two files:\n` +
|
|
98
|
+
`WSTP DeveloperKit not found on Windows.\n\n` +
|
|
99
|
+
`The build requires two files from the Wolfram Engine / Mathematica installation:\n` +
|
|
80
100
|
` wstp.h (C header)\n` +
|
|
81
101
|
` wstp64i4s.lib (static import library)\n\n` +
|
|
82
|
-
`
|
|
102
|
+
`These live inside:\n` +
|
|
103
|
+
` <WolframEngine>\\<version>\\SystemFiles\\Links\\WSTP\\DeveloperKit\\Windows-x86-64\\CompilerAdditions\\\n\n` +
|
|
104
|
+
`Run the diagnostic to find them:\n` +
|
|
83
105
|
` powershell -ExecutionPolicy Bypass -File scripts\\diagnose-windows.ps1\n\n` +
|
|
84
|
-
`Then set WSTP_DIR
|
|
85
|
-
` set WSTP_DIR
|
|
106
|
+
`Then set WSTP_DIR and retry:\n` +
|
|
107
|
+
` set WSTP_DIR=<path shown above>\n` +
|
|
86
108
|
` npm install`
|
|
87
109
|
);
|
|
88
110
|
}
|