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

@adhd/sox-embedding-provider

v0.5.0

Published

Pluggable text→vector embedding provider — generic EmbeddingProvider interface, config-driven model resolution, async batch-first API (AsyncIterable). Default: fastembed (local ONNX, >=3 model dims proven). Loud-fail: createEmbeddingProvider() throws Reso

Readme

@adhd/sox-embedding-provider

A pluggable text→vector embedding provider. One EmbeddingProvider interface, one createEmbeddingProvider(config) factory, config-driven model resolution, and an async batch-first API (AsyncIterable<Float32Array>) so a caller gets the first vector back before the last one in a large batch finishes computing. The default provider is fastembed — local ONNX inference, no network calls, no API key. A remote provider is also built in for calling an HTTP embedding endpoint. createEmbeddingProvider() throws a typed ResolutionError synchronously (or as a rejection) if the config is invalid or the model fails to load — it never silently degrades to a different provider.

pnpm add @adhd/sox-embedding-provider

Quick start

import { createEmbeddingProvider } from '@adhd/sox-embedding-provider';

const provider = await createEmbeddingProvider({
  type: 'fastembed',
  model: 'bge-small-en-v1.5',
});

console.log(provider.metadata);
// { modelId: 'bge-small-en-v1.5', dimensions: 384, maxTokens: 512, isRemote: false, isDeterministic: false }

const vec = await provider.embedSingle('The quick brown fox jumps over the lazy dog');
console.log(vec.length); // 384

for await (const v of provider.embedBatch(['first document', 'second document', 'third document'])) {
  console.log(v.length); // 384, one vector per input text, streamed as each is ready
}

API reference

createEmbeddingProvider(config): Promise<EmbeddingProvider>

interface EmbeddingProviderConfig {
  type: string;    // 'fastembed' | 'remote'
  model: string;
  options?: Record<string, unknown>;
}

Throws ResolutionError if type is missing/unknown, or if the model/runtime cannot be loaded. Never throws mid-call afterward — resolution failures happen only at factory time.

EmbeddingProvider

interface EmbeddingProvider {
  readonly metadata: EmbeddingProviderMetadata;
  embedSingle(text: string, role?: EmbedRole): Promise<Float32Array>;
  embedBatch(texts: string[], opts?: { role?: EmbedRole; batchSize?: number }): AsyncIterable<Float32Array>;
  warmUp(texts: string[]): Promise<void>;
  health(): EmbeddingHealth;
}

type EmbedRole = 'document' | 'query';

interface EmbeddingProviderMetadata {
  modelId: string;
  dimensions: number;
  maxTokens: number;
  isRemote: boolean;
  isDeterministic: boolean;
  providerUri?: string;
}

interface EmbeddingHealth {
  configured: string;       // e.g. 'fastembed:bge-base-en-v1.5'
  active: string | null;    // null until warm — never a placeholder model name
  state: 'uninitialized' | 'warming' | 'real' | 'error';
  dimensions: number | null;
  last_error: string | null;
  execution_provider?: string;
}

Every provider advertises its dimensions via metadata — never hardcode a vector dimension for a model; read it from the provider you constructed. warmUp() is currently a no-op on every shipped provider (fastembed and remote both report isDeterministic: false, and warm caching is only meaningful for deterministic providers) — it's safe to call, but don't rely on it to pre-populate a cache today.

fastembed — local ONNX models

const provider = await createEmbeddingProvider({
  type: 'fastembed',
  model: 'bge-m3',              // defaults to 'bge-base-en-v1.5' if omitted
  options: { cacheDir: '/custom/model/cache/path' },  // optional; falls back to $SOX_EMBED_CACHE_DIR, then ~/.cache/sox/models
});

Built-in models (modelId → dimensions):

| modelId | dim | max tokens | notes | |---|---|---|---| | bge-small-en-v1.5 | 384 | 512 | lightweight, ~33M params | | bge-base-en-v1.5 | 768 | 512 | balanced, ~110M params (default) | | multilingual-e5-large | 1024 | 512 | 100+ languages, ~335M params | | bge-m3 | 1024 | 8192 | 100+ languages, long context, ~570M params | | codexembed-400m | 1024 | 8192 | code-only, ~1.6GB RAM, long context |

fastembed inference is routed through a single process-wide shared child process rather than a worker per provider instance, so multiple FastembedProviders constructed in the same Node process (even concurrently) never race each other for the same native ONNX runtime.

remote — call an HTTP embedding endpoint

const provider = await createEmbeddingProvider({
  type: 'remote',
  model: 'remote-768',
  options: {
    endpoint: 'https://your-embedding-service.example.com/v1',
    apiKey: process.env.EMBEDDING_API_KEY,
    dimensions: 768,  // optional, default 768
  },
});

options.endpoint is required — createEmbeddingProvider throws ResolutionError without it.

Error taxonomy

class TransientEmbeddingError extends Error { readonly retryAfterMs?: number; }
class PermanentEmbeddingError extends Error {}
class ResolutionError extends Error {}
  • ResolutionError — factory-time only (bad config, model can't load). Never thrown mid-call.
  • TransientEmbeddingError — the caller may retry (e.g. a transient network hiccup on a remote call).
  • PermanentEmbeddingError — the caller must not retry (e.g. empty input text, an invalid API key shape).
import { TransientEmbeddingError, PermanentEmbeddingError } from '@adhd/sox-embedding-provider';

try {
  await provider.embedSingle(text);
} catch (err) {
  if (err instanceof TransientEmbeddingError) {
    // back off and retry, honoring err.retryAfterMs if present
  } else if (err instanceof PermanentEmbeddingError) {
    // do not retry — the input or config itself is the problem
  } else {
    throw err;
  }
}

Invariants

  • createEmbeddingProvider() throws ResolutionError synchronously or as a rejection if the config is invalid or the model/runtime cannot load — never a silent downgrade to another provider.
  • Every provider advertises { modelId, dimensions, isRemote, isDeterministic, providerUri? } via metadata — callers never hardcode dimensions.
  • embedBatch() returns AsyncIterable<Float32Array> — callers receive the first result before the last batch finishes, which matters for sequential local inference.
  • warmUp() is a no-op when isDeterministic === false — currently always true, since every shipped provider hard-codes isDeterministic: false.
  • TransientEmbeddingError → caller may retry; PermanentEmbeddingError → caller must not retry; ResolutionError → factory-time only, never thrown mid-call.