@danielsimonjr/mathts-functions 0.1.2 → 0.2.1

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.
@@ -0,0 +1,644 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/wasm/integrity.ts
8
+ var ALGO = "sha384";
9
+ async function sha384OfBuffer(buffer) {
10
+ const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
11
+ const isNode = typeof process !== "undefined" && process.versions?.node !== void 0;
12
+ let digest;
13
+ if (isNode) {
14
+ const { createHash } = await import("crypto");
15
+ const hash = createHash(ALGO);
16
+ hash.update(bytes);
17
+ digest = new Uint8Array(hash.digest());
18
+ } else {
19
+ const copy = new ArrayBuffer(bytes.byteLength);
20
+ new Uint8Array(copy).set(bytes);
21
+ const out = await crypto.subtle.digest("SHA-384", copy);
22
+ digest = new Uint8Array(out);
23
+ }
24
+ let binary = "";
25
+ for (let i = 0; i < digest.length; i++) binary += String.fromCharCode(digest[i]);
26
+ const b64 = typeof btoa === "function" ? btoa(binary) : Buffer.from(digest).toString("base64");
27
+ return `sha384-${b64}`;
28
+ }
29
+ async function loadWasmManifest(wasmPath) {
30
+ const isNode = typeof process !== "undefined" && process.versions?.node !== void 0;
31
+ const manifestPath = wasmPath.replace(/[^/\\]+$/, "wasm-manifest.json");
32
+ try {
33
+ if (isNode) {
34
+ const fs = await import("fs");
35
+ const { promisify } = await import("util");
36
+ const readFile = promisify(fs.readFile);
37
+ const text = await readFile(manifestPath, "utf8");
38
+ return JSON.parse(text);
39
+ } else {
40
+ const res = await fetch(manifestPath);
41
+ if (!res.ok) return null;
42
+ return await res.json();
43
+ }
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ async function verifyWasmIntegrity(buffer, wasmPath, options = {}) {
49
+ const fileName = wasmPath.split(/[/\\]/).pop() || wasmPath;
50
+ const manifest = options.manifest !== void 0 ? options.manifest : await loadWasmManifest(wasmPath);
51
+ if (!manifest) {
52
+ if (options.required) {
53
+ throw new Error(
54
+ `WASM integrity check failed: no manifest at sibling of "${wasmPath}" (required)`
55
+ );
56
+ }
57
+ if (typeof console !== "undefined") {
58
+ console.warn(
59
+ `[wasm-integrity] no manifest found beside "${wasmPath}"; skipping SHA-384 verification (set options.required=true to fail closed)`
60
+ );
61
+ }
62
+ return;
63
+ }
64
+ const expected = manifest[fileName];
65
+ if (!expected) {
66
+ if (options.required) {
67
+ throw new Error(`WASM integrity check failed: manifest has no entry for "${fileName}"`);
68
+ }
69
+ return;
70
+ }
71
+ const actual = await sha384OfBuffer(buffer);
72
+ if (actual !== expected) {
73
+ throw new Error(
74
+ `WASM integrity check failed for "${fileName}": expected ${expected}, got ${actual}`
75
+ );
76
+ }
77
+ }
78
+
79
+ // src/wasm/WasmLoader.ts
80
+ var WasmLoader = class _WasmLoader {
81
+ static instance = null;
82
+ wasmModule = null;
83
+ compiledModule = null;
84
+ loading = null;
85
+ isNode;
86
+ lastMetrics = null;
87
+ // Memory pool for reusable allocations
88
+ float64Pool = [];
89
+ int32Pool = [];
90
+ maxPoolSize = 32;
91
+ poolSizeThreshold = 1024 * 1024;
92
+ // 1MB max per pool entry
93
+ constructor() {
94
+ this.isNode = typeof process !== "undefined" && process.versions?.node !== void 0;
95
+ }
96
+ static getInstance() {
97
+ if (!_WasmLoader.instance) {
98
+ _WasmLoader.instance = new _WasmLoader();
99
+ }
100
+ return _WasmLoader.instance;
101
+ }
102
+ /**
103
+ * Load the WASM module
104
+ */
105
+ async load(wasmPath) {
106
+ if (this.wasmModule) {
107
+ return this.wasmModule;
108
+ }
109
+ if (this.loading) {
110
+ return this.loading;
111
+ }
112
+ this.loading = this.loadModule(wasmPath);
113
+ this.wasmModule = await this.loading;
114
+ return this.wasmModule;
115
+ }
116
+ /**
117
+ * Precompile the WASM module without instantiation
118
+ * Useful for build-time or startup optimization
119
+ */
120
+ async precompile(wasmPath) {
121
+ if (this.compiledModule) return;
122
+ const path = wasmPath || await this.getDefaultWasmPath();
123
+ const startTime = performance.now();
124
+ if (this.isNode) {
125
+ const fs = await import("fs");
126
+ const { promisify } = await import("util");
127
+ const readFile = promisify(fs.readFile);
128
+ const buffer = await readFile(path);
129
+ await verifyWasmIntegrity(buffer, path);
130
+ this.compiledModule = await WebAssembly.compile(buffer);
131
+ } else {
132
+ const manifest = await loadWasmManifest(path);
133
+ if (!manifest && typeof WebAssembly.compileStreaming === "function") {
134
+ this.compiledModule = await WebAssembly.compileStreaming(fetch(path));
135
+ } else {
136
+ const response = await fetch(path);
137
+ const buffer = await response.arrayBuffer();
138
+ await verifyWasmIntegrity(buffer, path, { manifest });
139
+ this.compiledModule = await WebAssembly.compile(buffer);
140
+ }
141
+ }
142
+ this.lastMetrics = {
143
+ fileReadMs: 0,
144
+ compileMs: performance.now() - startTime,
145
+ instantiateMs: 0,
146
+ totalMs: performance.now() - startTime,
147
+ fromCache: false
148
+ };
149
+ }
150
+ async loadModule(wasmPath) {
151
+ const path = wasmPath || await this.getDefaultWasmPath();
152
+ const totalStart = performance.now();
153
+ if (this.compiledModule) {
154
+ const instStart = performance.now();
155
+ const instance = await WebAssembly.instantiate(this.compiledModule, this.getImports());
156
+ this.lastMetrics = {
157
+ fileReadMs: 0,
158
+ compileMs: 0,
159
+ instantiateMs: performance.now() - instStart,
160
+ totalMs: performance.now() - totalStart,
161
+ fromCache: true
162
+ };
163
+ return instance.exports;
164
+ }
165
+ if (this.isNode) {
166
+ return this.loadNodeWasm(path, totalStart);
167
+ } else {
168
+ return this.loadBrowserWasm(path, totalStart);
169
+ }
170
+ }
171
+ /**
172
+ * Get the WASM binary path based on the selected backend.
173
+ * Set MATHTS_WASM_BACKEND=assemblyscript to use the AS binary.
174
+ * Default is Rust (after migration cutover).
175
+ *
176
+ * Filenames were renamed from mathjs.wasm / mathjs-as.wasm during the
177
+ * mathjs-to-MathTS rebrand; the legacy MATHJS_WASM_BACKEND env var
178
+ * still works for one release for backward compatibility.
179
+ *
180
+ * Returns an async result because the Node branch dynamically imports
181
+ * `node:url` (fileURLToPath) so the path is resolved relative to this
182
+ * source file's location rather than process.cwd().
183
+ */
184
+ async getDefaultWasmPath() {
185
+ const useAS = typeof process !== "undefined" && (process.env?.MATHTS_WASM_BACKEND === "assemblyscript" || process.env?.MATHJS_WASM_BACKEND === "assemblyscript");
186
+ const wasmFile = useAS ? "mathts-as.wasm" : "mathts.wasm";
187
+ const resolvedUrl = new URL(`../../../lib/wasm/${wasmFile}`, import.meta.url);
188
+ if (this.isNode) {
189
+ const { fileURLToPath } = await import("url");
190
+ return fileURLToPath(resolvedUrl);
191
+ }
192
+ return resolvedUrl.href;
193
+ }
194
+ async loadNodeWasm(path, totalStart) {
195
+ const fs = await import("fs");
196
+ const { promisify } = await import("util");
197
+ const readFile = promisify(fs.readFile);
198
+ const readStart = performance.now();
199
+ const buffer = await readFile(path);
200
+ const readEnd = performance.now();
201
+ await verifyWasmIntegrity(buffer, path);
202
+ const compileStart = performance.now();
203
+ this.compiledModule = await WebAssembly.compile(buffer);
204
+ const compileEnd = performance.now();
205
+ const instStart = performance.now();
206
+ const instance = await WebAssembly.instantiate(this.compiledModule, this.getImports());
207
+ const instEnd = performance.now();
208
+ this.lastMetrics = {
209
+ fileReadMs: readEnd - readStart,
210
+ compileMs: compileEnd - compileStart,
211
+ instantiateMs: instEnd - instStart,
212
+ totalMs: performance.now() - totalStart,
213
+ fromCache: false
214
+ };
215
+ return instance.exports;
216
+ }
217
+ async loadBrowserWasm(path, totalStart) {
218
+ const manifest = await loadWasmManifest(path);
219
+ if (!manifest && typeof WebAssembly.instantiateStreaming === "function") {
220
+ const instStart2 = performance.now();
221
+ const result = await WebAssembly.instantiateStreaming(fetch(path), this.getImports());
222
+ this.compiledModule = result.module;
223
+ this.lastMetrics = {
224
+ fileReadMs: 0,
225
+ // Combined with compile in streaming
226
+ compileMs: 0,
227
+ // Combined in streaming
228
+ instantiateMs: performance.now() - instStart2,
229
+ totalMs: performance.now() - totalStart,
230
+ fromCache: false
231
+ };
232
+ return result.instance.exports;
233
+ }
234
+ const readStart = performance.now();
235
+ const response = await fetch(path);
236
+ const buffer = await response.arrayBuffer();
237
+ const readEnd = performance.now();
238
+ await verifyWasmIntegrity(buffer, path, { manifest });
239
+ const compileStart = performance.now();
240
+ this.compiledModule = await WebAssembly.compile(buffer);
241
+ const compileEnd = performance.now();
242
+ const instStart = performance.now();
243
+ const instance = await WebAssembly.instantiate(this.compiledModule, this.getImports());
244
+ const instEnd = performance.now();
245
+ this.lastMetrics = {
246
+ fileReadMs: readEnd - readStart,
247
+ compileMs: compileEnd - compileStart,
248
+ instantiateMs: instEnd - instStart,
249
+ totalMs: performance.now() - totalStart,
250
+ fromCache: false
251
+ };
252
+ return instance.exports;
253
+ }
254
+ getImports() {
255
+ return {
256
+ env: {
257
+ abort: (msg, file, line, column) => {
258
+ console.error("WASM abort", { msg, file, line, column });
259
+ throw new Error("WASM abort");
260
+ },
261
+ seed: () => Date.now()
262
+ },
263
+ Math,
264
+ Date
265
+ };
266
+ }
267
+ /**
268
+ * Get the loaded WASM module
269
+ */
270
+ getModule() {
271
+ return this.wasmModule;
272
+ }
273
+ /**
274
+ * Get the compiled WASM module (for caching/serialization)
275
+ */
276
+ getCompiledModule() {
277
+ return this.compiledModule;
278
+ }
279
+ /**
280
+ * Check if WASM is loaded
281
+ */
282
+ isLoaded() {
283
+ return this.wasmModule !== null;
284
+ }
285
+ /**
286
+ * Check if WASM is precompiled
287
+ */
288
+ isPrecompiled() {
289
+ return this.compiledModule !== null;
290
+ }
291
+ /**
292
+ * Get loading performance metrics
293
+ */
294
+ getLoadingMetrics() {
295
+ return this.lastMetrics;
296
+ }
297
+ /**
298
+ * Allocate Float64Array in WASM memory
299
+ * Uses memory pooling for frequently reused sizes
300
+ */
301
+ allocateFloat64Array(data) {
302
+ const module = this.wasmModule;
303
+ if (!module) throw new Error("WASM module not loaded");
304
+ const length = data.length;
305
+ const byteLength = length * 8;
306
+ let ptr;
307
+ if (byteLength <= this.poolSizeThreshold) {
308
+ const poolEntry = this.getFromPool(this.float64Pool, byteLength);
309
+ if (poolEntry) {
310
+ ptr = poolEntry.ptr;
311
+ poolEntry.inUse = true;
312
+ } else {
313
+ ptr = module.__new(byteLength, 2);
314
+ }
315
+ } else {
316
+ ptr = module.__new(byteLength, 2);
317
+ }
318
+ const array = new Float64Array(module.memory.buffer, ptr, length);
319
+ array.set(data);
320
+ return { ptr, array };
321
+ }
322
+ /**
323
+ * Allocate Float64Array without copying data (for output buffers)
324
+ */
325
+ allocateFloat64ArrayEmpty(length) {
326
+ const module = this.wasmModule;
327
+ if (!module) throw new Error("WASM module not loaded");
328
+ const byteLength = length * 8;
329
+ let ptr;
330
+ if (byteLength <= this.poolSizeThreshold) {
331
+ const poolEntry = this.getFromPool(this.float64Pool, byteLength);
332
+ if (poolEntry) {
333
+ ptr = poolEntry.ptr;
334
+ poolEntry.inUse = true;
335
+ } else {
336
+ ptr = module.__new(byteLength, 2);
337
+ }
338
+ } else {
339
+ ptr = module.__new(byteLength, 2);
340
+ }
341
+ const array = new Float64Array(module.memory.buffer, ptr, length);
342
+ return { ptr, array };
343
+ }
344
+ /**
345
+ * Allocate Int32Array in WASM memory
346
+ * Uses memory pooling for frequently reused sizes
347
+ */
348
+ allocateInt32Array(data) {
349
+ const module = this.wasmModule;
350
+ if (!module) throw new Error("WASM module not loaded");
351
+ const length = data.length;
352
+ const byteLength = length * 4;
353
+ let ptr;
354
+ if (byteLength <= this.poolSizeThreshold) {
355
+ const poolEntry = this.getFromPool(this.int32Pool, byteLength);
356
+ if (poolEntry) {
357
+ ptr = poolEntry.ptr;
358
+ poolEntry.inUse = true;
359
+ } else {
360
+ ptr = module.__new(byteLength, 1);
361
+ }
362
+ } else {
363
+ ptr = module.__new(byteLength, 1);
364
+ }
365
+ const array = new Int32Array(module.memory.buffer, ptr, length);
366
+ array.set(data);
367
+ return { ptr, array };
368
+ }
369
+ /**
370
+ * Allocate Int32Array without copying data (for output buffers)
371
+ */
372
+ allocateInt32ArrayEmpty(length) {
373
+ const module = this.wasmModule;
374
+ if (!module) throw new Error("WASM module not loaded");
375
+ const byteLength = length * 4;
376
+ let ptr;
377
+ if (byteLength <= this.poolSizeThreshold) {
378
+ const poolEntry = this.getFromPool(this.int32Pool, byteLength);
379
+ if (poolEntry) {
380
+ ptr = poolEntry.ptr;
381
+ poolEntry.inUse = true;
382
+ } else {
383
+ ptr = module.__new(byteLength, 1);
384
+ }
385
+ } else {
386
+ ptr = module.__new(byteLength, 1);
387
+ }
388
+ const array = new Int32Array(module.memory.buffer, ptr, length);
389
+ return { ptr, array };
390
+ }
391
+ /**
392
+ * Get a suitable entry from the memory pool
393
+ */
394
+ getFromPool(pool, requestedSize) {
395
+ let bestFit = null;
396
+ let bestFitWaste = Infinity;
397
+ for (const entry of pool) {
398
+ if (!entry.inUse && entry.size >= requestedSize) {
399
+ const waste = entry.size - requestedSize;
400
+ if (waste < bestFitWaste && entry.size <= requestedSize * 2) {
401
+ bestFit = entry;
402
+ bestFitWaste = waste;
403
+ }
404
+ }
405
+ }
406
+ return bestFit;
407
+ }
408
+ /**
409
+ * Return allocation to pool for reuse
410
+ */
411
+ release(ptr, isFloat64 = true) {
412
+ const pool = isFloat64 ? this.float64Pool : this.int32Pool;
413
+ const entry = pool.find((e) => e.ptr === ptr);
414
+ if (entry) {
415
+ entry.inUse = false;
416
+ return;
417
+ }
418
+ this.free(ptr);
419
+ }
420
+ /**
421
+ * Free allocated memory (immediate, bypasses pool)
422
+ */
423
+ free(ptr) {
424
+ const module = this.wasmModule;
425
+ if (!module) return;
426
+ this.float64Pool = this.float64Pool.filter((e) => e.ptr !== ptr);
427
+ this.int32Pool = this.int32Pool.filter((e) => e.ptr !== ptr);
428
+ module.__unpin(ptr);
429
+ }
430
+ /**
431
+ * Clear the memory pool
432
+ */
433
+ clearPool() {
434
+ const module = this.wasmModule;
435
+ if (!module) return;
436
+ for (const entry of this.float64Pool) {
437
+ module.__unpin(entry.ptr);
438
+ }
439
+ for (const entry of this.int32Pool) {
440
+ module.__unpin(entry.ptr);
441
+ }
442
+ this.float64Pool = [];
443
+ this.int32Pool = [];
444
+ }
445
+ /**
446
+ * Get pool statistics
447
+ */
448
+ getPoolStats() {
449
+ const f64InUse = this.float64Pool.filter((e) => e.inUse).length;
450
+ const f64Bytes = this.float64Pool.reduce((sum, e) => sum + e.size, 0);
451
+ const i32InUse = this.int32Pool.filter((e) => e.inUse).length;
452
+ const i32Bytes = this.int32Pool.reduce((sum, e) => sum + e.size, 0);
453
+ return {
454
+ float64: {
455
+ total: this.float64Pool.length,
456
+ inUse: f64InUse,
457
+ totalBytes: f64Bytes
458
+ },
459
+ int32: {
460
+ total: this.int32Pool.length,
461
+ inUse: i32InUse,
462
+ totalBytes: i32Bytes
463
+ }
464
+ };
465
+ }
466
+ /**
467
+ * Run garbage collection
468
+ */
469
+ collect() {
470
+ const module = this.wasmModule;
471
+ if (!module) return;
472
+ module.__collect();
473
+ }
474
+ /**
475
+ * Reset the loader (for testing)
476
+ */
477
+ reset() {
478
+ this.clearPool();
479
+ this.wasmModule = null;
480
+ this.compiledModule = null;
481
+ this.loading = null;
482
+ this.lastMetrics = null;
483
+ }
484
+ };
485
+ var wasmLoader = WasmLoader.getInstance();
486
+
487
+ // src/wasm/sort/wasm-bridge.ts
488
+ var WASM_SORT_THRESHOLD = 16384;
489
+ function getWasm() {
490
+ try {
491
+ return wasmLoader.getModule();
492
+ } catch {
493
+ return null;
494
+ }
495
+ }
496
+ function cmpNanLast(a, b) {
497
+ if (a < b) return -1;
498
+ if (a > b) return 1;
499
+ if (a === b) return 0;
500
+ if (Number.isNaN(a) && Number.isNaN(b)) return 0;
501
+ if (Number.isNaN(a)) return 1;
502
+ return -1;
503
+ }
504
+ function sortF64JS(data) {
505
+ const arr = Array.from(data);
506
+ arr.sort(cmpNanLast);
507
+ for (let i = 0; i < arr.length; i++) data[i] = arr[i];
508
+ return data;
509
+ }
510
+ function argsortF64JS(data) {
511
+ const n = data.length;
512
+ const indices = new Int32Array(n);
513
+ for (let i = 0; i < n; i++) indices[i] = i;
514
+ const arr = Array.from(indices);
515
+ arr.sort((i, j) => cmpNanLast(data[i], data[j]));
516
+ for (let k = 0; k < n; k++) indices[k] = arr[k];
517
+ return indices;
518
+ }
519
+ function rankF64JS(data) {
520
+ const n = data.length;
521
+ const argsort = argsortF64JS(data);
522
+ const rank = new Int32Array(n);
523
+ for (let k = 0; k < n; k++) rank[argsort[k]] = k;
524
+ return rank;
525
+ }
526
+ function sortF64Dispatch(data) {
527
+ const n = data.length;
528
+ if (n >= WASM_SORT_THRESHOLD) {
529
+ const wasm = getWasm();
530
+ if (wasm) {
531
+ const rustFn = wasm["sort_f64"];
532
+ if (typeof rustFn === "function") {
533
+ try {
534
+ const alloc = wasmLoader.allocateFloat64Array(data);
535
+ try {
536
+ const ret = rustFn(alloc.ptr, n);
537
+ if (ret >= 0) {
538
+ data.set(alloc.array);
539
+ return data;
540
+ }
541
+ } finally {
542
+ wasmLoader.free(alloc.ptr);
543
+ }
544
+ } catch {
545
+ }
546
+ }
547
+ const asFn = wasm["sort_f64"];
548
+ if (typeof asFn === "function") {
549
+ try {
550
+ asFn(data);
551
+ return data;
552
+ } catch {
553
+ }
554
+ }
555
+ }
556
+ }
557
+ return sortF64JS(data);
558
+ }
559
+ function argsortF64Dispatch(data) {
560
+ const n = data.length;
561
+ if (n >= WASM_SORT_THRESHOLD) {
562
+ const wasm = getWasm();
563
+ if (wasm) {
564
+ const rustFn = wasm["argsort_f64"];
565
+ if (typeof rustFn === "function") {
566
+ try {
567
+ const inAlloc = wasmLoader.allocateFloat64Array(data);
568
+ const outAlloc = wasmLoader.allocateInt32ArrayEmpty(n);
569
+ try {
570
+ const ret = rustFn(
571
+ inAlloc.ptr,
572
+ n,
573
+ outAlloc.ptr
574
+ );
575
+ if (ret >= 0) {
576
+ return new Int32Array(outAlloc.array);
577
+ }
578
+ } finally {
579
+ wasmLoader.free(inAlloc.ptr);
580
+ wasmLoader.free(outAlloc.ptr);
581
+ }
582
+ } catch {
583
+ }
584
+ }
585
+ const asFn = wasm["argsort_f64"];
586
+ if (typeof asFn === "function") {
587
+ try {
588
+ return asFn(data);
589
+ } catch {
590
+ }
591
+ }
592
+ }
593
+ }
594
+ return argsortF64JS(data);
595
+ }
596
+ function rankF64Dispatch(data) {
597
+ const n = data.length;
598
+ if (n >= WASM_SORT_THRESHOLD) {
599
+ const wasm = getWasm();
600
+ if (wasm) {
601
+ const rustFn = wasm["rank_f64"];
602
+ if (typeof rustFn === "function") {
603
+ try {
604
+ const inAlloc = wasmLoader.allocateFloat64Array(data);
605
+ const outAlloc = wasmLoader.allocateInt32ArrayEmpty(n);
606
+ try {
607
+ const ret = rustFn(
608
+ inAlloc.ptr,
609
+ n,
610
+ outAlloc.ptr
611
+ );
612
+ if (ret >= 0) {
613
+ return new Int32Array(outAlloc.array);
614
+ }
615
+ } finally {
616
+ wasmLoader.free(inAlloc.ptr);
617
+ wasmLoader.free(outAlloc.ptr);
618
+ }
619
+ } catch {
620
+ }
621
+ }
622
+ const asFn = wasm["rank_f64"];
623
+ if (typeof asFn === "function") {
624
+ try {
625
+ return asFn(data);
626
+ } catch {
627
+ }
628
+ }
629
+ }
630
+ }
631
+ return rankF64JS(data);
632
+ }
633
+
634
+ export {
635
+ __export,
636
+ wasmLoader,
637
+ WASM_SORT_THRESHOLD,
638
+ sortF64JS,
639
+ argsortF64JS,
640
+ rankF64JS,
641
+ sortF64Dispatch,
642
+ argsortF64Dispatch,
643
+ rankF64Dispatch
644
+ };