npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@helix3/helix-sdk

v0.1.6-helix3.116

Published

HELIX Instant SDK — identity (and later multiplayer/voice/wallet) for worlds running on HELIX Instant

Readme

@hypersoniclabs/helix-sdk

The HELIX Instant browser SDK — the runtime a world embeds to talk to the HELIX shell (the play page that iframes it). v0.1 provides identity; later versions add multiplayer, voice, wallet, and inventory.

A world is a static bundle that runs inside a sandboxed iframe. The shell hands it a short-lived, world-scoped session token (minted by the backend at POST /api/v1/instant-worlds/:worldId/session) over postMessage. The SDK wraps that handshake.

Install

npm install @hypersoniclabs/helix-sdk

Usage

import { Helix } from '@hypersoniclabs/helix-sdk';

const { embedded, world, user, runtimeFidelity } = await Helix.init(); // call once, at startup

// Pass the versioned intent to the engine's canonical fidelity resolver. The
// SDK never detects the device or derives subsystem budgets itself.
engine.resolveFidelity(runtimeFidelity);

if (Helix.auth.isAuthenticated()) {
  const me = await Helix.auth.getUser(); // { id, username, displayName }
}

// Raise the shell's login overlay (no-op reload — world state is preserved):
loginButton.onclick = async () => {
  try {
    const user = await Helix.auth.requestLogin();
    console.log('signed in as', user.username);
  } catch {
    /* dismissed or unavailable */
  }
};

Helix.auth.onAuthChanged((user) => updateUi(user)); // login + logout

// Ask the HELIX shell to render native status notifications.
Helix.notify.success('Prize claimed', 'Welcome Chip added to inventory');
Helix.notify.failure('Purchase failed', 'Not enough LIX');
Helix.notify.notification('Quest updated', 'Return to the VIP lounge');
Helix.notify.message('Nova', 'Meet me by the arcade machines');

// Publish a current interaction prompt without tying your world to a renderer.
Helix.prompts.set({
  id: 'fortune-arcade.vip',
  title: 'VIP Room',
  description: 'Buy a keycard to enter.',
  input: { key: 'E', label: 'Interact' },
  actionLabel: 'Buy keycard',
  state: 'available',
});
Helix.prompts.clear('fortune-arcade.vip');

When the world is opened directly (e.g. a local vite dev server with no shell), init() resolves with embedded: false and all identity APIs return null/false — so the same build runs locally and embedded.

API

  • Helix.init(): Promise<{ embedded, world, user, runtimeFidelity }> — handshake; call once before anything else. runtimeFidelity is the validated versioned presentation intent, or null for an older shell or malformed payload; in that case the engine retains its Auto fallback.
  • Helix.auth.getUser(): Promise<HelixUser | null>
  • Helix.auth.isAuthenticated(): boolean
  • Helix.auth.requestLogin(): Promise<HelixUser> — resolves on login, rejects on dismiss/unavailable.
  • Helix.auth.onAuthChanged(fn): () => void — fires on login and logout; returns an unsubscribe fn.
  • Helix.notify.show({ kind, title, message?, timeoutMs?, actionLabel?, actionHref? }): boolean
  • Helix.notify.success|failure|notification|message(title, message?): boolean — returns false when the world is running standalone without a shell.
  • Helix.prompts.set({ id, title, description?, input?, actionLabel?, state?, priority? }): boolean
  • Helix.prompts.clear(promptId?): boolean
  • Helix.getSessionToken(): string | null — the world-scoped token, for advanced direct API calls.

Economy & items (v2, shell-mediated)

