DeckProbe 2.6 for JavaScript: the deckprobe command line tool for Node, and a
browser SDK that runs the same Rust engine in WebAssembly. In the browser,
documents stay in the browser — the package does not upload files.
npm install @deckflow/deckprobeInstalling the package puts deckprobe on PATH, or run it without installing:
npx @deckflow/deckprobe --help
npx @deckflow/deckprobe -t slide_count deck.pptxThis is the same native binary the standalone installers ship, so every flag, help page, JSON report, and exit code matches the CLI reference exactly. The binary arrives through a per-platform optional dependency for the targets DeckProbe releases: macOS arm64/x64, Linux arm64/x64 (glibc and musl), and Windows x64.
If installation skipped optional dependencies, deckprobe reports which
platform package is missing. Install it directly, or build from source with
cargo install --git https://github.com/deckflow/deckprobe --locked deckprobe.
import { probeFile } from "@deckflow/deckprobe";
const report = await probeFile("deck.pptx", {
targets: ["powerpoint.slide_count", "powerpoint.smartart_data_part_count"],
targetConfidence: { "powerpoint.slide_count": "exact" },
formatOptions: { "powerpoint.slide_count_path": "presentation-xml" },
budget: { timeoutMs: 1000 },
}, {
deadlineMs: 2500,
maxInputBytes: 16 * 1024 * 1024,
backend: "auto",
});The optional third argument enables a Node host deadline covering startup, input loading
and parsing. auto uses an installed same-version optional native package, otherwise a
fresh WASM Worker. It never downloads a runtime or sends document bytes to a service.
The default bounded request caps input at 16 MiB, output at 1 MiB and simultaneous workers
at two. signal cancels the request. onMetrics receives host timings after cleanup;
validation failures do not start an executor. Engine actual_cost keeps its existing meaning.
A terminated request returns a schema-v2 BUDGET_EXCEEDED error, with no invented facts.
The two-argument API below retains its original direct WASM behavior.
A 2500 ms deadline leaves cleanup/scheduling margin for a 3-second response SLO on supported
local environments. It is not a real-time guarantee or a guarantee of complete inspection.
Direct native CLI --timeout-ms remains cooperative; --max-input-bytes adds a separate
source-size cap. Browser APIs are unchanged.
SmartArt counts declared, existing, unique diagram-data parts by effective Content Type, including nonstandard part names. It does not count visible instances or parse SmartArt text.
The node export condition loads the WebAssembly binary from disk, so the
package works under Node with no configuration:
import { probeFile } from "@deckflow/deckprobe";
const report = await probeFile("deck.pptx", {
targets: ["@summary", "@security"],
level: "metadata",
});probeFile() reports source_kind: "local_file" and produces a report byte for
byte identical to the native CLI on the same input. probe() accepts Buffer,
Uint8Array, and ArrayBuffer alongside the browser input types, and reports
node_bytes. deckProbeWasmPath exposes the absolute path of the binary being
loaded.
The two-argument probeFile() reads the whole file into memory, because the WebAssembly engine
takes bytes rather than a file handle. The deckprobe command reads lazily
instead — for a 300 KB PPTX it touches under 18 KB — so prefer the CLI, or
--jsonl for batches, when inputs are large.
| Need | Import |
|---|---|
| Probe on the calling thread | import { initDeckProbe, probe } from "@deckflow/deckprobe" |
| Discover supported formats, targets, schema, or runtime version | import { formats, targets, schema, version } from "@deckflow/deckprobe" |
| Probe in a module Worker (browser only) | import { createDeckProbeWorker } from "@deckflow/deckprobe/worker" |
| Probe a file on disk (Node only) | import { probeFile } from "@deckflow/deckprobe" |
| Use TypeScript response and option types | import type { ProbeResult, ProbeCallOptions } from "@deckflow/deckprobe" |
probe() lazily initializes the WASM runtime. Use initDeckProbe() during
application startup or an idle period when the first interaction should avoid
that initialization cost.
| Path | Choose it when |
|---|---|
probe() on the main thread |
The probe is small and its result directly drives an interaction. |
createDeckProbeWorker() |
Files are user-provided or potentially large, or deep inspection must not block rendering. |
Both paths run the same engine; the Worker adds byte-copy and messaging cost in exchange for keeping the UI thread responsive. The first probe on either path also pays one-time WASM initialization; see the execution modes overview. Measure on your own corpus before optimizing.
Initialize on application startup or during idle time when you want the first user interaction to use the warm path:
import { initDeckProbe, probe } from "@deckflow/deckprobe";
await initDeckProbe();
const report = await probe(file, {
targets: ["@summary", "@security"],
level: "metadata",
});For larger documents, run the probe away from the UI thread. Keep the worker alive across a batch, then terminate it with its owning component or job:
import { createDeckProbeWorker } from "@deckflow/deckprobe/worker";
const worker = createDeckProbeWorker();
try {
const report = await worker.probe(file, { targets: ["@summary"] });
} finally {
worker.terminate();
}terminate() cancels all outstanding work and permanently closes that Worker
wrapper. Later probe() calls reject immediately; create a new wrapper for a
new batch. File and Blob reads happen inside the Worker, while direct
probe() intentionally runs input conversion and parsing on the calling
thread.
The package ships a WebAssembly binary next to its JavaScript wrapper and resolves it relative to that wrapper. Any tool that moves the wrapper without moving the binary breaks initialization.
Vite's dependency pre-bundling rewrites @deckflow/deckprobe into
node_modules/.vite/deps/ and does not copy the adjacent .wasm file, so the
dev server requests a binary that is not there. Every Vite version is
affected in dev; vite build is not affected — a production build resolves
and emits the binary correctly. That asymmetry is the clearest way to recognize
the problem.
The reported error depends on how the dev server answers the missing path:
| Vite | Error |
|---|---|
| 4.x | TypeError: Failed to execute 'compile' on 'WebAssembly': HTTP status code is not ok |
| 5.x, 6.x, 7.x | CompileError: WebAssembly.instantiate(): expected magic word 00 61 73 6d, found 3c 21 64 6f |
On Vite 5 and newer the SPA fallback answers with index.html, so the request
succeeds and WebAssembly rejects the HTML body instead — 3c 21 64 6f is
<!do. The Worker entry point fails the same way and surfaces it as
DeckProbe worker failed to load.
Exclude the package from dependency pre-bundling. This is the recommended fix, and the only one that covers the Worker entry point:
// vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
optimizeDeps: {
exclude: ["@deckflow/deckprobe"],
},
});Excluding the package name also covers the /worker subpath; it does not need
its own entry.
If you only use the main-thread entry point, passing the binary URL works without touching the Vite configuration:
import { initDeckProbe, probe } from "@deckflow/deckprobe";
import wasmUrl from "@deckflow/deckprobe/wasm?url";
await initDeckProbe(wasmUrl);?url is Vite's asset syntax; add /// <reference types="vite/client" /> so
TypeScript types the import. Call initDeckProbe(wasmUrl) before the first
probe() or discovery call, because the first initialization wins.
This form does not help createDeckProbeWorker(). The Worker runs its own
module instance, so a URL passed on the main thread never reaches it, and
pre-bundling relocates worker-runtime.js itself. Use optimizeDeps.exclude
whenever the Worker entry point is in play.
@deckflow/deckprobe/wasm is the stable path to the binary and accepts the
same treatment elsewhere: bundlers that understand new URL(..., import.meta.url)
(webpack 5, Rollup, esbuild) need no configuration, and anything else can load
the binary itself and hand it to initDeckProbe():
await initDeckProbe(await fetch("/assets/deckprobe_wasm_bg.wasm"));initDeckProbe() accepts a URL or path, a Request or Response, raw bytes,
or a compiled WebAssembly.Module (InitInput). Use the Response or bytes
form when a CDN origin, a sub-path deployment, or a strict CSP makes the
default relative URL wrong.
Uint8Array, ArrayBuffer, and Blob inputs require options.name with a
filename extension. File inputs use File.name automatically. Successful and
partial probes return the same schema-v2 report as the native CLI; recognized
probe failures return the schema-v2 error envelope.
A status of "partial" means one requested target could not be resolved; the
other results are still valid, and it is not a verdict on the document. Each
result carries confidence (exact, high, medium, low, none) describing
evidence strength, with confidence_score as a fixed constant per label
(1.0, 0.95, 0.7, 0.4, 0.0) rather than a calibrated probability — 0.95
does not mean the value is right 95% of the time. See
Reading confidence and partial results.
input.source_kind is typed as SourceKind: browser_bytes in the browser,
node_bytes for byte input under Node, local_file from probeFile() and the
native CLI, plus any custom value passed as options.sourceKind.
There is no view option. --view values is a CLI flag, so probe() and
probeFile() always resolve the full evidence report and ProbeResult is
ProbeReport | ErrorReport. If you parse deckprobe --view values output
instead, the exported ValuesReport type describes that envelope — note its
unresolved_targets sits at the top level rather than under execution.
Apple iWork files use the same selectors and target IDs as the native engine. For example:
const report = await probe(file, {
level: "deep",
targets: [
"iwork.producer_build",
"iwork.asset_type_counts",
"iwork.preview_dimensions",
"iwork.archive_object_count",
"iwork.message_type_counts",
"keynote.slide_size",
"keynote.hidden_slide_count",
"iwork.all_iwa_valid",
],
});targets("key"), targets("numbers"), and targets("pages") return typed
TargetSpecReport entries. Their applicable and supported_levels fields
distinguish the shared catalog from targets the chosen iWork driver can execute.
npm ci
npm run build
npx playwright install chromium
npm testnpm run build regenerates the Rust WASM package and then compiles the
TypeScript wrapper, so it requires cargo, the wasm32-unknown-unknown target,
and the local wasm-pack dependency. The generated files under wasm/ are
build artifacts and are intentionally ignored by Git. Rebuild them after a
version bump: the smoke tests assert that the compiled runtime reports the same
version as package.json.
npm test runs the type check, the version and artifact checks, and three
browser suites. npm run test:vite is the bundler guard: it packs the real
tarball, installs it into a throwaway Vite consumer, and asserts that both
documented Vite fixes and a production build still reach the WASM binary. It
drives Vite through this package's own dependency, so it needs Node
^20.19.0 || >=22.12.0 — the range Vite 7 requires.
The package is published independently as @deckflow/deckprobe while its Rust
binding remains part of the main DeckProbe Cargo workspace.
Licensed under the Apache License 2.0. See NOTICE for copyright attribution. Third-party components retain their own licenses.