|
1 | | -# sparqlc monorepo |
| 1 | +# sparqlc |
2 | 2 |
|
3 | | -A set of tools for compiling and using SPARQL queries as first‑class modules across Node.js, Vite, esbuild, and TypeScript. |
| 3 | +Typed SPARQL query modules for TypeScript and JavaScript across Node.js, Vite, and esbuild. |
4 | 4 |
|
5 | | -Packages (in usage order): |
6 | | -- `sparqlc` – core compiler and runtime (JS API) |
7 | | -- `sparqlc-cli` – CLI tools (`sparqlc-tsc` drop-in replacement for `tsc`, and `sparqlc` CLI compiler) |
8 | | -- `node-loader-sparql` – Node.js loader to import `.rq` files directly |
9 | | -- `vite-plugin-sparql` – Vite plugin to import `.rq` in web apps |
10 | | -- `esbuild-plugin-sparql` – esbuild plugin to import `.rq` |
11 | | -- `ts-plugin-sparqlc` – TypeScript language service plugin to get strong types for `.rq` imports in IDEs |
| 5 | +Write SPARQL queries in `.rq` (query) and `.ru` (update) files, import them directly as ECMAScript modules, and execute them with strong types against a SPARQL endpoint or RDF/JS dataset. |
12 | 6 |
|
13 | | -## Installation |
| 7 | +## Packages |
14 | 8 |
|
15 | | -In most projects you do not install `sparqlc` directly. Instead, add the high‑level integration you use — it will pull `sparqlc` as a dependency: |
| 9 | +- **[`sparqlc`](./packages/sparqlc)** – Core compiler and runtime library (JS API, query analysis, parameter binding). |
| 10 | +- **[`sparqlc-cli`](./packages/sparqlc-cli)** – CLI tools: `sparqlc-tsc` (a drop-in replacement for `tsc` that type-checks `.rq`/`.ru` imports and copies assets) and `sparqlc` (standalone query compiler). |
| 11 | +- **[`node-loader-sparql`](./packages/node-loader-sparql)** – Node.js ESM loader to import `.rq`/`.ru` files directly at runtime. |
| 12 | +- **[`vite-plugin-sparql`](./packages/vite-plugin-sparql)** – Vite plugin for importing and bundling SPARQL queries in web applications. |
| 13 | +- **[`esbuild-plugin-sparql`](./packages/esbuild-plugin-sparql)** – esbuild plugin to bundle SPARQL queries. |
| 14 | +- **[`ts-plugin-sparqlc`](./packages/ts-plugin-sparqlc)** – TypeScript language service plugin providing editor type inference and autocomplete for `.rq`/`.ru` imports. |
16 | 15 |
|
17 | | -- Node.js (import `.rq` in Node): |
18 | | - ```sh |
19 | | - npm i -D node-loader-sparql |
20 | | - ``` |
21 | | -- Vite (web apps): |
22 | | - ```sh |
23 | | - npm i -D vite-plugin-sparql |
24 | | - ``` |
25 | | -- esbuild: |
26 | | - ```sh |
27 | | - npm i -D esbuild-plugin-sparql |
28 | | - ``` |
29 | | -- TypeScript editor types for `.rq` imports: |
30 | | - ```sh |
31 | | - npm i -D ts-plugin-sparqlc |
32 | | - ``` |
| 16 | +## Quick Start |
33 | 17 |
|
34 | | -Only install `sparqlc` itself if you want to call the core API or use the CLI directly: |
| 18 | +### 1. Install |
35 | 19 |
|
36 | | -```sh |
37 | | -npm i sparqlc |
38 | | -``` |
39 | | - |
40 | | -Below you’ll find usage for each package. |
41 | | - |
42 | | ---- |
43 | | - |
44 | | -## 1) `sparqlc` (core) |
45 | | - |
46 | | -`sparqlc` compiles a SPARQL string into a small executable function with: |
47 | | -- `code` – the emitted function source (stringified) |
48 | | -- `returnType` – inferred SPARQL result kind: `Select | Construct | Ask | Update | unknown` |
49 | | -- `execute` – a function that, when given parameters and an executor `{ env, client, processors }`, will either: |
50 | | - - return a SPARQL string (when no `client` is provided), or |
51 | | - - execute the query using `sparql-http-client` and return typed results based on `returnType` |
52 | | - |
53 | | -### API usage |
54 | | - |
55 | | -```ts |
56 | | -import { compile } from 'sparqlc' |
57 | | -import env from '@zazuko/env' |
58 | | -import { StreamClient } from 'sparql-http-client' |
| 20 | +Install the integration appropriate for your environment: |
59 | 21 |
|
60 | | -const source = ` |
61 | | -PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
62 | | -SELECT ?name WHERE { ?s foaf:name ?name } |
63 | | -LIMIT 10 |
64 | | -` |
| 22 | +```sh |
| 23 | +# For Node.js runtime imports: |
| 24 | +npm i -D node-loader-sparql |
65 | 25 |
|
66 | | -const { execute, returnType } = compile(source) |
67 | | -console.log(returnType) // e.g. 'Select' |
| 26 | +# For Vite: |
| 27 | +npm i -D vite-plugin-sparql |
68 | 28 |
|
69 | | -// Build executor options |
70 | | -const client = new StreamClient({ endpointUrl: 'https://dbpedia.org/sparql' }) |
| 29 | +# For esbuild: |
| 30 | +npm i -D esbuild-plugin-sparql |
71 | 31 |
|
72 | | -// Optional parameterization examples (URLSearchParams, object, or Map<Term, Term|Term[]>) |
73 | | -const params = { name: env.literal('Alice') } |
| 32 | +# For TypeScript IDE support: |
| 33 | +npm i -D ts-plugin-sparqlc |
74 | 34 |
|
75 | | -// Execute: |
76 | | -const rows = await execute(params, { env, client }) |
77 | | -// If you omit `client`, execute(...) returns the final SPARQL string instead |
| 35 | +# For command-line type-checking and builds: |
| 36 | +npm i -D sparqlc-cli |
78 | 37 | ``` |
79 | 38 |
|
80 | | -### Reference parameters with `sparqlc:param` |
81 | | - |
82 | | -You can declare and consume query parameters inside `.rq` files using the SPARQL function identified by `https://sparqlc.described.at/param` (IRI). Use a prefix for convenience: |
| 39 | +### 2. Write a Query |
83 | 40 |
|
84 | 41 | ```sparql |
| 42 | +# queries/find-fruits.rq |
| 43 | +PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> |
85 | 44 | PREFIX sparqlc: <https://sparqlc.described.at/> |
86 | | -``` |
87 | | - |
88 | | -- Parameter function: `sparqlc:param("name")` or `sparqlc:param(prefix:name)` |
89 | | - - Can be easily inlined in `BIND`, `FILTER`, or anywhere an expression is allowed. |
90 | | - - String literal keys: `BIND(sparqlc:param("minAge") AS ?minAge)` |
91 | | - - NamedNode / IRI keys: `BIND(sparqlc:param(schema:mainEntity) AS ?entity)` |
92 | | - - Example: |
93 | | - ```sparql |
94 | | - PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
95 | | - PREFIX sparqlc: <https://sparqlc.described.at/> |
96 | | - |
97 | | - SELECT ?name ?age WHERE { |
98 | | - ?s foaf:name ?name ; foaf:age ?age . |
99 | | - FILTER (?age >= sparqlc:param("minAge")) |
100 | | - } |
101 | | - ``` |
102 | | - Runtime call: |
103 | | - ```ts |
104 | | - import env from '@zazuko/env' |
105 | | - const rows = await execute({ minAge: env.literal('18', env.ns.xsd.integer) }, { env, client }) |
106 | | - ``` |
107 | | -
|
108 | | -Passing parameters at runtime |
109 | | -- You may pass parameters as: |
110 | | - - `Record<string, Term>`: `{ person: env.namedNode('...'), minAge: env.literal('18', xsdInteger) }` |
111 | | - - `URLSearchParams`: `new URLSearchParams([["person", "http://example.com/alice"], ["minAge", "18"]])` (simple string values) |
112 | | - - `Map<Term, Term | Term[]>` to use NamedNode parameter names |
113 | | -- Values are RDFJS `Term`s. For typed literals, construct them via your RDF environment (e.g., `@zazuko/env`). |
114 | | -
|
115 | | -Notes |
116 | | -- The parameter IRI is exactly `https://sparqlc.described.at/param`. |
117 | | -- Use `PREFIX sparqlc: <https://sparqlc.described.at/>` so `sparqlc:param` expands to that IRI. |
118 | | -- 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. |
119 | | -
|
120 | | -### CLI usage |
121 | | -
|
122 | | -`sparqlc` also ships a tiny CLI wrapper that can be used to process a query and print the effective query: |
123 | | -
|
124 | | -```sh |
125 | | -npx sparqlc file.rq [param1=value1 param2=value2 ...] |
126 | | -``` |
127 | | - |
128 | | -Parameter values are string parsed using [rdf-string](https://npm.im/rdf-string). |
129 | | - |
130 | | -Typical pattern is to use the library API in your build tool via the plugins below; the CLI is primarily for local debugging. |
131 | | - |
132 | | ---- |
133 | | - |
134 | | -## 2) `node-loader-sparql` |
135 | | - |
136 | | -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. |
137 | | - |
138 | | -### Run with Node’s loader flag |
139 | | - |
140 | | -```sh |
141 | | -node --experimental-loader=node-loader-sparql ./app.mjs |
142 | | -# Node 22+ keeps the loader flag; ES module specifiers are supported |
143 | | -``` |
144 | | - |
145 | | -Now you can do: |
146 | | - |
147 | | -```ts |
148 | | -// app.mjs (or .ts with tsx/ts-node) |
149 | | -import query from './queries/find-people.rq' |
150 | | -import rdf from '@zazuko/env' |
151 | | -import { StreamClient } from 'sparql-http-client' |
152 | | - |
153 | | -const env = rdf |
154 | | -const client = new StreamClient({ endpointUrl: 'https://dbpedia.org/sparql' }) |
155 | | - |
156 | | -const rows = await query({ env, client }) |
157 | | -console.log(rows) |
158 | | -``` |
159 | | - |
160 | | ---- |
161 | | - |
162 | | -## 3) `vite-plugin-sparql` |
163 | | - |
164 | | -Use `.rq` files seamlessly in Vite projects. The plugin compiles the query at transform time. |
165 | | - |
166 | | -### Setup |
167 | 45 |
|
168 | | -```ts |
169 | | -// vite.config.ts |
170 | | -import { defineConfig } from 'vite' |
171 | | -import sparql from 'vite-plugin-sparql' |
172 | | - |
173 | | -export default defineConfig({ |
174 | | - plugins: [sparql], |
175 | | -}) |
176 | | -``` |
177 | | - |
178 | | -### Usage in app code |
179 | | - |
180 | | -```ts |
181 | | -import query from './queries/people.rq' |
182 | | -import rdf from '@zazuko/env' |
183 | | -import { StreamClient } from 'sparql-http-client' |
184 | | - |
185 | | -const env = rdf |
186 | | -const client = new StreamClient({ endpointUrl: '/sparql' }) |
187 | | - |
188 | | -const data = await query({ env, client }) |
189 | | -``` |
190 | | - |
191 | | ---- |
192 | | - |
193 | | -## 4) `esbuild-plugin-sparql` |
194 | | - |
195 | | -Add support for importing `.rq` in esbuild builds. |
196 | | - |
197 | | -### Minimal build script |
198 | | - |
199 | | -```ts |
200 | | -// build.ts |
201 | | -import { build } from 'esbuild' |
202 | | -import sparql from 'esbuild-plugin-sparql' |
203 | | - |
204 | | -await build({ |
205 | | - entryPoints: ['src/index.ts'], |
206 | | - bundle: true, |
207 | | - format: 'esm', |
208 | | - platform: 'node', |
209 | | - plugins: [sparql], |
210 | | -}) |
211 | | -``` |
212 | | - |
213 | | -Then in your sources: |
214 | | - |
215 | | -```ts |
216 | | -import query from './queries/people.rq' |
217 | | -// … use query.execute({ env, client }) |
218 | | -``` |
219 | | - |
220 | | -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). |
221 | | - |
222 | | ---- |
223 | | - |
224 | | -## 5) `ts-plugin-sparqlc` |
225 | | - |
226 | | -TypeScript language service plugin that gives proper types for `.rq` default exports based on the query’s `returnType`. That means you’ll get: |
227 | | -- Autocomplete/typed signatures for `execute` |
228 | | -- Distinct return types for `Select`, `Construct`, `Ask`, and `Update` |
| 46 | +SELECT ?fruit ?label WHERE { |
| 47 | + BIND(sparqlc:param("category") AS ?category) |
229 | 48 |
|
230 | | -### Configure `tsconfig.json` |
231 | | - |
232 | | -```jsonc |
233 | | -{ |
234 | | - "compilerOptions": { |
235 | | - // Let TS consider non‑TS imports so the plugin can attach types |
236 | | - "allowArbitraryExtensions": true, |
237 | | - |
238 | | - // Enable the plugin |
239 | | - "plugins": [ |
240 | | - { "name": "ts-plugin-sparqlc" } |
241 | | - ] |
242 | | - } |
| 49 | + ?fruit a ?category ; |
| 50 | + rdfs:label ?label . |
243 | 51 | } |
244 | 52 | ``` |
245 | 53 |
|
246 | | -### What you get in TS |
| 54 | +### 3. Import and Execute |
247 | 55 |
|
248 | 56 | ```ts |
249 | | -import query from './queries/people.rq' |
250 | | - |
251 | | -// Hovering shows: ExecuteSelect | ExecuteConstruct | ... depending on the query |
252 | | -const result = await query({ env, client }) |
253 | | -``` |
254 | | - |
255 | | -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. |
256 | | - |
257 | | ---- |
258 | | - |
259 | | -## Runtime expectations |
260 | | - |
261 | | -All environments ultimately call `query.execute(..., { env, client, processors?, ...options })` produced by `sparqlc`. |
262 | | -- `env`: an RDF/JS environment (e.g. `@zazuko/env`) |
263 | | -- `client`: optional `sparql-http-client` client; when omitted, `execute` returns the final SPARQL string instead of performing a request |
264 | | -- `processors`: optional array of `@hydrofoil/sparql-processor` instances to transform the parsed query before serialization |
265 | | -- `distinct`: `boolean` – dynamically add or remove the `DISTINCT` modifier |
266 | | -- `from` / `fromNamed`: `NamedNode | string | (NamedNode | string)[]` – set default (`FROM`) or named (`FROM NAMED`) graph IRIs |
267 | | -- `limit`: `number` – set or override `LIMIT` |
268 | | -- `offset`: `number` – set or override `OFFSET` |
269 | | -- `orderBy`: variable, direction tuple `[variable, 'ASC' | 'DESC']`, or array thereof – sort results (restricted to query variables in TypeScript) |
| 57 | +import env from '@zazuko/env' |
| 58 | +import { ParsingClient } from 'sparql-http-client' |
| 59 | +import findFruits from './queries/find-fruits.rq' |
270 | 60 |
|
271 | | -## Examples |
| 61 | +const client = new ParsingClient({ endpointUrl: 'https://example.org/sparql' }) |
272 | 62 |
|
273 | | -A typical `.rq` file: |
| 63 | +const rows = await findFruits( |
| 64 | + { category: env.namedNode('http://example.org/Fruit') }, |
| 65 | + { env, client } |
| 66 | +) |
274 | 67 |
|
275 | | -```sparql |
276 | | -# queries/people.rq |
277 | | -PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
278 | | -SELECT ?name WHERE { ?s foaf:name ?name } |
279 | | -LIMIT 5 |
| 68 | +for (const row of rows) { |
| 69 | + console.log(row.fruit.value, row.label.value) |
| 70 | +} |
280 | 71 | ``` |
281 | 72 |
|
282 | | -And consuming it with runtime options (works with the loader/plugins): |
283 | | - |
284 | | -```ts |
285 | | -import query from './queries/people.rq' |
286 | | -import rdf from '@zazuko/env' |
287 | | -import { StreamClient } from 'sparql-http-client' |
| 73 | +## Documentation |
288 | 74 |
|
289 | | -const env = rdf |
290 | | -const client = new StreamClient({ endpointUrl: 'https://dbpedia.org/sparql' }) |
291 | | - |
292 | | -const rows = await query.execute({ |
293 | | - env, |
294 | | - client, |
295 | | - limit: 10, |
296 | | - offset: 5, |
297 | | - orderBy: [['name', 'ASC']], |
298 | | -}) |
299 | | -``` |
| 75 | +For detailed guides, configuration options, and API documentation, refer to the individual packages: |
300 | 76 |
|
301 | | ---- |
| 77 | +- [Core compiler and runtime (`sparqlc`)](./packages/sparqlc/README.md) |
| 78 | +- [CLI utilities and `sparqlc-tsc` (`sparqlc-cli`)](./packages/sparqlc-cli/README.md) |
| 79 | +- [Node.js ESM loader (`node-loader-sparql`)](./packages/node-loader-sparql/README.md) |
| 80 | +- [Vite plugin (`vite-plugin-sparql`)](./packages/vite-plugin-sparql/README.md) |
| 81 | +- [esbuild plugin (`esbuild-plugin-sparql`)](./packages/esbuild-plugin-sparql/README.md) |
| 82 | +- [TypeScript language service plugin (`ts-plugin-sparqlc`)](./packages/ts-plugin-sparqlc/README.md) |
302 | 83 |
|
303 | 84 | ## License |
304 | 85 |
|
|
0 commit comments