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.2.1

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 the ONNX runtime.

Licence split: package code is MIT. The Zipformer model and phoneme corpus are NPL-1.2 (non-commercial, share-alike). FastConformer assets are MIT / NVIDIA CC-BY-4.0. Details: NOTICE.md.

Two engines ship in the box. The default is Zipformer — streaming Zipformer2-CTC over a 251-token tajweed-phoneme vocabulary. FastConformer (text CTC) is still there under its original API.

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

Default-engine assets from release v0.3.0:

base=https://github.com/yazinsai/tilawa/releases/download/v0.3.0
curl -L -O "$base/zipformer_interp_gentle_a05.int8.onnx"  # 66 MB
curl -L -O "$base/zipformer_quran.json"                    # 5.5 MB, NPL-1.2
# optional — Arabic text on verse_match events
curl -L -O https://github.com/yazinsai/tilawa/releases/download/v0.2.0/quran.json

The model's I/O manifest is bundled (DEFAULT_ZIPFORMER_IO). quran.json is display text only; matching works without it.

Browser

import * as ort from "onnxruntime-web";
// `onnxruntime-web/wasm` works too
import { createRecognitionSession } from "@tilawa/core";

// Vite copies `*.wasm` into the bundle by default. A raw <script type=module>
// or a bundler that doesn't should set:
// ort.env.wasm.wasmPaths = "https://cdn.jsdelivr.net/npm/[email protected]/dist/";

const session = await createRecognitionSession({
  ort,
  model: () => fetch("/zipformer_interp_gentle_a05.int8.onnx").then((r) => r.arrayBuffer()),
  corpus: () => fetch("/zipformer_quran.json").then((r) => r.json()),
  quran: () => fetch("/quran.json").then((r) => r.json()), // optional
  onEvent: (msg) => {
    if (msg.type === "verse_match") console.log(`${msg.surah}:${msg.ayah}`, msg.verse_text);
  },
});

for await (const chunk of micChunks) await session.feed(chunk);
const final = await session.stop();
session.reset();

Passing ort + model picks the provider automatically: ["wasm"] under onnxruntime-web, ["cpu"] under onnxruntime-node. Override with executionProviders. On web we also set ort.env.wasm.numThreads = 1 unless you already set it — pthread init hangs in workers without COOP/COEP; single-thread is what the demo ships.

createZipformerSession(opts) is the same thing without the engine switch, and returns the richer ZipformerSession (transcript, verses, engineState, …).

Node

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

const session = await createRecognitionSession({
  ort,
  model: () => readFile("zipformer_interp_gentle_a05.int8.onnx"),
  corpus: async () => JSON.parse(await readFile("zipformer_quran.json", "utf8")),
  quran: async () => JSON.parse(await readFile("quran.json", "utf8")),
  onEvent: (msg) => {
    if (msg.type === "verse_match") console.log(`${msg.surah}:${msg.ayah}`);
    if (msg.type === "raw_transcript") console.log(msg.text);
  },
});

const CHUNK = Math.round(0.3 * 16000); // 300 ms
for (let i = 0; i < pcm16k.length; i += CHUNK) {
  await session.feed(pcm16k.subarray(i, i + CHUNK));
}
const final = await session.stop();
session.reset();

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, create the session from the path, and pass that session in with the runtime's Tensor:

import * as ort from "onnxruntime-react-native";
import { createZipformerSession } from "@tilawa/core";

const session = await createZipformerSession({
  session: await ort.InferenceSession.create(modelPath),
  Tensor: ort.Tensor,
  corpus: () => loadJsonAsset("zipformer_quran.json"),
  quran: () => loadJsonAsset("quran.json"),
});

Walkthrough: examples/react-native.md. Copy-paste runners: examples/.

Alternate engine: FastConformer

Pick it for one-shot transcribe(), a raw Arabic transcript, or MIT-only assets. Download from release v0.2.0:

