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

@tilawa/core

v0.1.0

Published

Offline Quran verse recognition. Give it 16kHz audio, get surah:ayah. Pure TypeScript core with a pluggable ONNX runtime seam — works in web, node, and React Native.

Readme

Tilawa

Formerly called offline-tarteel.

Maintained by auto-maintainer

Offline Quran recognition. Give it 16 kHz mono audio, get back surah:ayah. Fully on-device — web, mobile, or node, no network at inference time.

@tilawa/core is pure TypeScript with zero native dependencies. You inject an ONNX session behind a small SessionRunner interface, so the same package works everywhere by swapping which onnxruntime you wire in.

graph LR
  dev[Your app] -->|injects ort session| runner[SessionRunner]
  runner --> core["@tilawa/core: CTC decode + QuranDB + tracker"]
  core --> result["surah / ayah / transcript"]

Install

npm i @tilawa/core
# plus the onnxruntime for your platform (you own this dep):
npm i onnxruntime-web            # browser / WASM
npm i onnxruntime-node           # node
npm i onnxruntime-react-native   # React Native

Then download the model + text assets from GitHub Releases:

base=https://github.com/yazinsai/tilawa/releases/download/v0.2.0

curl -L -O "$base/fastconformer_full_mixed.onnx"  # 88 MB — goes into your SessionRunner
curl -L -O "$base/vocab.json"                      # TilawaAssets.vocab
curl -L -O "$base/quran_ctc_tokens.json"           # TilawaAssets.quranCtcTokens

Plus quran.json (all 6,236 verses) from web/frontend/public/quran.jsonTilawaAssets.quran.

Quickstart

The core never imports onnxruntime — you write a ~20-line SessionRunner that owns the ort dependency, then hand it to createTilawaSession along with the three JSON assets. Copy-paste adapters for each runtime live in packages/core/examples/.

Web (onnxruntime-web / WASM)

import * as ort from "onnxruntime-web/wasm";
import { createTilawaSession, type SessionRunner } from "@tilawa/core";

async function createWebSessionRunner(modelBuffer: ArrayBuffer): Promise<SessionRunner> {
  ort.env.wasm.numThreads = 1; // single-threaded is the reliable default
  ort.env.wasm.simd = true;
  const session = await ort.InferenceSession.create(modelBuffer, {
    executionProviders: ["wasm"],
  });
  return {
    async run(audio) {
      const input = new ort.Tensor("float32", audio, [1, audio.length]);
      const length = new ort.Tensor("int64", BigInt64Array.from([BigInt(audio.length)]), [1]);
      const results = await session.run({ audio_signal: input, length });
      const output = results[session.outputNames[0]];
      const [, timeSteps, vocabSize] = output.dims as number[];
      return { logprobs: output.data as Float32Array, timeSteps, vocabSize };
    },
  };
}

const runner = await createWebSessionRunner(modelBuffer);
const session = createTilawaSession(runner, { vocab, quranCtcTokens, quran });

const pred = await session.transcribe(audioFloat32); // 16 kHz mono
// { surah: 1, ayah: 1, ayah_end: 3, score: 0.92, transcript: "..." }

Node (onnxruntime-node)

import { readFile } from "node:fs/promises";
import * as ort from "onnxruntime-node";
import { createTilawaSession, type SessionRunner } from "@tilawa/core";

async function createNodeSessionRunner(modelBuffer: Uint8Array): Promise<SessionRunner> {
  const session = await ort.InferenceSession.create(modelBuffer);
  return {
    async run(audio) {
      const input = new ort.Tensor("float32", audio, [1, audio.length]);
      const length = new ort.Tensor("int64", BigInt64Array.from([BigInt(audio.length)]), [1]);
      const results = await session.run({ audio_signal: input, length });
      const output = results[session.outputNames[0]];
      const [, timeSteps, vocabSize] = output.dims as number[];
      return { logprobs: output.data as Float32Array, timeSteps, vocabSize };
    },
  };
}

const runner = await createNodeSessionRunner(await readFile("fastconformer_full_mixed.onnx"));
const session = createTilawaSession(runner, { vocab, quranCtcTokens, quran });
const pred = await session.transcribe(audioFloat32);

React Native (onnxruntime-react-native)

RN can't hand the model to ORT as an ArrayBuffer — bundle the .onnx as an asset, copy it to the documents dir, and pass the file path.

import * as ort from "onnxruntime-react-native";
import { createTilawaSession, type SessionRunner } from "@tilawa/core";

async function createRNSessionRunner(modelPath: string): Promise<SessionRunner> {
  const session = await ort.InferenceSession.create(modelPath);
  return {
    async run(audio) {
      const input = new ort.Tensor("float32", audio, [1, audio.length]);
      const length = new ort.Tensor("int64", BigInt64Array.from([BigInt(audio.length)]), [1]);
      const results = await session.run({ audio_signal: input, length });
      const output = results[session.outputNames[0]];
      const [, timeSteps, vocabSize] = output.dims as number[];
      return { logprobs: output.data as Float32Array, timeSteps, vocabSize };
    },
  };
}

