A set of tools for compiling and using SPARQL queries as first‑class modules across Node.js, Vite, esbuild, and TypeScript.
Packages (in usage order):
sparqlc– core compiler and runtime (JS API + CLI)node-loader-sparql– Node.js loader to import.rqfiles directlyvite-plugin-sparql– Vite plugin to import.rqin web appsesbuild-plugin-sparql– esbuild plugin to import.rqts-plugin-sparqlc– TypeScript language service plugin to get strong types for.rqimports
In most projects you do not install sparqlc directly. Instead, add the high‑level integration you use — it will pull sparqlc as a dependency:
- Node.js (import
.rqin Node):npm i -D node-loader-sparql
- Vite (web apps):
npm i -D vite-plugin-sparql
- esbuild:
npm i -D esbuild-plugin-sparql
- TypeScript editor types for
.rqimports:npm i -D ts-plugin-sparqlc
Only install sparqlc itself if you want to call the core API or use the CLI directly:
npm i sparqlcBelow you’ll find usage for each package.
sparqlc compiles a SPARQL string into a small executable function with:
code– the emitted function source (stringified)returnType– inferred SPARQL result kind:Select | Construct | Ask | Update | unknownexecute– a function that, when given parameters and an executor{ env, client, processors }, will either:- return a SPARQL string (when no
clientis provided), or - execute the query using
sparql-http-clientand return typed results based onreturnType
- return a SPARQL string (when no
import { compile } from 'sparqlc'
import env from '@zazuko/env'
import { StreamClient } from 'sparql-http-client'
const source = `
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?name WHERE { ?s foaf:name ?name }
LIMIT 10
`
const { execute, returnType } = compile(source)
console.log(returnType) // e.g. 'Select'
// Build executor options
const client = new StreamClient({ endpointUrl: 'https://dbpedia.org/sparql' })
// Optional parameterization examples (URLSearchParams, object, or Map<Term, Term|Term[]>)
const params = { name: env.literal('Alice') }
// Execute:
const rows = await execute(params, { env, client })
// If you omit `client`, execute(...) returns the final SPARQL string insteadYou can declare and consume query parameters inside .rq files using the RDF property/function identified by https://sparqlc.described.at/param (IRI). Use a prefix for convenience:
PREFIX sparqlc: <https://sparqlc.described.at/>-
(Recommended) Function style:
sparqlc:param("name")- Can be easily inlined in
BIND,FILTER, or anywhere an expression is allowed. - Example:
PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX sparqlc: <https://sparqlc.described.at/> SELECT ?name ?age WHERE { ?s foaf:name ?name ; foaf:age ?age . FILTER (?age >= sparqlc:param("minAge")) }
- Can be easily inlined in
-
Pattern style:
?variable sparqlc:param "name" .- This binds the value of the parameter
"name"to the SPARQL variable?variable. - Example:
Runtime call:
PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX sparqlc: <https://sparqlc.described.at/> SELECT ?person WHERE { ?name sparqlc:param "person" . ?person a foaf:Person ; foaf:name ?name . }
import env from '@zazuko/env' const rows = await execute({ person: env.namedNode('http://example.com/alice') }, { env, client })
- This binds the value of the parameter
Passing parameters at runtime
- You may pass parameters as:
Record<string, Term>:{ person: env.namedNode('...'), minAge: env.literal('18', xsdInteger) }URLSearchParams:new URLSearchParams([["person", "http://example.com/alice"], ["minAge", "18"]])(simple string values)Map<Term, Term | Term[]>for full control
- Values are RDFJS
Terms. For typed literals, construct them via your RDF environment (e.g.,@zazuko/env).
Notes
- The parameter IRI is exactly
https://sparqlc.described.at/param. - Use
PREFIX sparqlc: <https://sparqlc.described.at/>sosparqlc:paramexpands to that IRI in both triple and function forms. - Arrays (
Term[]) are supported by the runtime API for positions that accept multiple terms (e.g., via custom processors). Your usage may vary depending on processors you apply.
sparqlc also ships a tiny CLI wrapper that can be used to process a query and print the effective query:
npx sparqlc file.rq [param1=value1 param2=value2 ...]Parameter values are string parsed using rdf-string.
Typical pattern is to use the library API in your build tool via the plugins below; the CLI is primarily for local debugging.
Import .rq files directly in Node.js. The loader compiles the query on‑the‑fly and makes the default export be the executable query function (typed at runtime) compatible with sparqlc’s execute signature.
node --experimental-loader=node-loader-sparql ./app.mjs
# Node 22+ keeps the loader flag; ES module specifiers are supportedNow you can do:
// app.mjs (or .ts with tsx/ts-node)
import query from './queries/find-people.rq'
import rdf from '@zazuko/env'
import { StreamClient } from 'sparql-http-client'
const env = rdf
const client = new StreamClient({ endpointUrl: 'https://dbpedia.org/sparql' })
const rows = await query({ env, client })
console.log(rows)Use .rq files seamlessly in Vite projects. The plugin compiles the query at transform time.
// vite.config.ts
import { defineConfig } from 'vite'
import sparql from 'vite-plugin-sparql'
export default defineConfig({
plugins: [sparql],
})import query from './queries/people.rq'
import rdf from '@zazuko/env'
import { StreamClient } from 'sparql-http-client'
const env = rdf
const client = new StreamClient({ endpointUrl: '/sparql' })
const data = await query({ env, client })Add support for importing .rq in esbuild builds.
// build.ts
import { build } from 'esbuild'
import sparql from 'esbuild-plugin-sparql'
await build({
entryPoints: ['src/index.ts'],
bundle: true,
format: 'esm',
platform: 'node',
plugins: [sparql],
})Then in your sources:
import query from './queries/people.rq'
// … use query.execute({ env, client })If you’re bundling for Node ESM and also using require() somewhere, you may need to add a banner to create a require shim (see the repo tests for an example).
TypeScript language service plugin that gives proper types for .rq default exports based on the query’s returnType. That means you’ll get:
- Autocomplete/typed signatures for
execute - Distinct return types for
Select,Construct,Ask, andUpdate
import query from './queries/people.rq'
// Hovering shows: ExecuteSelect | ExecuteConstruct | ... depending on the query
const result = await query({ env, client })The plugin internally generates virtual .d.rq.ts type stubs next to your .rq files and keeps them in sync with your editor session—no files are written to disk.
All environments ultimately call query.execute(..., { env, client, processors? }) produced by sparqlc.
env: an RDF/JS environment (e.g.@zazuko/env)client: optionalsparql-http-clientclient; when omitted,executereturns the final SPARQL string instead of performing a requestprocessors: optional array of@hydrofoil/sparql-processorinstances to transform the parsed query before serialization
A typical .rq file:
# queries/people.rq
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?name WHERE { ?s foaf:name ?name }
LIMIT 5And consuming it (works with the loader/plugins):
import query from './queries/people.rq'
import rdf from '@zazuko/env'
import { StreamClient } from 'sparql-http-client'
const env = rdf
const client = new StreamClient({ endpointUrl: 'https://dbpedia.org/sparql' })
const rows = await query.execute({ env, client })MIT © Tomasz Pluskiewicz
{ "compilerOptions": { // Let TS consider non‑TS imports so the plugin can attach types "allowArbitraryExtensions": true, // Enable the plugin "plugins": [ { "name": "ts-plugin-sparqlc" } ] } }