@cfasim-ui/docs 0.3.11
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/LICENSE +201 -0
- package/charts/ChartMenu/ChartMenu.vue +140 -0
- package/charts/ChartMenu/download.ts +44 -0
- package/charts/ChartTooltip/ChartTooltip.vue +97 -0
- package/charts/ChoroplethMap/ChoroplethMap.md +398 -0
- package/charts/ChoroplethMap/ChoroplethMap.vue +777 -0
- package/charts/ChoroplethMap/hsaMapping.ts +4116 -0
- package/charts/DataTable/DataTable.md +143 -0
- package/charts/DataTable/DataTable.vue +277 -0
- package/charts/LineChart/LineChart.md +472 -0
- package/charts/LineChart/LineChart.vue +1216 -0
- package/charts/index.ts +23 -0
- package/charts/tooltip-position.ts +49 -0
- package/components/Box/Box.md +49 -0
- package/components/Box/Box.vue +52 -0
- package/components/Button/Button.md +67 -0
- package/components/Button/Button.vue +81 -0
- package/components/Expander/Expander.md +34 -0
- package/components/Expander/Expander.vue +95 -0
- package/components/Hint/Hint.md +29 -0
- package/components/Hint/Hint.vue +83 -0
- package/components/Icon/Icon.md +67 -0
- package/components/Icon/Icon.vue +112 -0
- package/components/LightDarkToggle/LightDarkToggle.vue +49 -0
- package/components/NumberInput/NumberInput.md +305 -0
- package/components/NumberInput/NumberInput.vue +531 -0
- package/components/SelectBox/SelectBox.md +110 -0
- package/components/SelectBox/SelectBox.vue +195 -0
- package/components/SidebarLayout/SidebarLayout.md +104 -0
- package/components/SidebarLayout/SidebarLayout.vue +466 -0
- package/components/Spinner/Spinner.md +51 -0
- package/components/Spinner/Spinner.vue +55 -0
- package/components/TextInput/TextInput.md +82 -0
- package/components/TextInput/TextInput.vue +94 -0
- package/components/Toggle/Toggle.md +81 -0
- package/components/Toggle/Toggle.vue +81 -0
- package/components/index.ts +15 -0
- package/index.json +121 -0
- package/package.json +24 -0
- package/pyodide/index.ts +7 -0
- package/pyodide/pyodide.worker.ts +233 -0
- package/pyodide/pyodideWorkerApi.ts +102 -0
- package/pyodide/useModel.ts +86 -0
- package/pyodide/vitePlugin.js +51 -0
- package/shared/ModelOutput.ts +88 -0
- package/shared/csv.ts +22 -0
- package/shared/index.ts +24 -0
- package/shared/transferUtils.ts +126 -0
- package/shared/useUrlParams.ts +296 -0
- package/theme/all.js +5 -0
- package/theme/base.css +176 -0
- package/theme/cfasim.css +3 -0
- package/theme/theme.css +113 -0
- package/theme/themes/cdc.css +22 -0
- package/theme/utilities.css +518 -0
- package/wasm/index.ts +2 -0
- package/wasm/useModel.ts +53 -0
- package/wasm/vitePlugin.js +35 -0
- package/wasm/wasm.worker.ts +74 -0
- package/wasm/wasmWorkerApi.ts +38 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { TransferableResponse } from "@cfasim-ui/shared";
|
|
2
|
+
import { unwrapResponse } from "@cfasim-ui/shared";
|
|
3
|
+
|
|
4
|
+
interface PromiseResolver<T> {
|
|
5
|
+
promise: Promise<T>;
|
|
6
|
+
resolve: (value: T) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getPromiseAndResolve<T>(): PromiseResolver<T> {
|
|
10
|
+
let resolve: (value: T) => void;
|
|
11
|
+
const promise = new Promise<T>((res) => {
|
|
12
|
+
resolve = res;
|
|
13
|
+
});
|
|
14
|
+
return { promise, resolve: resolve! };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let lastId = 0;
|
|
18
|
+
function getId(): number {
|
|
19
|
+
return ++lastId;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface WorkerMessage {
|
|
23
|
+
id: number;
|
|
24
|
+
type?: "run" | "loadModule";
|
|
25
|
+
python?: string;
|
|
26
|
+
module?: string;
|
|
27
|
+
context?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function requestResponse(
|
|
31
|
+
worker: Worker,
|
|
32
|
+
msg: Omit<WorkerMessage, "id">,
|
|
33
|
+
): Promise<{ result?: unknown; error?: string }> {
|
|
34
|
+
const { promise, resolve } = getPromiseAndResolve<{
|
|
35
|
+
result?: unknown;
|
|
36
|
+
error?: string;
|
|
37
|
+
}>();
|
|
38
|
+
const idWorker = getId();
|
|
39
|
+
|
|
40
|
+
function listener(event: MessageEvent<TransferableResponse>) {
|
|
41
|
+
if (event.data?.id !== idWorker) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
worker.removeEventListener("message", listener);
|
|
45
|
+
if (event.data.error) {
|
|
46
|
+
resolve({ error: event.data.error });
|
|
47
|
+
} else {
|
|
48
|
+
resolve({ result: unwrapResponse(event.data) });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
worker.addEventListener("message", listener);
|
|
53
|
+
worker.postMessage({ id: idWorker, ...msg } as WorkerMessage);
|
|
54
|
+
return promise;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type WorkerName = "baseline" | "intervention";
|
|
58
|
+
|
|
59
|
+
function createWorker(): Worker {
|
|
60
|
+
return new Worker(new URL("./pyodide.worker.ts", import.meta.url), {
|
|
61
|
+
type: "module",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const workers: Record<WorkerName, Worker> = {
|
|
66
|
+
baseline: createWorker(),
|
|
67
|
+
intervention: createWorker(),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export async function loadModuleOnWorker(
|
|
71
|
+
moduleName: string,
|
|
72
|
+
worker: WorkerName,
|
|
73
|
+
): Promise<{ result?: unknown; error?: string }> {
|
|
74
|
+
return requestResponse(workers[worker], {
|
|
75
|
+
type: "loadModule",
|
|
76
|
+
module: moduleName,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function loadModule(
|
|
81
|
+
moduleName: string,
|
|
82
|
+
): Promise<{ result?: unknown; error?: string }> {
|
|
83
|
+
// Load module on all workers so any worker can run code that depends on it
|
|
84
|
+
const results = await Promise.all(
|
|
85
|
+
Object.values(workers).map((w) =>
|
|
86
|
+
requestResponse(w, { type: "loadModule", module: moduleName }),
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
return results[0];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function asyncRunPython(
|
|
93
|
+
script: string,
|
|
94
|
+
context?: Record<string, unknown>,
|
|
95
|
+
worker?: WorkerName,
|
|
96
|
+
): Promise<{ result?: unknown; error?: string }> {
|
|
97
|
+
const target = worker ? workers[worker] : workers.baseline;
|
|
98
|
+
return requestResponse(target, {
|
|
99
|
+
python: script,
|
|
100
|
+
context,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ref, toRaw, toValue, watch } from "vue";
|
|
2
|
+
import type { MaybeRef } from "vue";
|
|
3
|
+
import type { ModelOutput } from "@cfasim-ui/shared";
|
|
4
|
+
import { asyncRunPython, loadModule } from "./pyodideWorkerApi.js";
|
|
5
|
+
|
|
6
|
+
export function useModel<T = unknown>(moduleName: string) {
|
|
7
|
+
const result = ref<T>();
|
|
8
|
+
const error = ref<string>();
|
|
9
|
+
const loading = ref(true);
|
|
10
|
+
|
|
11
|
+
const loaded = loadModule(moduleName);
|
|
12
|
+
loaded.then(() => {
|
|
13
|
+
loading.value = false;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
async function run(fn: string, context?: Record<string, unknown>) {
|
|
17
|
+
loading.value = true;
|
|
18
|
+
error.value = undefined;
|
|
19
|
+
try {
|
|
20
|
+
await loaded;
|
|
21
|
+
const argNames = context ? Object.keys(context) : [];
|
|
22
|
+
const callArgs = argNames.join(", ");
|
|
23
|
+
const plainContext = context
|
|
24
|
+
? Object.fromEntries(
|
|
25
|
+
Object.entries(context).map(([k, v]) => [k, toRaw(v)]),
|
|
26
|
+
)
|
|
27
|
+
: undefined;
|
|
28
|
+
const response = await asyncRunPython(
|
|
29
|
+
`import ${moduleName}\n${moduleName}.${fn}(${callArgs})`,
|
|
30
|
+
plainContext,
|
|
31
|
+
);
|
|
32
|
+
if (response.error) {
|
|
33
|
+
error.value = response.error;
|
|
34
|
+
} else {
|
|
35
|
+
result.value = response.result as T;
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {
|
|
38
|
+
error.value = e instanceof Error ? e.message : String(e);
|
|
39
|
+
} finally {
|
|
40
|
+
loading.value = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function useOutputs<P extends Record<string, unknown>>(
|
|
45
|
+
fn: string,
|
|
46
|
+
params: MaybeRef<P>,
|
|
47
|
+
) {
|
|
48
|
+
const outputs = ref<Record<string, ModelOutput>>();
|
|
49
|
+
const outputsError = ref<string>();
|
|
50
|
+
const outputsLoading = ref(true);
|
|
51
|
+
|
|
52
|
+
watch(
|
|
53
|
+
() => toValue(params),
|
|
54
|
+
async (p) => {
|
|
55
|
+
outputsLoading.value = true;
|
|
56
|
+
outputsError.value = undefined;
|
|
57
|
+
try {
|
|
58
|
+
await loaded;
|
|
59
|
+
const plain = Object.fromEntries(
|
|
60
|
+
Object.entries(p).map(([k, v]) => [k, toRaw(v)]),
|
|
61
|
+
);
|
|
62
|
+
const argNames = Object.keys(plain);
|
|
63
|
+
const callArgs = argNames.join(", ");
|
|
64
|
+
const response = await asyncRunPython(
|
|
65
|
+
`import ${moduleName}\n${moduleName}.${fn}(${callArgs})`,
|
|
66
|
+
plain,
|
|
67
|
+
);
|
|
68
|
+
if (response.error) {
|
|
69
|
+
outputsError.value = response.error;
|
|
70
|
+
} else {
|
|
71
|
+
outputs.value = response.result as Record<string, ModelOutput>;
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
outputsError.value = e instanceof Error ? e.message : String(e);
|
|
75
|
+
} finally {
|
|
76
|
+
outputsLoading.value = false;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{ immediate: true, deep: true },
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return { outputs, error: outputsError, loading: outputsLoading };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { run, result, error, loading, useOutputs };
|
|
86
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { readdirSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} CfasimPyodideOptions
|
|
7
|
+
* @property {string} [model] Path to the Python model directory (default: "model")
|
|
8
|
+
* @property {string[]} [pypiDeps] PyPI packages to download at build time and serve locally
|
|
9
|
+
* @property {string} [pipCommand] Command used to invoke pip for downloading `pypiDeps` (default: "uvx pip"). Override with e.g. "pip" or "uv run pip".
|
|
10
|
+
* @property {string} [pythonVersion] Python version passed to pip's --python-version flag when downloading `pypiDeps` (default: "3.12"). Should match the Python shipped by your Pyodide runtime.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Vite plugin that builds a Python wheel from a local model directory
|
|
15
|
+
* and generates wheels.json in the public directory.
|
|
16
|
+
*
|
|
17
|
+
* Use `pypiDeps` to prebuild PyPI packages (like cfasim-model) as local
|
|
18
|
+
* wheels for faster browser runtime — avoids PyPI round-trips on page load.
|
|
19
|
+
*
|
|
20
|
+
* @param {CfasimPyodideOptions} [options]
|
|
21
|
+
* @returns {import("vite").Plugin}
|
|
22
|
+
*/
|
|
23
|
+
export function cfasimPyodide(options) {
|
|
24
|
+
const modelDir = options?.model ?? "model";
|
|
25
|
+
const pipCommand = options?.pipCommand ?? "uvx pip";
|
|
26
|
+
const pythonVersion = options?.pythonVersion ?? "3.12";
|
|
27
|
+
|
|
28
|
+
function build(root) {
|
|
29
|
+
const publicDir = resolve(root, "public");
|
|
30
|
+
mkdirSync(publicDir, { recursive: true });
|
|
31
|
+
for (const dep of options?.pypiDeps ?? []) {
|
|
32
|
+
execSync(
|
|
33
|
+
`${pipCommand} download ${dep} --dest public --no-deps --python-version ${pythonVersion} --platform any`,
|
|
34
|
+
{ cwd: root, stdio: "pipe" },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
execSync(`uv build ${modelDir} --wheel --out-dir public`, {
|
|
38
|
+
cwd: root,
|
|
39
|
+
stdio: "pipe",
|
|
40
|
+
});
|
|
41
|
+
const wheels = readdirSync(publicDir).filter((f) => f.endsWith(".whl"));
|
|
42
|
+
writeFileSync(resolve(publicDir, "wheels.json"), JSON.stringify(wheels));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
name: "cfasim-pyodide",
|
|
47
|
+
configResolved(config) {
|
|
48
|
+
build(config.root);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type ColumnType = "i32" | "u32" | "f64" | "bool" | "enum";
|
|
2
|
+
|
|
3
|
+
export interface ColumnDescriptor {
|
|
4
|
+
name: string;
|
|
5
|
+
type: ColumnType;
|
|
6
|
+
enumLabels?: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type TypedColumn = Float64Array | Int32Array | Uint32Array | Uint8Array;
|
|
10
|
+
|
|
11
|
+
export interface ModelOutputWire {
|
|
12
|
+
__modelOutput: true;
|
|
13
|
+
length: number;
|
|
14
|
+
columns: ColumnDescriptor[];
|
|
15
|
+
buffers: ArrayBuffer[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ModelOutputsWire {
|
|
19
|
+
__modelOutputs: true;
|
|
20
|
+
outputs: Record<string, ModelOutputWire>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const COLUMN_TYPE_TO_CTOR: Record<
|
|
24
|
+
ColumnType,
|
|
25
|
+
new (buffer: ArrayBuffer) => TypedColumn
|
|
26
|
+
> = {
|
|
27
|
+
f64: Float64Array,
|
|
28
|
+
i32: Int32Array,
|
|
29
|
+
u32: Uint32Array,
|
|
30
|
+
bool: Uint8Array,
|
|
31
|
+
enum: Uint32Array,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class ModelOutput {
|
|
35
|
+
readonly length: number;
|
|
36
|
+
readonly columns: readonly ColumnDescriptor[];
|
|
37
|
+
readonly buffers: readonly TypedColumn[];
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
length: number,
|
|
41
|
+
columns: ColumnDescriptor[],
|
|
42
|
+
buffers: TypedColumn[],
|
|
43
|
+
) {
|
|
44
|
+
this.length = length;
|
|
45
|
+
this.columns = columns;
|
|
46
|
+
this.buffers = buffers;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
column(name: string): TypedColumn {
|
|
50
|
+
const idx = this.columns.findIndex((c) => c.name === name);
|
|
51
|
+
if (idx === -1) throw new Error(`Unknown column: ${name}`);
|
|
52
|
+
return this.buffers[idx];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
descriptor(name: string): ColumnDescriptor {
|
|
56
|
+
const idx = this.columns.findIndex((c) => c.name === name);
|
|
57
|
+
if (idx === -1) throw new Error(`Unknown column: ${name}`);
|
|
58
|
+
return this.columns[idx];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
label(columnName: string, index: number): string {
|
|
62
|
+
const desc = this.descriptor(columnName);
|
|
63
|
+
if (desc.type !== "enum" || !desc.enumLabels) {
|
|
64
|
+
throw new Error(`Column ${columnName} is not an enum`);
|
|
65
|
+
}
|
|
66
|
+
return desc.enumLabels[index];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get names(): string[] {
|
|
70
|
+
return this.columns.map((c) => c.name);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static fromWire(wire: ModelOutputWire): ModelOutput {
|
|
74
|
+
const typed = wire.columns.map((col, i) => {
|
|
75
|
+
const Ctor = COLUMN_TYPE_TO_CTOR[col.type];
|
|
76
|
+
return new Ctor(wire.buffers[i]);
|
|
77
|
+
});
|
|
78
|
+
return new ModelOutput(wire.length, wire.columns, typed);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static recordFromWire(wire: ModelOutputsWire): Record<string, ModelOutput> {
|
|
82
|
+
const result: Record<string, ModelOutput> = {};
|
|
83
|
+
for (const [key, outputWire] of Object.entries(wire.outputs)) {
|
|
84
|
+
result[key] = ModelOutput.fromWire(outputWire);
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
}
|
package/shared/csv.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ModelOutput } from "./ModelOutput.js";
|
|
2
|
+
|
|
3
|
+
export function modelOutputToCSV(output: ModelOutput): string {
|
|
4
|
+
const header = output.names.join(",");
|
|
5
|
+
const rows: string[] = [header];
|
|
6
|
+
for (let r = 0; r < output.length; r++) {
|
|
7
|
+
const cells: string[] = [];
|
|
8
|
+
for (let c = 0; c < output.columns.length; c++) {
|
|
9
|
+
const desc = output.columns[c];
|
|
10
|
+
const val = output.buffers[c][r];
|
|
11
|
+
if (desc.type === "enum") {
|
|
12
|
+
cells.push(desc.enumLabels![val as number]);
|
|
13
|
+
} else if (desc.type === "bool") {
|
|
14
|
+
cells.push(val ? "true" : "false");
|
|
15
|
+
} else {
|
|
16
|
+
cells.push(String(val));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
rows.push(cells.join(","));
|
|
20
|
+
}
|
|
21
|
+
return rows.join("\n");
|
|
22
|
+
}
|
package/shared/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export {
|
|
2
|
+
postWithTransfer,
|
|
3
|
+
postModelOutputsWithTransfer,
|
|
4
|
+
postErrorWithTransfer,
|
|
5
|
+
unwrapResponse,
|
|
6
|
+
} from "./transferUtils.js";
|
|
7
|
+
export type { TransferableResponse } from "./transferUtils.js";
|
|
8
|
+
export { ModelOutput } from "./ModelOutput.js";
|
|
9
|
+
export type {
|
|
10
|
+
ColumnDescriptor,
|
|
11
|
+
ColumnType,
|
|
12
|
+
TypedColumn,
|
|
13
|
+
ModelOutputWire,
|
|
14
|
+
ModelOutputsWire,
|
|
15
|
+
} from "./ModelOutput.js";
|
|
16
|
+
export { modelOutputToCSV } from "./csv.js";
|
|
17
|
+
export {
|
|
18
|
+
useUrlParams,
|
|
19
|
+
serialize,
|
|
20
|
+
deserialize,
|
|
21
|
+
paramsToQuery,
|
|
22
|
+
queryToParams,
|
|
23
|
+
} from "./useUrlParams.js";
|
|
24
|
+
export type { UrlParamsOptions } from "./useUrlParams.js";
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { ColumnDescriptor, ModelOutputsWire } from "./ModelOutput.js";
|
|
2
|
+
import { ModelOutput } from "./ModelOutput.js";
|
|
3
|
+
|
|
4
|
+
interface ModelOutputsResponsePayload {
|
|
5
|
+
outputs: Record<
|
|
6
|
+
string,
|
|
7
|
+
{ length: number; columns: ColumnDescriptor[]; buffers: ArrayBuffer[] }
|
|
8
|
+
>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TransferableResponse {
|
|
12
|
+
id: number;
|
|
13
|
+
result?: unknown;
|
|
14
|
+
buffer?: ArrayBuffer;
|
|
15
|
+
bufferType?: string;
|
|
16
|
+
modelOutputs?: ModelOutputsResponsePayload;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TYPED_ARRAY_CONSTRUCTORS: Record<
|
|
21
|
+
string,
|
|
22
|
+
new (buffer: ArrayBuffer) => ArrayBufferView
|
|
23
|
+
> = {
|
|
24
|
+
Int8Array,
|
|
25
|
+
Uint8Array,
|
|
26
|
+
Uint8ClampedArray,
|
|
27
|
+
Int16Array,
|
|
28
|
+
Uint16Array,
|
|
29
|
+
Int32Array,
|
|
30
|
+
Uint32Array,
|
|
31
|
+
Float32Array,
|
|
32
|
+
Float64Array,
|
|
33
|
+
BigInt64Array,
|
|
34
|
+
BigUint64Array,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function isTypedArray(value: unknown): value is ArrayBufferView {
|
|
38
|
+
return ArrayBuffer.isView(value) && !(value instanceof DataView);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function postWithTransfer(
|
|
42
|
+
port: typeof globalThis,
|
|
43
|
+
id: number,
|
|
44
|
+
result: unknown,
|
|
45
|
+
): void {
|
|
46
|
+
if (result instanceof ArrayBuffer) {
|
|
47
|
+
port.postMessage(
|
|
48
|
+
{
|
|
49
|
+
id,
|
|
50
|
+
buffer: result,
|
|
51
|
+
bufferType: "ArrayBuffer",
|
|
52
|
+
} satisfies TransferableResponse,
|
|
53
|
+
{ transfer: [result] },
|
|
54
|
+
);
|
|
55
|
+
} else if (isTypedArray(result)) {
|
|
56
|
+
const buffer = result.buffer as ArrayBuffer;
|
|
57
|
+
port.postMessage(
|
|
58
|
+
{
|
|
59
|
+
id,
|
|
60
|
+
buffer,
|
|
61
|
+
bufferType: result.constructor.name,
|
|
62
|
+
} satisfies TransferableResponse,
|
|
63
|
+
{ transfer: [buffer] },
|
|
64
|
+
);
|
|
65
|
+
} else {
|
|
66
|
+
port.postMessage({ id, result } satisfies TransferableResponse);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function postModelOutputsWithTransfer(
|
|
71
|
+
port: typeof globalThis,
|
|
72
|
+
id: number,
|
|
73
|
+
wire: ModelOutputsWire,
|
|
74
|
+
): void {
|
|
75
|
+
const transferList: ArrayBuffer[] = [];
|
|
76
|
+
const outputs: ModelOutputsResponsePayload["outputs"] = {};
|
|
77
|
+
|
|
78
|
+
for (const [key, outputWire] of Object.entries(wire.outputs)) {
|
|
79
|
+
outputs[key] = {
|
|
80
|
+
length: outputWire.length,
|
|
81
|
+
columns: outputWire.columns,
|
|
82
|
+
buffers: outputWire.buffers,
|
|
83
|
+
};
|
|
84
|
+
transferList.push(...outputWire.buffers);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
port.postMessage(
|
|
88
|
+
{ id, modelOutputs: { outputs } } satisfies TransferableResponse,
|
|
89
|
+
{ transfer: transferList },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function postErrorWithTransfer(
|
|
94
|
+
port: typeof globalThis,
|
|
95
|
+
id: number,
|
|
96
|
+
error: unknown,
|
|
97
|
+
): void {
|
|
98
|
+
port.postMessage({
|
|
99
|
+
id,
|
|
100
|
+
error: error instanceof Error ? error.message : String(error),
|
|
101
|
+
} satisfies TransferableResponse);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function unwrapResponse(data: TransferableResponse): unknown {
|
|
105
|
+
if (data.modelOutputs) {
|
|
106
|
+
return ModelOutput.recordFromWire({
|
|
107
|
+
__modelOutputs: true,
|
|
108
|
+
outputs: data.modelOutputs.outputs as Record<
|
|
109
|
+
string,
|
|
110
|
+
{
|
|
111
|
+
__modelOutput: true;
|
|
112
|
+
length: number;
|
|
113
|
+
columns: ColumnDescriptor[];
|
|
114
|
+
buffers: ArrayBuffer[];
|
|
115
|
+
}
|
|
116
|
+
>,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (data.buffer != null) {
|
|
120
|
+
if (data.bufferType === "ArrayBuffer") return data.buffer;
|
|
121
|
+
const Ctor = TYPED_ARRAY_CONSTRUCTORS[data.bufferType!];
|
|
122
|
+
if (Ctor) return new Ctor(data.buffer);
|
|
123
|
+
return data.buffer;
|
|
124
|
+
}
|
|
125
|
+
return data.result;
|
|
126
|
+
}
|