@spatialpack/sdk
v0.3.0
Published
TypeScript SDK for SpatialPack — analyze, optimize, and validate glTF / GLB / USDZ assets, dedup texture batches, round-trip USDA ↔ USDC, run safety-gated visual-diff, and ingest Gaussian splats.
Maintainers
Readme
@spatialpack/sdk
TypeScript SDK for SpatialPack.
Wraps the @spatialpack/core pipeline so Node.js integrators can:
- analyze / optimize / conform glTF / GLB assets
- de-duplicate embedded textures across a batch (SPEC-0091)
- bundle a GLB + sidecars into a single
.glb.ziparchive - round-trip USDA text ↔ USDC binary (SPEC-0076)
- compose USD layer stacks (SPEC-USD-Comp.1)
- score visual-diff metrics against cohort-aware thresholds (SPEC-0090)
- compute asset embeddings + k-NN similarity + recipe inheritance (SPEC-0087)
- drive recipe search with progress callbacks (SPEC-0013 / 0044 / 0072 / 0092)
- ingest Gaussian splats from Polycam / Luma / Niantic
- short-circuit visual-diff with multi-view phash signatures (SPEC-0062)
Requirements
- Node.js >= 20
- ESM-only (
"type": "module"). Use dynamicimport('@spatialpack/sdk')from CommonJS, or run your project with"type": "module"and.mjs/ TypeScript-with-ESM.
Install
pnpm add @spatialpack/sdk
# or
npm install @spatialpack/sdk
# or
yarn add @spatialpack/sdkThe SDK transparently depends on @spatialpack/core (installed
automatically). client.visualDiff(...) additionally requires the
optional @spatialpack/cli binary on PATH (or pass a custom
cliPath) — install it with pnpm add -D @spatialpack/cli if you
need it.
Quick start (recommended: SpatialPackClient)
import { SpatialPackClient } from '@spatialpack/sdk';
const client = new SpatialPackClient({ defaultPreset: 'web-mobile' });
// analyze
const report = await client.analyze('hero.glb');
// optimize with real-time progress (SPEC-0092)
const opt = await client.optimize('hero.glb', {
outPath: 'out.glb',
onProgress: (e) => console.log(e.kind, e),
});
// conformance vs USDZ delivery rules
const conf = await client.conformance('hero.glb', { target: 'apple-ar' });The previous v0.1.0 named exports (analyze, optimize,
conformance, ...) remain fully supported. New code should prefer
SpatialPackClient because it gives you a single place to set
defaults (default preset, custom CLI path) without threading them
through every call site.
SPEC-0091 — dedup-textures + .glb.zip bundle
const report = await client.dedupTextures(
['a.glb', 'b.glb', 'c.glb'],
{ outDir: 'out', minOccurrences: 2 },
);
console.log(`saved ${report.savedBytes} bytes (${(report.ratio * 100).toFixed(1)}% of original)`);
const bundle = client.bundleGlbZip({
primaryPath: 'a.glb',
sidecarPaths: ['out/textures/abc123.png'],
outputPath: 'a.glb.zip',
});
const back = client.unbundleGlbZip({
bundlePath: 'a.glb.zip',
outDir: 'unpacked',
});SPEC-0076 — USDA ↔ USDC round-trip + composition
// USDC → USDA (mirrors `usdcat` from the Pixar USD toolkit).
const usda = client.usdcFileToUsda('asset.usdc');
// USDA → USDC.
const usdc = client.usdaToUsdc(usda);
// Compose a stage from a root layer + sublayer search roots.
const stage = client.composeStageFromFile('root.usda', {
layerRoots: ['layers/'],
});
console.log('layer stack (strongest first):', stage.layerStack);SPEC-0087 — embeddings, k-NN, and recipe inheritance
import { AssetEmbeddingIndex } from '@spatialpack/sdk';
// 1. Embed one asset.
const emb = await client.embed('hero.glb');
// 2. Build an index across a directory tree.
const index = await client.buildEmbeddingIndex('corpus/', { recursive: true });
client.saveEmbeddingIndex(index, 'corpus/embedding-index.json');
// 3. Top-K nearest neighbors.
const similar = await client.findSimilar('hero.glb', index, {
k: 5,
excludeQueryFromResults: true,
});
// 4. Seed a recipe search from neighbors' cached winners.
const seeds = await client.seedRecipesFromNeighbors(
'hero.glb',
index,
(id) => recipeCache.get(id) ?? null, // host-provided lookup
{ k: 5 },
);
console.log(`${seeds.cacheHits}/${seeds.k} neighbors had cached recipes`);
const search = await client.recipeSearch('hero.glb', {
outPath: 'winner.glb',
searchStrategy: 'multi-fidelity-tpe',
seedRecipes: seeds.seedRecipes,
maxRecipes: 12,
});SPEC-0090 — safety-gate
import {
buildAssetVerdict,
gateAssetMetrics,
summarizeAssetVerdicts,
} from '@spatialpack/sdk';
// Score your own visual-diff metric bundle against cohort thresholds:
const verdict = buildAssetVerdict({
assetId: 'hero',
cohortTags: { textureCohort: 'textured', animationCohort: 'static', polyCohort: 'medium' },
metrics: { ssim: 0.98, deltaE94Mean: 0.7, edgeDelta: 0.02 },
});
// Or run the full CLI orchestrator (spawns nested `spatialpack` invocations):
const result = await client.safetyGate({
featureId: 'spec-foo',
cohort: 'static-props',
reportDir: 'reports/spec-foo',
});
if (result.exitCode !== 0) throw new Error('safety-gate failed');SPEC-0093 — visual-diff (CLI subprocess)
Visual-diff requires Playwright. The SDK doesn't import Playwright
because @spatialpack/core MUST NOT depend on it (per the repo
CLAUDE.md). Instead, the SDK spawns the spatialpack CLI binary and
parses the JSON report:
const vd = await client.visualDiff({
beforePath: 'before.glb',
afterPath: 'after.glb',
outDir: 'visual-diff-out',
threshold: 0.05,
});
console.log(`exit ${vd.exitCode}`);A JS-native renderer (headless three.js) is on the SPEC-0093 Phase 2
roadmap; until it lands, client.visualDiff(...) requires
spatialpack on PATH (or pass cliPath).
SPEC-0062 — phash short-circuit for CI
const sig = client.phashSignature(views); // views: [{ label, png }]
const baseline = parsePhashSignature(fs.readFileSync('phash-signature.json'));
const cmp = client.phashCompare(baseline, sig);
if (cmp.action === 'skip-diff') {
// ~50ms triage replaces a 10-20s visual-diff (200-400× speedup).
return { pass: true };
}Splat ingest
const splat = client.parseSplat('polycam-export.ply');
const glb = client.wrapSplat(splat, { center: true, shOrder: 2 });
fs.writeFileSync('splat.glb', glb);Type re-exports
The SDK re-exports every public type from @spatialpack/core so you
don't need to import from two packages:
AnalyzeReport,OptimizationReport,PresetId,OptimizeProgressEvent,OptimizeProgressCallbackConformanceReport,ConformanceTargetSplatInput,SplatWrapOptions,ParsePlyOptionsFrameCostPredictionMultiViewPhashSignature,MultiViewPhashComparisonSourceTextureDedupReport,GlbZipManifest,BundleInput,UnbundleResultComposedStageSafetyGateReport,SafetyGateAssetMetrics,SafetyGateAssetVerdict,CohortTags,CohortConfig,SafetyGateFeatureToggle,SafetyGateFeatureConfigAssetEmbedding,EmbeddingSource,SimilarAsset,SimilarityResult,SeedRecipesFromNeighborsResult,InheritedRecipeNeighbor,RecipeLookupRecipeSearchReport,RunRecipeSearchOptions,RecipeCandidate,RecipeRunResult,RecipeWinnerStrategy
Examples
packages/sdk/examples/ ships runnable scripts:
optimize-one.mjs— optimize a corpus GLB with progress eventsdedup-batch.mjs— dedup textures across the first 6 corpus assetsusdc-to-usda.mjs— USDC ↔ USDA round-triprecipe-search-with-inheritance.mjs— k-NN seed feedglb-zip-roundtrip.mjs— bundle + unbundle
node packages/sdk/examples/optimize-one.mjsAPI surface (at a glance)
SpatialPackClient
| Method | What it does |
| --------------------------------- | ----------------------------------------------------------------------- |
| analyze(input) | Full structured analyze report. |
| optimize(input, opts) | Run the pipeline + return report + optimized GLB bytes (SPEC-0092). |
| conformance(input, opts) | Web / Apple-AR conformance check (SPEC-0067). |
| predictFrameCost(input) | SPEC-0068 frame-cost prediction. |
| dedupTextures(paths, opts) | SPEC-0091 batch texture dedup. |
| bundleGlbZip(input) | Bundle GLB + sidecars as .glb.zip. |
| unbundleGlbZip(input) | Unbundle .glb.zip back to GLB + sidecars. |
| usdcFileToUsda(path) | USDC → USDA (SPEC-0076 Phase G). |
| usdaToUsdc(text) | USDA → USDC. |
| composeStackFromFile(path, opt) | Compose a USD layer stack (SPEC-USD-Comp.1). |
| embed(input, opts) | SPEC-0087 asset embedding. |
| buildEmbeddingIndex(dir, opts) | Build a k-NN index across a directory. |
| findSimilar(input, index, opts) | Top-K nearest neighbors. |
| seedRecipesFromNeighbors(...) | Inherit recipe winners from neighbors. |
| recipeSearch(input, opts) | SPEC-0072 recipe search with progress callbacks. |
| safetyGate(opts) | SPEC-0090 orchestrator (spawns CLI subprocesses). |
| visualDiff(opts) | SPEC-0093 visual-diff (requires @spatialpack/cli). |
| phashSignature(views) | SPEC-0062 multi-view perceptual hash. |
| phashCompare(baseline, sig) | Compare phash signatures for CI short-circuit. |
| parseSplat(input, opts) | Parse Polycam / Luma / Niantic Gaussian-splat PLY. |
| wrapSplat(input, opts) | Wrap a splat as glTF GLB. |
Standalone helpers
For pure-data work without instantiating a client:
buildAssetVerdict,gateAssetMetrics,summarizeAssetVerdicts,expandCohort,validateFeatureToggle,renderSafetyGateMarkdown(SPEC-0090)serializePhashSignature,parsePhashSignature(SPEC-0062)AssetEmbeddingIndexclass (SPEC-0087)normalizeInput,withNormalizedInput,toBytes(shared)SDK_VERSION(string)
See the source of packages/sdk/src/index.ts for the full re-export
manifest.
Spec
The SDK shape is locked by SPEC-0095.
The companion runtime SDK (@spatialpack/runtime, browser-side LOD +
progressive + imposter) is SPEC-0081.
Versioning
SDK_VERSION is exported as a constant. The SDK and @spatialpack/core
ship together; pin both to the same minor version.
See CHANGELOG.md for the version-by-version surface delta.
Contributing
The SDK lives in the SpatialPack monorepo
at packages/sdk/. To work on it locally:
git clone https://github.com/montabano1/SpatialPack.git
cd SpatialPack
pnpm install
pnpm --filter @spatialpack/core build
pnpm --filter @spatialpack/sdk build
pnpm --filter @spatialpack/sdk testOpen an issue at https://github.com/montabano1/SpatialPack/issues before sending non-trivial PRs so we can keep the spec catalog authoritative.
License
MIT © Michael Montalbano.
