@sanity/groq-wasm
v0.0.1
Published
WASM bindings for GROQ linting and formatting (groq-lint + groq-format)
Maintainers
Readme
@sanity/groq-wasm
WASM bindings for GROQ linting and formatting.
This package provides TypeScript wrappers around:
- groq-lint (Rust) - GROQ query linting
- groq-format (Rust) - GROQ query formatting
Both are compiled to WebAssembly for use in Node.js and browsers without requiring the Rust toolchain.
Installation
npm install @sanity/groq-wasmUsage
import { initWasm, lint, format } from '@sanity/groq-wasm'
// Initialize WASM (call once at startup)
await initWasm()
// Lint a query
const findings = lint('*[_type == "post"]{ author-> }')
console.log(findings)
// [{ ruleId: 'join-in-filter', message: '...', severity: 'error' }]
// Format a query
const formatted = format('*[_type=="post"]{title,body}')
console.log(formatted)
// '*[_type == "post"]{ title, body }'API
Initialization
initWasm(): Promise<void>
Initialize the WASM modules. Must be called before using lint() or format().
Safe to call multiple times.
isInitialized(): boolean
Check if WASM modules are initialized.
Linting
lint(query: string, config?: WasmLintConfig): Finding[]
Lint a GROQ query and return findings.
interface WasmLintConfig {
rules?: Record<string, boolean> // Enable/disable specific rules
}lintAsync(query: string, config?: WasmLintConfig): Promise<Finding[]>
Async version of lint().
Formatting
format(query: string, config?: WasmFormatConfig): string
Format a GROQ query.
interface WasmFormatConfig {
width?: number // Max line width (default: 80)
}formatAsync(query: string, config?: WasmFormatConfig): Promise<string>
Async version of format().
isValidSyntax(query: string): boolean
Check if a query has valid GROQ syntax.
Constants
DEFAULT_WIDTH: number
Default line width for formatting (80).
RULE_ID_MAP: Record<string, string>
Mapping from Rust rule IDs (snake_case) to TS convention (kebab-case).
Lint Rules
The following rules are available (from Rust groq-lint):
| Rule ID | Severity | Description |
| ------------------------------ | -------- | ---------------------------------------------------- |
| join-in-filter | error | Dereference (->) inside filter prevents optimization |
| join-to-get-id | warning | Using -> to get _id (use ._ref instead) |
| computed-value-in-filter | error | Computed values in filter prevent optimization |
| match-on-id | info | Using match on _id with wildcard |
| order-on-expr | error | Ordering on computed values |
| deep-pagination | warning | Large offset in slice (>=1000) |
| large-pages | warning | Fetching >100 results from index 0 |
| non-literal-comparison | error | Comparing two non-literal expressions |
| repeated-dereference | info | Multiple -> on same attribute in projection |
| count-in-correlated-subquery | info | count() on correlated subqueries |
| very-large-query | error | Query exceeds 10KB |
| extremely-large-query | error | Query exceeds 100KB |
| many-joins | warning | Query has >10 dereference operators |
Error Handling
import { WasmError } from '@sanity/groq-wasm'
try {
const findings = lint(query)
} catch (error) {
if (error instanceof WasmError) {
switch (error.code) {
case 'NOT_INITIALIZED':
// Call initWasm() first
break
case 'PARSE_ERROR':
// Invalid GROQ syntax
break
case 'WASM_ERROR':
// Other WASM error
break
}
}
}Browser Support
This package works in modern browsers that support WebAssembly. The WASM modules are loaded automatically based on the environment.
// In browser
import { initWasm, lint } from '@sanity/groq-wasm'
await initWasm()
const findings = lint(query)Architecture
This package uses a wrapper crate approach:
packages/groq-wasm/
├── rust/ # Rust wrapper crate
│ ├── Cargo.toml # Depends on groq-lint & groq-format
│ └── src/lib.rs # wasm-bindgen exports
├── wasm/ # Built WASM output (generated)
├── src/ # TypeScript wrappers
│ ├── index.ts # Main exports
│ ├── lint.ts # Linting API
│ ├── format.ts # Formatting API
│ └── wasm-loader.ts # WASM initialization
└── scripts/
└── build-wasm.sh # Build scriptThe Rust crate depends on upstream repos as git dependencies (no forking required).
Building from Source
To rebuild the WASM bindings:
Prerequisites
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add WASM target
rustup target add wasm32-unknown-unknown
# Install wasm-pack
cargo install wasm-packBuild
cd packages/groq-wasm
./scripts/build-wasm.shThis will:
- Compile the Rust wrapper crate to WASM
- Generate JS bindings via wasm-bindgen
- Output files to
wasm/
Performance
The WASM implementation provides significant performance benefits:
- Linting: ~5-10x faster than TypeScript for complex queries
- Formatting: Near-instant formatting for most queries
- Memory: Lower memory footprint than JavaScript AST processing
License
MIT