base=https://github.com/yazinsai/tilawa/releases/download/v0.2.0
curl -L -O "$base/fastconformer_full_mixed.onnx"
curl -L -O "$base/vocab.json"
curl -L -O "$base/quran_ctc_tokens.json"

Write a SessionRunner that owns ort, then hand it to createTilawaSession with { vocab, quranCtcTokens, quran }. Missing keys throw Error("fastconformer engine requires assets: vocab, ctcTokens, quran ...") before anything is read.

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

async function createWebSessionRunner(modelBuffer: ArrayBuffer): Promise<SessionRunner> {
  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 session = createTilawaSession(await createWebSessionRunner(modelBuffer), {
  vocab,
  quranCtcTokens,
  quran,
});
const pred = await session.transcribe(audioFloat32);
// { surah: 1, ayah: 1, ayah_end: 3, score: 0.92, transcript: "..." }

Same runner shape on node (onnxruntime-node) and RN (create from a file path). FastConformer has no stop() — it finalizes on trailing silence. Wrap it in createRecognitionSession({ engine: "fastconformer", runner, assets }) for the uniform feed() / stop() / reset() surface.

Verse events

Both engines emit the same WorkerOutbound union — via onEvent / onOutput, and as the return value of feed() / stop():

| 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 | | raw_transcript | Accumulated transcript so far (and again on stop()) | text, confidence | | final_sequence | Full ordered sequence when recitation ends | verses[], confidence |

API

createRecognitionSession(options)

engine defaults to "zipformer" (DEFAULT_ENGINE). Pass engine: "fastconformer" with { runner, assets }. Returns feed(), stop() / flush(), reset(), plus zipformer / fastconformer (the other is null).

createZipformerSession(options) → ZipformerSession

  • { ort, model } — runtime namespace + model bytes (or a loader). Providers: ["wasm"] under onnxruntime-web, ["cpu"] under onnxruntime-node.
  • { session, Tensor } — an InferenceSession you created plus that runtime's Tensor. RN shape.

| Option | Default | Purpose | |---|---|---| | corpus | required | Parsed zipformer_quran.json, or a loader | | quran | empty | Arabic text for verse_match | | io | DEFAULT_ZIPFORMER_IO | Override only for your own export | | executionProviders | auto | See above | | onEvent | — | Verse events, same order feed() / stop() return them | | minWordFraction | 0.5 | Fraction of an ayah's words that must land | | enableFallback | true | Whole-ayah search when nothing locked | | tailSeconds | 2.0 | Silence stop() appends to flush the CTC tail |

Also: transcript, tallies / verses, engineState, config.

createTilawaSession(runner, assets, options?) → TilawaSession

assets: { vocab, quranCtcTokens, quran, blankId? }. transcribe() / transcribeRaw() / feed() / reset() / setConfig() / getConfig(). Streaming presets: "conservative" / "balanced" / "aggressiveAdvance".

audio on SessionRunner.run is borrowed, not owned — treat it as read-only.

Models

| | Zipformer (default) | FastConformer | |---|---|---| | File | zipformer_interp_gentle_a05.int8.onnx (66 MB) | fastconformer_full_mixed.onnx (88 MB) | | Input | 16 kHz mono Float32Array, streamed | same, preprocessing in-graph | | Recall / Precision / SeqAcc | 100% / 100% / 100% on v1 (53/53) and v2 (43/43) | 100% / 100% / 100% on v1 (53/53) | | Licence | NPL-1.2 non-commercial share-alike | NVIDIA CC-BY-4.0 |

This repository

Live demo: web/frontend (?engine=fastconformer to switch). Bake-off writeups: lab/EXPERIMENTS.md.

Zipformer models, vocabulary, and the phoneme corpus derive from Quran-Lab/zipformer_p-arabic-v3 and alketab's ملقّن القرآن. They are NPL-1.2 and are not covered by this repo's MIT licence. NOTICE.md.