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

@clickhouse/lexer

v1.0.1

Published

ClickHouse's SQL lexer compiled to WebAssembly — the exact tokenizer ClickHouse itself uses, in ~9 KB, with no dependencies

Readme

ClickHouse SQL Lexer

NPM version

The actual SQL lexer of ClickHouse (src/Parsers/Lexer.cpp) compiled to WebAssembly: ~9 KB, no dependencies, works in browsers and in Node.js. Because it is the exact code the server uses, it tokenizes every corner of ClickHouse SQL correctly — nested /* */ comments, $heredoc$ strings, backtick and double-quote identifiers, 0x/0b literals, \G, |>, and whatever gets added next.

This is the same module that powers syntax highlighting in the ClickHouse Web UI (play.html) and the ClickHouse documentation. A GitHub Action syncs this repository from ClickHouse master daily and publishes a release whenever the lexer changes.

Installation

npm install @clickhouse/lexer

Usage

import {tokenize, TokenType, TOKEN_NAMES, isSignificant, isError} from "@clickhouse/lexer"

const tokens = await tokenize("SELECT count() FROM `events` WHERE user != 'guest' -- filter")

for (const token of tokens)
    console.log(TOKEN_NAMES[token.type], JSON.stringify(token.text))
// BareWord "SELECT"
// Whitespace " "
// BareWord "count"
// OpeningRoundBracket "("
// ...
// StringLiteral "'guest'"
// Whitespace " "
// Comment "-- filter"

// Only keywords/identifiers/literals/operators, no whitespace and comments:
tokens.filter(t => isSignificant(t.type))

// The lexer stops at unlexable input and reports what went wrong:
const bad = await tokenize("SELECT 'unterminated")
isError(bad.at(-1).type) // true
TOKEN_NAMES[bad.at(-1).type] // "ErrorSingleQuoteIsNotClosed"

Each token is {type, text, begin, end}. Tokens tile the input from the start — every byte belongs to exactly one token (whitespace and comments included), which is exactly what you want for syntax highlighting. begin/end are byte offsets into the UTF-8 encoding of the query; text is the decoded token.

In the browser it works the same way — the WASM is embedded as base64, so there is nothing to configure in any bundler:

<script type="module">
import {tokenize} from "https://esm.sh/@clickhouse/lexer"
console.log(await tokenize("SELECT 1"))
</script>

For hot paths, load once and tokenize synchronously:

import {loadLexer, tokenizeSync} from "@clickhouse/lexer"

await loadLexer()
const tokens = tokenizeSync(query) // no await, e.g. inside an editor's render path

API

| Export | Description | |---|---| | tokenize(query, options?) | Promise<Token[]>; loads the lexer on first use. | | loadLexer() | Instantiate the WASM module (idempotent). | | tokenizeSync(query, options?) | Synchronous tokenization after loadLexer(). | | lexerIsLoaded() | Whether tokenizeSync can be called. | | TokenType | Name → numeric type (TokenType.StringLiteral...). | | TOKEN_NAMES | Numeric type → name. | | isSignificant(type) | Not whitespace and not a comment. | | isError(type) | One of the Error* types. | | options.maxQuerySize | Byte limit for the lexer; 0 (default) = unlimited. |

Important: token type values are positional — they mirror the TokenType enum in src/Parsers/Lexer.h, and inserting a token type renumbers everything after it (this has happened). Always compare against TokenType.* from this package, never against hard-coded numbers, and never mix the token table of one version with the WASM of another. Each release of this package pairs both from the same ClickHouse revision (recorded in upstream/COMMIT).

Using the raw WASM (non-JavaScript hosts)

The package also ships the plain binary as @clickhouse/lexer/lexer.wasm for use from any WASM runtime. The C ABI (see upstream/clickhouse_lexer.h):

extern size_t clickhouse_lexer_size; // size of the lexer state object
void clickhouse_lexer_create(void * ptr, const char * begin, const char * end, size_t max_query_size);
unsigned char clickhouse_lexer_next_token(void * ptr, const char ** out_token_begin, const char ** out_token_end);
int clickhouse_lexer_token_is_significant(unsigned char token);
int clickhouse_lexer_token_is_error(unsigned char token);
int clickhouse_lexer_token_is_end(unsigned char token);

Place your buffers (the lexer state, the query bytes, and the two out-pointers) at __heap_base or above — the module's shadow stack occupies the memory below it and grows down, so lower buffers get corrupted on large queries. Call clickhouse_lexer_next_token in a loop until is_end or is_error. index.js in this package is a complete reference implementation.

How this repository is synced

The lexer sources live in upstream/, copied verbatim from src/Parsers of ClickHouse/ClickHouse (Lexer.cpp, Lexer.h, LexerStandalone.h, clickhouse_lexer.h); upstream/COMMIT records the master commit they came from.

The Sync workflow runs daily:

  1. scripts/sync.sh — fetches the latest sources from ClickHouse master; stops if nothing changed.
  2. scripts/build.sh — compiles lexer.wasm with clang (--target=wasm32 -nostdlib, the same command as the lexer_wasm CMake target in the main repository).
  3. scripts/generate.js — regenerates lexer-wasm-base64.js and tokens.js (the token table is parsed from Lexer.h, keeping the numbering in lockstep with the binary).
  4. Runs the tests, commits, bumps the patch version, creates a GitHub release, and publishes to npm.

Rebuilding manually

Requirements: clang, lld (for wasm-ld) and Node.js ≥ 18.

npm run sync   # pull the latest lexer sources from ClickHouse master into upstream/
npm run build  # compile lexer.wasm and regenerate the derived JS modules
npm test

Equivalently, from a checkout of the main ClickHouse repository, the standalone lexer build is:

clang++ -Os -fno-exceptions -fno-rtti -DLEXER_STANDALONE_BUILD --target=wasm32 -flto -nostdlib \
    -I src -Wl,--no-entry -Wl,--export-all \
    src/Parsers/Lexer.cpp -o Lexer.wasm

which is what the lexer_wasm CMake target produces at build/src/Parsers/Lexer.wasm when configured with -DENABLE_LEXER_TEST=1.

License

Apache License 2.0, same as ClickHouse. The upstream/ sources are verbatim copies from the ClickHouse repository and remain © ClickHouse, Inc.