@clickhouse/lexer

中文文档
1.0.1发布于 2026/09/23Apache-2.0TypeScriptES Module零依赖近 30 天下载 2141

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

原始使用文档
文档目录7 节
  1. Installation
  2. Usage
  3. API
  4. Using the raw WASM (non-JavaScript hosts)
  5. How this repository is synced
  6. Rebuilding manually
  7. License

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

ExportDescription
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.
TokenTypeName → numeric type (TokenType.StringLiteral...).
TOKEN_NAMESNumeric type → name.
isSignificant(type)Not whitespace and not a comment.
isError(type)One of the Error* types.
options.maxQuerySizeByte 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.

版本与发布记录

@clickhouse/lexer 共发布 1 个版本,首次发布于 2026/09/23;过去 12 个月发布了 1 个版本。

版本发布日期距今
1.0.1latest2026/09/231 个月前

关于 @clickhouse/lexer 的常见问题

@clickhouse/lexer 是什么?

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

如何安装 @clickhouse/lexer?

在项目目录运行 npm install @clickhouse/lexer,或使用 pnpm add / yarn add / bun add。

@clickhouse/lexer 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@clickhouse/lexer 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@clickhouse/lexer 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@clickhouse/lexer 可以商用吗?

@clickhouse/lexer 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@clickhouse/lexer 的下载量有多少?

根据 npm 官方下载统计,@clickhouse/lexer 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,141 次,日均约 71 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@clickhouse/lexer 还在维护吗?

最新版本 1.0.1 发布于 2026/09/23(1 个月前);至今共发布 1 个版本,其中过去 12 个月发布了 1 个;首次发布于 2026/09/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。