const runner = await createRNSessionRunner(modelPath);
const session = createTilawaSession(runner, { vocab, quranCtcTokens, quran });
const pred = await session.transcribe(audioFloat32);

Streaming (live recitation)

For live recitation, feed audio chunks with feed(). The built-in tracker emits verse matches, candidates, and word-level progress as the reciter speaks. Subscribe via onOutput, or use the messages feed() returns.

const session = createTilawaSession(runner, assets, {
  config: "balanced", // or a Partial<StreamingConfig>
  onOutput: (msg) => {
    switch (msg.type) {
      case "verse_match":
        console.log(`${msg.surah}:${msg.ayah}`, msg.verse_text, msg.confidence);
        break;
      case "word_progress":
        console.log(`word ${msg.word_index}/${msg.total_words}`);
        break;
      case "final_sequence":
        console.log("done", msg.verses);
        break;
    }
  },
});

// push ~300ms Float32 chunks as they arrive from the mic
for await (const chunk of micChunks) await session.feed(chunk);

session.reset(); // start a new recitation

onOutput receives a WorkerOutbound union. The ones you care about:

| msg.type | Meaning | Key fields | |---|---|---| | verse_match | Confident match for the current verse | surah, ayah, verse_text, surah_name, confidence, surrounding_verses | | verse_candidate | Ranked candidates before lock-in | candidates[], stable, final_flush | | word_progress | Word-level alignment within a verse | surah, ayah, word_index, total_words, matched_indices | | final_sequence | Full ordered sequence when recitation ends | verses[], confidence |

API reference

createTilawaSession(runner: SessionRunner, assets: TilawaAssets, options?): TilawaSession

TilawaAssets — the JSON blobs you load (model bytes go into the runner, not here):

interface TilawaAssets {
  vocab: Record<string, string>;      // vocab.json (CTC token id -> string)
  quranCtcTokens: CtcTokenTable;       // quran_ctc_tokens.json
  quran: unknown[];                    // quran.json (6,236 verse records)
  blankId?: number;                    // defaults to 1024
}

SessionRunner — the seam you implement (see quickstarts):

interface SessionRunner {
  run(audio: Float32Array): Promise<{
    logprobs: Float32Array;
    timeSteps: number;
    vocabSize: number;
  }>;
}

TilawaSession — what you get back:

| Method | Purpose | |---|---| | transcribe(audio) | One-shot → { surah, ayah, ayah_end, score, transcript } (0/0 on no match) | | transcribeRaw(audio) | One-shot → full TranscribeResult (acoustic logprobs + champion match) | | feed(chunk) | Streaming → WorkerOutbound[], also fired via onOutput | | reset() | Reset the streaming tracker for a new recitation | | setConfig(partial) | Update streaming config live | | getConfig() | Current effective StreamingConfig | | db | Underlying QuranDB (verse lookup, search) | | decoder | Underlying TextCTCDecoder |

Streaming config — pass a preset name or a Partial<StreamingConfig> via options.config:

  • "conservative" — waits for strong evidence before locking a verse (fewer false matches)
  • "balanced" — the default (DEFAULT_STREAMING_CONFIG)
  • "aggressiveAdvance" — advances quickly, best for continuous recitation

The full StreamingConfig interface, the three presets (CONSERVATIVE_STREAMING_CONFIG, BALANCED_STREAMING_CONFIG, AGGRESSIVE_ADVANCE_STREAMING_CONFIG), and every exported type are re-exported from @tilawa/core.

Model

| | Value | |---|---| | Model | Cyberistic's c2c-direct-mixed-tta (base: nvidia/stt_ar_fastconformer_hybrid_large_pcd_v1.0) | | File | fastconformer_full_mixed.onnx — 88 MB, int4 MatMul + int8 Conv/LayerNorm | | Input | 16 kHz mono audio, Float32Array (preprocessing baked into the graph) | | Output | CTC logprobs over 1025-token Arabic BPE vocab, then CTC re-rank against Quran candidates | | Recall / Precision / SeqAcc | 100% / 100% / 100% on the v1 53-sample benchmark (median of 3 runs) | | Latency | 0.84s average on Apple Silicon CPU | | License | CC-BY-4.0 (NVIDIA model) |

Live demo

web/frontend/ is a complete browser app that runs the SDK live — record and watch verses lock in in real time. It's also the regression guard for the SDK.

cd web/frontend && npm run dev

Research & benchmarks

The model behind this SDK is the winner of a 20-approach bake-off (Whisper variants, pruned CTC, FastConformer sweeps, contrastive/embedding attempts). All of that — the Python benchmark harness, experiment code, training scripts, and per-approach writeups — lives under lab/. Start with lab/EXPERIMENTS.md and lab/AGENTS.md.