All of these are server-authoritative: the world requests, the shell (which holds the player's session) settles with the backend and renders the purchase popup. Reads degrade to empty defaults when not embedded; a purchase returns Unauthorized (never a fake success).

const { lix, coins } = await Helix.wallet.getBalance();
Helix.wallet.onBalanceChanged((b) => updateHud(b));

// World code names a registered distribution key; the shell shows Claim/Buy.
const res = await Helix.marketplace.purchaseDistributionKey('harbor_postcard', { quantity: 2 });
if (res.completed) displaySouvenirs(res.fulfilledQuantity ?? 0); // Granted | Claimed | AlreadyOwned
else showToast(res.status);                            // InsufficientFunds | Cancelled | …

await Helix.marketplace.purchaseProduct('golden_key'); // stable world-product key -> pkey:golden_key
await Helix.marketplace.purchaseDistribution('public_distribution_id'); // exact public primary route
await Helix.marketplace.purchaseListing('listing_for_serial_12');        // exact secondary serial
const items = await Helix.marketplace.getListings({ kind: 'home_item' });

const context = await Helix.marketplace.getPurchaseContext('dkey:harbor_postcard', { quantity: 2 });
// The shell/API own price, edition, fixed supply, remaining count and next serial.

// Ownership gating — works across worlds & creators (VIP / season passes).
if (await Helix.inventory.hasItem('vip_pass_001')) openBackstage();
const owned = await Helix.inventory.getMyItems();
await Helix.inventory.equipItem('vip_hat_001');
Helix.inventory.onInventoryChanged(refreshInventory);
  • Helix.wallet.getBalance() / onBalanceChanged(fn)
  • Helix.marketplace.purchaseDistributionKey(key, options?) / purchaseDistribution(id, options?) / purchaseListing(id)
  • Helix.marketplace.purchaseProduct(productKey) for world-local products/effects; it does not mint universal items or accept an internal product UUID
  • Helix.marketplace.getListings(query?) / getPurchaseContext(ref, options?)
  • Helix.inventory.hasItem(id) / getQuantity(id) / getMyItems() / equipItem(id) / onInventoryChanged(fn)

The SDK mints a stable idempotency key per purchase (reused across the popup's retries) so a dropped network can never double-charge. Item-definition ids are deliberately not purchase references: use only dkey:, distribution: or listing: through the typed methods above. See the backend /api/v1/iwp/* endpoints for the settlement layer.

Collectible acquisition results expose resaleEligibleAt; exact listing context exposes a separate seller holdUntil. The unified relisting cooldown is 24 hours after every acquisition. Basic requires email; verified means unique verified mobile plus good standing. The type also accepts reserved identity_verified and future business_verified. Payout holds are 3 days base and may be risk-extended up to 15 days; never treat the relisting timestamp as payout availability.

Interaction prompts

Helix.prompts is intentionally engine-agnostic. A Three.js, Phaser, Unity WebGL, or plain DOM world can all publish the same contract: a stable prompt id, display text, optional keyboard/gamepad labels, and a coarse state (default, available, locked, or busy). The shell decides how to render it.

Rich presence

Push a live activity for the local player that enriches the baseline "In [World]" their friends already see (in the friends sidebar and on their profile) — e.g. Racing — lap 3/5. It's dynamic: call it whenever the moment changes, no registration, no per-activity setup.

// Enrich presence with whatever the player is doing right now.
await Helix.presence.setActivity({
  state: 'Racing — lap 3/5',       // required, ≤128 chars — the line friends see
  details: 'Sunset Circuit',        // optional second line, ≤128 chars
  party: { current: 2, max: 8 },    // optional party sizing
  // image: 'my-mode-icon',         // optional; omitted → the world's thumbnail (zero-config)
  // joinable: true,                // hint only; joining an instance ships with the MP runtime
});

// e.g. drive it off game state
raceEvents.on('lap', (n, total) =>
  Helix.presence.setActivity({ state: `Racing — lap ${n}/${total}` }),
);

// Back to plain "In [World]". Also happens automatically when the player leaves.
await Helix.presence.clearActivity();
  • Helix.presence.setActivity(activity)Promise<boolean>true on success; false for guests, outside a shell, when not in a world, or when rejected. Never throws into your game loop.
  • Helix.presence.clearActivity()Promise<boolean>.

Server-authoritative (by design). setActivity posts to the authenticated presence endpoint using the player's own session. The server sets activity only for that player and only for the world they're currently in — a world can never write another player's status, or a world they aren't in. This is the interim of the "the world's server sets it" model: when the multiplayer room runtime lands it will set activity server-to-server after authenticating the player into its instance; the API and validation are already shaped for that. Backend: POST / DELETE /api/v1/users/me/presence/activity.

Privacy & safety. Activity is governed by the player's presence privacy (visibility Everyone / Friends only / Nobody, plus a hide-activity toggle) — if they've opted out, the server never emits it, to anyone. Text is length-capped, rate-limited, and screened by the shared text moderation layer server-side (a bad-actor world can't push junk into a friend's status card).

Durable storage

Helix.dataStore is per-world key–value storage — the place for anything that must survive a session. A player's own session may write exactly one key, player:${user.id}, so put the whole save in that single JSON document; sub-keys, world keys and room state are written by the game server. Reads are world-open point-gets, and list returns keys only — values are never handed out in bulk.

await Helix.dataStore.set(`player:${user.id}`, { level: 4, coins: 120 });
const save = await Helix.dataStore.get<Save>(`player:${user.id}`);       // null when unset

// Optimistic concurrency — read the version, hand it back, and a losing race is refused
// instead of silently clobbering the other writer.
const entry = await Helix.dataStore.getEntry<Save>(`player:${user.id}`); // { value, version } | null
const next = { ...(entry?.value ?? fresh), coins: (entry?.value.coins ?? 0) + 10 };
await Helix.dataStore.set(`player:${user.id}`, next, { expectedVersion: entry?.version ?? 0 });
  • Helix.dataStore.get<T>(key)Promise<T | null>null when unset, and in preview mode.
  • Helix.dataStore.getEntry<T>(key)Promise<{ value: T; version: number } | null>.
  • Helix.dataStore.set<T>(key, value, { expectedVersion? })Promise<void>expectedVersion: 0 asserts create-only. Omit it and the write is last-writer-wins.
  • Helix.dataStore.delete(key, { expectedVersion? })Promise<void>.
  • Helix.dataStore.list(prefix?, { cursor?, limit? })Promise<{ keys: string[]; nextCursor?: string }> — keys only, one page (≤100) per call; pass nextCursor back as cursor for the next page.

There is no multi-key transaction: design so no two keys must move together. In preview mode reads return null/empty and writes no-op.

Leaderboards

Helix.leaderboard is keep-best per player, decided atomically by the backend — a worse score is a harmless no-op, so resubmitting is always safe. Each board is ONE bounded document keeping its declared top size (up to 100): scores below the cutoff are not stored, and best comes back null when the player is off the board. Boards are declared per build (one must be named main — the world's default board) and are usually written by the authoritative room; a session submits only for itself.

const { best, updated } = await Helix.leaderboard.submit('bestTime', lapMs, { order: 'asc' });
if (!updated) showToast(best === null ? 'Didn\'t place' : `Your record of ${best} stands`);

const { entries, truncated } = await Helix.leaderboard.top('bestTime', { limit: 10, order: 'asc' });
for (const e of entries) addRow(e.displayName, e.score);
  • Helix.leaderboard.submit(board, score, { order? })Promise<{ best, updated }>order ('desc' default, 'asc' for times) only decides direction while the board is new.
  • Helix.leaderboard.top(board, { limit?, order? })Promise<{ entries, truncated }>; limit ≤ 100. Each entry is { subject, displayName, score, source, updatedAt }, where source is 'session' (client-claimed) or 'room' (written by the authoritative room).
  • Both need a shell — in preview mode they reject rather than fake a board.

Achievements

Helix.achievements is a read-only view of the achievements registered to this world and whether the viewer has earned them — the world id comes from the shell, so a world can never enumerate the viewer's earns anywhere else. Awarding is server-side only: write-time criteria, or the multiplayer room's awardAchievement rule. There is no award/claim method here by design.

const { achievements, total, earned } = await Helix.achievements.list();
setProgressLabel(`${earned}/${total} earned here`);
for (const a of achievements) addBadge(a.name, a.iconUrl, a.earned, a.points);

const mine = await Helix.achievements.mine();   // the earned-only subset for this world
  • Helix.achievements.list()Promise<{ achievements, total, earned }> — every achievement of this world; total is the world's count, earned how many the viewer holds (0 when signed out).
  • Helix.achievements.mine()Promise<{ achievements }> — the earned ones only. Empty for a signed-out player (they have earned nothing).
  • Each row is { id, key, name, description, points, hidden, earned, rarityTier, earnPct, iconUrl }. A hidden achievement the viewer has not earned arrives redacted — the shell replaces name/description with placeholders and blanks key/iconUrl, so neither a hidden name nor the slug that spells it reaches world code. Identify such a row by id.
  • Preview mode (no shell) resolves neutral empties rather than rejecting — this is display data and must never break the world loop. A real backend failure still rejects (see below).

Failures carry a code. A rejected call throws a HelixRequestError whose code classifies it, so a world branches on the code rather than matching error prose: version-conflict (stale expectedVersion — re-read and re-apply), rate-limited, session-foreign-key (another player's key), session-sub-key (a key under your own — use the one document), session-server-key, platform-reserved (helix:*), prefix-reserved (mail:*, mp:world, leaderboard:*), counter-server-only, leaderboard-self-only. code is undefined when the shell is older than the code it would have sent, or the failure has no classification.

Protocol

src/protocol.ts is the wire contract (postMessage messages between world and shell) and is imported by both sides. Any breaking change must bump PROTOCOL_VERSION. The HelixSession shape matches the backend's InstantWorldSessionResponseDto, so the shell forwards the backend response verbatim.

Engine-neutral runtime contract

The public Web surface is also captured in one generated, engine-neutral contract for Web, Unreal and future bindings. Import the typed metadata without changing existing SDK behavior:

import {
  HELIX_RUNTIME_CONTRACT,
  RUNTIME_CONTRACT_DIGEST,
  getRuntimeMethod,
} from '@hypersoniclabs/helix-sdk/runtime-contract';

const purchase = getRuntimeMethod('marketplace.purchaseProduct');
// purchase.transportOwner === 'shell'
// purchase.authority === 'platform'

The canonical source, generated C++ header, known-answer fixtures, authority rules and update workflow are documented in docs/runtime-contract.md. Audited broken or authority-drifted calls are explicitly unavailable or correction_required; generators must not reproduce their current false-success behavior.

Native avatar camera

Helix.camera carries two separate things. capture/savePhoto are the album pipeline: pixels a world already rendered, on their way to the player's account, gated on the camera.capture manifest capability. getState/onStateChanged/capabilities/reportCapability/control are the CAMERA — the device the player's avatar carries, which HELIX OS opens.

HELIX OS owns the camera UI and pushes the whole state down; the runtime owns the rig and reports what it can actually do back up.

// The runtime declares its abilities at boot, and again whenever one is LOST.
Helix.camera.reportCapability({
  photo: true, video: false, drone: false, twoHandPose: true,
  focusPoint: true, exposureBias: false, worldAudio: false,
  unavailableReason: 'this world ships photo only',
});

Helix.camera.onStateChanged((state) => {
  // state.shutter.sequence is monotonic — fire on the CHANGE, never on a boolean.
  applyCamera(state.spatial, state.zoom, state.filter);
});

// A world script driving the player's camera needs the `camera.control` capability, and a call the
// runtime already said it cannot serve fails locally with RUNTIME_CAPABILITY_UNAVAILABLE.
await Helix.camera.control({ spatial: 'selfie', zoom: 2.4, filter: 'vivid' });

Reading the camera never requires a world manifest capability — it is an avatar ability, not something the world asks for. The ownership split, the dynamic-capability model, the drone's Phase-2 status, and the reasoning behind that permission boundary are in docs/camera-contract.md.

Universal interaction action surface

UniversalActionSurface renders the shared run, choice, number, text, point, asset, and takeover forms from a caller-scoped action snapshot. Labels, options, visibility, permissions, and busy/occupied state come from authority; the snapshot is only a presentation cache, so every invocation must still be re-authorized server-side.

import {
  UniversalActionSurface,
  createActionSurfaceMode,
} from '@hypersoniclabs/helix-sdk';

const surface = new UniversalActionSurface({
  host: document.querySelector('#ui')!,
  hints: {
    activeDevice: () => character.services.input.activeDevice(),
    hint: (action) => character.services.input.hint(action),
  },
  onInvoke: ({ action, ...value }) => {
    // Send action.call ?? action.id plus value to authority. Re-authorize there.
  },
});

surface.setSnapshot(await actionsForNearbyItem());

// Proximity may show the offer without capturing movement. Push the engaged
// menu mode only after the player chooses to focus it.
await modeStack.push(createActionSurfaceMode({ surface }));

The mode consumes only move, primary, and cancel; it never installs keyboard, gamepad, or raw-touch bindings. A host whose input router has no change subscription calls refreshHints() after an active-device or rebind change. Sustained number changes stream live while the native range remains open, native text input preserves focus across authority refreshes, and choice/asset pickers keep the saved value visually distinct from keyboard/gamepad focus. The port source is the frozen interactive-items prototype's src/client/ui.ts; new development lives here.

DeviceDefinitionV1.interactions uses the same portable forms. A Device text control declares its typed stateKey plus optional placeholder, maxLength, and Action Surface inputMode; the enclosing interaction label is the player-facing prompt. Submitted text goes through normal Device authority, which must validate it before writing state.

Vault assets

The Vault client lets a web world search and consume all 14 resource kinds without coupling the SDK to Three.js or another renderer. Persist the returned { assetId, version } handle. Never persist artifact.url: it is a resolved serving pointer, while the Vault ID and immutable integer version are the durable identity.

import { createVaultClient } from '@hypersoniclabs/helix-sdk';

const vault = createVaultClient({
  apiBaseUrl: 'https://api.helixgame.com',
  accessToken: () => Helix.getSessionToken(),
});

const result = await vault.search({
  q: 'weathered stone',
  kind: ['material', 'texture'],
  performanceTier: 'mobile',
  rank: 'performance',
});

const asset = await vault.resolve({
  assetId: result.items[0].assetId,
  version: result.items[0].currentVersion,
});
const plan = vault.createLoadPlan(asset);
const installed = await vault.install(asset.handle);

// `plan` is a typed handoff such as:
// - gltf (prop, character, animation, environment, terrain)
// - image (texture, decal)
// - audio
// - hdr-environment (HDR/EXR)
// - gaussian-splat (SPZ, with object/environment scope)
// - helix-descriptor (material, sky, terrain, VFX, scene)
//
// Pass `installed.primary.bytes` and `plan` to the world's chosen loader.

Downloads follow the Vault's version-aware download route and verify the server-provided SHA-256 by default when Web Crypto is available. A runtime without Web Crypto may inject a portable sha256 function. Related renditions are resolved from asset detail and downloaded only when their roles are explicitly requested:

await vault.install(asset.handle, {
  relatedRoles: ['ktx2-albedo', 'ktx2-normal'],
});

The SDK deliberately does not report a client-side "usage" event. Vault usage attribution is authoritative and derived when a world containing durable Vault IDs is published. There is currently no separate install, usage-track, or related-rendition API endpoint; install() is the local verified-download plus loader-plan operation, and related artifacts come from asset detail.

Generate audio and consume the published Vault asset

Creator tools can use the SDK's typed Dreamer audio client with an authenticated creator/client token. Sound effects, music, and text to speech all call the same HELIX route; the SDK never calls ElevenLabs directly or handles a provider key. Completed jobs are automatically published to Vault unless the creator has explicitly opted out.

import { createDreamerAudioClient } from '@hypersoniclabs/helix-sdk';

const audio = createDreamerAudioClient({
  apiBaseUrl: 'https://api.helixgame.com',
  accessToken: creatorToken,
});

const effect = await audio.generate({
  audioMode: 'sound_effect',
  prompt: 'heavy metal door slamming shut',
  durationSeconds: 3,
  promptInfluence: 0.4,
});

const music = await audio.generate({
  audioMode: 'music',
  prompt: 'hopeful orchestral exploration theme',
  durationSeconds: 180,
  forceInstrumental: true,
});

const voice = await audio.generateAndResolve({
  audioMode: 'text_to_speech',
  text: 'Welcome to HELIX.',
  voiceId: '21m00Tcm4TlvDq8ikWAM',
  languageCode: 'en',
  voiceSettings: { stability: 0.5, speakerBoost: true },
});

// The exact auto-published asset is now resolved through the normal Vault path.
audioElement.src = voice.loadPlan.primary.url!;
console.log(effect.vaultAssetId, music.vaultAssetId, voice.resolved.handle);

generate() polls for up to 30 minutes by default so long music generation has time to render, ingest, and publish. It returns measured units, providerCostUsd, server-stamped billingEvidence, and vaultAssetId. generateAndResolve() additionally requires default-on Vault publication and returns the renderer-neutral audio load plan for that same durable asset.

Develop

npm install
npm test       # jest + jsdom
npm run build  # tsc → dist/ (ESM)
npm run lint