├── .babelrc
├── .changeset
├── README.md
└── config.json
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ ├── docs.yml
│ ├── npmpublish.yml
│ └── test.yml
├── .gitignore
├── .husky
└── pre-commit
├── CHANGELOG.md
├── README.md
├── docs
├── .nojekyll
├── ASK.md
├── CONSTRUCT.md
├── DELETE-DATA.md
├── DELETE.md
├── DESCRIBE.md
├── INSERT-DATA.md
├── INSERT.md
├── README.md
├── SELECT.md
├── _sidebar.md
├── examples
│ ├── execute-construct.js
│ └── execute-select.js
├── execute.md
├── expressions.md
├── index.html
├── modifiers.md
├── overview.md
└── prologue.md
├── package-lock.json
├── package.json
├── src
├── expressions.ts
├── index.ts
└── lib
│ ├── AskBuilder.ts
│ ├── ConstructBuilder.ts
│ ├── DeleteBuilder.ts
│ ├── DescribeBuilder.ts
│ ├── InsertBuilder.ts
│ ├── QueryError.ts
│ ├── SelectBuilder.ts
│ ├── TemplateResult.ts
│ ├── WithBuilder.ts
│ ├── execute.ts
│ ├── expressions
│ ├── in.ts
│ ├── union.ts
│ └── values.ts
│ ├── index.ts
│ └── partials
│ ├── DATA.ts
│ ├── FROM.ts
│ ├── GROUP.ts
│ ├── HAVING.ts
│ ├── INSERT.ts
│ ├── LIMIT.ts
│ ├── ORDER.ts
│ ├── WHERE.ts
│ └── prologue.ts
├── test
├── ASK.test.ts
├── CONSTRUCT.test.ts
├── DELETE.test.ts
├── DELETE_DATA.test.ts
├── DESCRIBE.test.ts
├── INSERT.test.ts
├── INSERT_DATA.test.ts
├── SELECT.test.ts
├── WITH.test.ts
├── __snapshots__
│ ├── ASK.test.ts.snap
│ ├── CONSTRUCT.test.ts.snap
│ ├── DELETE.test.ts.snap
│ ├── DELETE_DATA.test.ts.snap
│ ├── DESCRIBE.test.ts.snap
│ ├── INSERT.test.ts.snap
│ ├── INSERT_DATA.test.ts.snap
│ └── SELECT.test.ts.snap
├── _mocks.ts
├── e2e.test.ts
├── execute.test.ts
├── expressions
│ ├── IN.test.ts
│ ├── UNION.test.ts
│ └── VALUES.test.ts
├── mochaSetup.js
└── sparql.ts
├── tsconfig.eslint.json
└── tsconfig.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "test": {
4 | "presets": [
5 | [
6 | "@babel/preset-env",
7 | {
8 | "targets": {
9 | "node": "current"
10 | }
11 | }
12 | ],
13 | "@babel/preset-typescript"
14 | ]
15 | },
16 | "modules": {
17 | "presets": [
18 | "@babel/preset-typescript"
19 | ],
20 | "plugins": [
21 | ["babel-plugin-add-import-extension", { "extension": "mjs" }]
22 | ]
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@1.5.0/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "linked": [],
6 | "access": "restricted",
7 | "baseBranch": "master",
8 | "updateInternalDependencies": "patch",
9 | "ignore": []
10 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | indent_size = 2
3 | indent_style = space
4 | insert_final_newline = true
5 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | main.js
2 | docs/examples/
3 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [ "@tpluscode" ],
3 | "env": {
4 | "mocha": true,
5 | "browser": true
6 | },
7 | "parserOptions": {
8 | "project": "./tsconfig.eslint.json"
9 | },
10 | "overrides": [
11 | {
12 | "files": ["docs/examples/**"],
13 | "rules": {
14 | "@typescript-eslint/no-var-requires": "off"
15 | }
16 | },
17 | {
18 | "files": ["test/**"],
19 | "rules": {
20 | "@typescript-eslint/no-explicit-any": "off"
21 | }
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 | - run: npm ci
16 | - run: npx typedoc
17 | - name: GH Pages deploy
18 | uses: Cecilapp/GitHub-Pages-deploy@2.0.0
19 | env:
20 | EMAIL: tomasz@t-code.pl
21 | GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
22 | BUILD_DIR: docs/
23 |
--------------------------------------------------------------------------------
/.github/workflows/npmpublish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | release:
10 | name: Release
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout Repo
14 | uses: actions/checkout@v4
15 | with:
16 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
17 | fetch-depth: 0
18 |
19 | - name: Setup Node.js
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: 18
23 |
24 | - name: Install Dependencies
25 | run: npm ci
26 |
27 | - name: Create Release Pull Request or Publish to npm
28 | id: changesets
29 | uses: changesets/action@v1
30 | with:
31 | # This expects you to have a script called release which does a build for your packages and calls changeset publish
32 | publish: yarn release
33 | env:
34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
36 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node: [ '20', '18' ]
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: ${{ matrix.node }}
17 | - run: npm ci
18 | - run: yarn build
19 | - run: yarn test
20 | - run: npx typedoc
21 | - name: Codecov
22 | uses: codecov/codecov-action@v3
23 | with:
24 | token: ${{ secrets.CODECOV_TOKEN }}
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | *.d.ts
3 | *.d.ts.map
4 | coverage/
5 | *.js
6 | !main.js
7 | docs/api/
8 | *.mjs
9 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx lint-staged
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 3.0.1
4 |
5 | ### Patch Changes
6 |
7 | - a4736b6: `UNION` expression typing did not accept a subquery
8 |
9 | ## 3.0.0
10 |
11 | ### Major Changes
12 |
13 | - ae69cd7: Removed built-in logging. Use an optional parameter to `execute`. Exceptions will be reported as a custom `Error` subclass
14 |
15 | ## 2.0.4
16 |
17 | ### Patch Changes
18 |
19 | - 463ea01: Update anylogger to 1.1-beta
20 |
21 | ## 2.0.3
22 |
23 | ### Patch Changes
24 |
25 | - 43e8ce9: Export `WithQuery` to avoid error like "The inferred type of 'X' cannot be named without a reference to '/node_modules/@tpluscode/sparql-builder/lib/WithBuilder.js'. This is likely not portable. A type annotation is necessary."
26 |
27 | ## 2.0.2
28 |
29 | ### Patch Changes
30 |
31 | - c48bd28: Export the `*Executable` types
32 |
33 | ## 2.0.1
34 |
35 | ### Patch Changes
36 |
37 | - 5e451cd: Correct imports to work with `moduleResolution = NodeNext`
38 |
39 | ## 2.0.0
40 |
41 | ### Major Changes
42 |
43 | - b34931c: Updated `sparql-http-client` to v3
44 |
45 | The `.execute` method now takes an instance of client and not the `query` object. Also, since the new client does not return a promise from methods which return streams, `await` is no longer necessary
46 |
47 | ## 1.1.0
48 |
49 | ### Minor Changes
50 |
51 | - 127f72f: No longer depends on `debug`. Use `anylogger-*` adapters to receive logs
52 |
53 | ### Patch Changes
54 |
55 | - 5bdc6b0: Relax dependency on `@tpluscode/rdf-ns-builders`
56 |
57 | ## 1.0.1
58 |
59 | ### Patch Changes
60 |
61 | - 1ad0905: Update `@tpluscode/rdf-string` - improves inference of return types
62 |
63 | ## 1.0.0
64 |
65 | ### Major Changes
66 |
67 | - 5b1a043: Package is now ESM-only
68 |
69 | ## 0.4.1
70 |
71 | ### Patch Changes
72 |
73 | - 9812437: ESM exported types which failed in runtime
74 |
75 | ## 0.4.0
76 |
77 | ### Minor Changes
78 |
79 | - beaf9e1: When interpolating `SELECT` inside another template, it will automatically be wrapped in a graph pattern
80 |
81 | ## 0.3.31
82 |
83 | ### Patch Changes
84 |
85 | - c7cea63: Main module incorrectly exported types as JS
86 |
87 | ## 0.3.30
88 |
89 | ### Patch Changes
90 |
91 | - d00f737: Export full builder types of query forms
92 | - 0f5f895: Added `DISTINCT` and `AND` which allow modifying the query after it has been initialised
93 |
94 | ## 0.3.29
95 |
96 | ### Patch Changes
97 |
98 | - 66cb644: Allow `string` as parameter of `UNION`
99 |
100 | ## 0.3.28
101 |
102 | ### Patch Changes
103 |
104 | - e04f163: SPARQL `UNION` expression builder
105 |
106 | ## 0.3.27
107 |
108 | ### Patch Changes
109 |
110 | - ec05a22: Added direct dependency on `@types/sparql-http-client`
111 |
112 | ## 0.3.26
113 |
114 | ### Patch Changes
115 |
116 | - 92e4fcf: Add `prefixes` parameter to `build` so ad-hoc prefixes can be applied to a query
117 | Re-export `prefixes` where it's possible to [add prefixes globally](https://github.com/zazuko/rdf-vocabularies#project-specific-prefixes)
118 | (re #81)
119 |
120 | ## 0.3.25
121 |
122 | ### Patch Changes
123 |
124 | - 9f681a2: Type declaration prevented `DELETE.DATA` where the interpolated value was quad array
125 |
126 | ## 0.3.24
127 |
128 | ### Patch Changes
129 |
130 | - 3f1495e: Adding prologue before the prefixes (closes #75)
131 |
132 | ## 0.3.23
133 |
134 | ### Patch Changes
135 |
136 | - 329c89e: Shorthand `CONSTRUCT` syntax would interpolate templates incorrectly, producing invalid SPARQL
137 |
138 | ## 0.3.22
139 |
140 | ### Patch Changes
141 |
142 | - 551f6a1: Updated `@tpluscode/rdf-ns-builders` to v2
143 |
144 | ## 0.3.21
145 |
146 | ### Patch Changes
147 |
148 | - 3231508: Correct syntax for `FROM` in shorthand `CONSTRUCT`
149 |
150 | ## 0.3.20
151 |
152 | ### Patch Changes
153 |
154 | - 6200b56: Add shorthand form `CONSTRUCT WHERE`
155 |
156 | ## 0.3.19
157 |
158 | ### Patch Changes
159 |
160 | - a4dea43: `DESCRIBE` query function should support `ORDER BY` clause
161 |
162 | ## 0.3.18
163 |
164 | ### Patch Changes
165 |
166 | - 0e045af: Wrong URLs in package meta
167 |
168 | ## 0.3.17
169 |
170 | ### Patch Changes
171 |
172 | - ede2fb5: Export types representing queries which can be executed and built
173 | - 837447c: Support for `GROUP BY` and `HAVING` clauses in `SELECT` (closes #57)
174 |
175 | ## 0.3.16
176 |
177 | ### Patch Changes
178 |
179 | - 3e72cd3: Export a function to create `IN` function
180 | - 4afbddb: Builder function for `VALUES` (closes #4)
181 |
182 | ## 0.3.15
183 |
184 | ### Patch Changes
185 |
186 | - 795ff8a: Export SparqlTemplateResult as type
187 |
188 | ## 0.3.14
189 |
190 | ### Patch Changes
191 |
192 | - d9a173b: Re-export SPARQL template from `@tpluscode/rdf-string`
193 | - 51c985a: Update @tpluscode/rdf-ns-builders and rdf-js types
194 |
195 | ## 0.3.13
196 |
197 | ### Patch Changes
198 |
199 | - fbf2e33: Re-export `sparql` string template tag function from [@tpluscode/rdf-string](https://npm.im/@tpluscode/rdf-string)
200 |
201 | ## 0.3.12
202 |
203 | ### Patch Changes
204 |
205 | - 928b9d9: Update @tpluscode/rdf-ns-builders
206 |
207 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
208 |
209 | ### [0.3.11](https://github.com/tpluscode/ts-template/compare/v0.3.10...v0.3.11) (2021-02-18)
210 |
211 | ### Features
212 |
213 | - added WITH clause ([b36a0b2](https://github.com/tpluscode/ts-template/commit/b36a0b22e2de2c59a5fbe4f92a226464118a226d))
214 |
215 | ### [0.3.10](https://github.com/tpluscode/ts-template/compare/v0.3.9...v0.3.10) (2020-11-10)
216 |
217 | ### Features
218 |
219 | - add FROM to ASK and DESCRIBE builders ([63ccaea](https://github.com/tpluscode/ts-template/commit/63ccaea068243e14b9a2fff9e4ebc8b6eabd07f6))
220 |
221 | ### [0.3.9](https://github.com/tpluscode/ts-template/compare/v0.3.8...v0.3.9) (2020-08-08)
222 |
223 | ### Features
224 |
225 | - multiple FROM statements ([5fa4f39](https://github.com/tpluscode/ts-template/commit/5fa4f39c1e2e8ff3f986087853b2fa5b73b2f4e0))
226 |
227 | ### [0.3.8](https://github.com/tpluscode/ts-template/compare/v0.3.7...v0.3.8) (2020-08-08)
228 |
229 | ### Features
230 |
231 | - add FROM (NAMED) to Construct queries ([4a3d662](https://github.com/tpluscode/ts-template/commit/4a3d662974deb3b331160afb013351ddf605a189))
232 | - add FROM NAMED clause builder ([2451561](https://github.com/tpluscode/ts-template/commit/2451561fbb02557dff6219078dd001518abd8186))
233 |
234 | ### [0.3.7](https://github.com/tpluscode/ts-template/compare/v0.3.6...v0.3.7) (2020-08-02)
235 |
236 | ### Features
237 |
238 | - **select:** added SELECT \* shorthand ([ae9fdf4](https://github.com/tpluscode/ts-template/commit/ae9fdf418e204c9bea5849bf5083710bdc8f2df8))
239 |
240 | ### Bug Fixes
241 |
242 | - **select:** added more intuitive API for multiple ORDER BY ([02a31d1](https://github.com/tpluscode/ts-template/commit/02a31d1ebcf6afd0a930749e3e0231aaa5203032))
243 | - **select:** ordering descending was not implemented ([4537ca8](https://github.com/tpluscode/ts-template/commit/4537ca8242cf9c726f3edd0f6934c62db9b72864))
244 |
245 | ### [0.3.6](https://github.com/tpluscode/ts-template/compare/v0.3.5...v0.3.6) (2020-07-01)
246 |
247 | ### [0.3.5](https://github.com/tpluscode/ts-template/compare/v0.3.4...v0.3.5) (2020-06-08)
248 |
249 | ### [0.3.4](https://github.com/tpluscode/ts-template/compare/v0.3.3...v0.3.4) (2020-06-07)
250 |
251 | ### [0.3.3](https://github.com/tpluscode/ts-template/compare/v0.3.2...v0.3.3) (2020-05-21)
252 |
253 | ### [0.3.2](https://github.com/tpluscode/ts-template/compare/v0.3.1...v0.3.2) (2020-04-08)
254 |
255 | ### Bug Fixes
256 |
257 | - skip INSERT in DELETE when empty ([5f0a734](https://github.com/tpluscode/ts-template/commit/5f0a734337480bb618c52b269bca62d64c7c8237))
258 |
259 | ### [0.3.1](https://github.com/tpluscode/ts-template/compare/v0.3.0...v0.3.1) (2020-04-01)
260 |
261 | ### Bug Fixes
262 |
263 | - parameters of execute were not correctly inferred ([87ed666](https://github.com/tpluscode/ts-template/commit/87ed666a853a46adc08eba33ee8eaf9dddc38080))
264 |
265 | ## [0.3.0](https://github.com/tpluscode/ts-template/compare/v0.2.2...v0.3.0) (2020-04-01)
266 |
267 | ### ⚠ BREAKING CHANGES
268 |
269 | - changes the signature of execute method
270 |
271 | ### Features
272 |
273 | - update to sparql-http-client-2 ([9953317](https://github.com/tpluscode/ts-template/commit/99533173972d87a1ca4b38d98cdf9f6fa9d30dfc))
274 |
275 | ### [0.2.2](https://github.com/tpluscode/ts-template/compare/v0.2.1...v0.2.2) (2020-03-02)
276 |
277 | ### Bug Fixes
278 |
279 | - **execute:** incomplete export prevented the BASE from being applied ([7f301a5](https://github.com/tpluscode/ts-template/commit/7f301a51fc23999701abd4fa6e136b473f82911c))
280 | - **execute:** the build was not actually called when executing ([437a749](https://github.com/tpluscode/ts-template/commit/437a749580f4154c37768dd203402d9ceadb223b))
281 |
282 | ### [0.2.1](https://github.com/tpluscode/ts-template/compare/v0.2.0...v0.2.1) (2020-03-02)
283 |
284 | ### Bug Fixes
285 |
286 | - it's impossible to use BASE with execute ([2c43d2b](https://github.com/tpluscode/ts-template/commit/2c43d2bb110fb5c171c2497af11ba1108453559b))
287 |
288 | ## [0.2.0](https://github.com/tpluscode/ts-template/compare/v0.1.2...v0.2.0) (2020-03-02)
289 |
290 | ### ⚠ BREAKING CHANGES
291 |
292 | - will potentially break if `SparqlQuery<>` was imported and not only inferred
293 |
294 | ### Bug Fixes
295 |
296 | - refactor execute to return the correct Response type ([928cdbe](https://github.com/tpluscode/ts-template/commit/928cdbe9c3e841b5546e128706df5f708a3d16e6))
297 |
298 | ### [0.1.2](https://github.com/tpluscode/ts-template/compare/v0.1.1...v0.1.2) (2020-02-28)
299 |
300 | ### Bug Fixes
301 |
302 | - it was impossible to interpolate another (sub-)query ([a060113](https://github.com/tpluscode/ts-template/commit/a060113c5969c2b63aed716dd4ac0eff4b61af4a))
303 |
304 | ### [0.1.1](https://github.com/tpluscode/ts-template/compare/v0.1.0...v0.1.1) (2020-02-25)
305 |
306 | ### Bug Fixes
307 |
308 | - the ORDER BY clause must come before LIMIT/OFFSET ([80f77c6](https://github.com/tpluscode/ts-template/commit/80f77c648821876281665cd59aa8fcb18471f3d5))
309 |
310 | ## [0.1.0](https://github.com/tpluscode/ts-template/compare/v0.0.10...v0.1.0) (2020-02-24)
311 |
312 | ### ⚠ BREAKING CHANGES
313 |
314 | - CONSTRUCT/DESCRIBE now return Response and not a Stream
315 |
316 | ### Features
317 |
318 | - **select:** support for ORDER BY ([6841709](https://github.com/tpluscode/ts-template/commit/68417094b7f9d4b9686efb553d6738958bb491cb))
319 |
320 | * change the graph query API ([ccfe4d6](https://github.com/tpluscode/ts-template/commit/ccfe4d62e71b2141a4f7432c1e5ebd18fec06bde))
321 |
322 | ### [0.0.10](https://github.com/tpluscode/ts-template/compare/v0.0.9...v0.0.10) (2020-02-24)
323 |
324 | ### Bug Fixes
325 |
326 | - base was not applied to nested templates ([9ca4bea](https://github.com/tpluscode/ts-template/commit/9ca4bea68b3d93741a3911fa8257497a1925df27))
327 |
328 | ### [0.0.9](https://github.com/tpluscode/ts-template/compare/v0.0.8...v0.0.9) (2020-02-24)
329 |
330 | ### [0.0.8](https://github.com/tpluscode/ts-template/compare/v0.0.7...v0.0.8) (2020-02-24)
331 |
332 | ### Features
333 |
334 | - support LIMIT/OFFSET in SPARQL Queries ([a52cc02](https://github.com/tpluscode/ts-template/commit/a52cc0275d8b9ac175e13216926f1a563e69a2fc))
335 | - **select:** support REDUCED and DISTINCT ([e3987e8](https://github.com/tpluscode/ts-template/commit/e3987e82d5e633db919e842149f06e690ebb67bd))
336 |
337 | ### [0.0.7](https://github.com/tpluscode/ts-template/compare/v0.0.6...v0.0.7) (2020-02-23)
338 |
339 | ### Bug Fixes
340 |
341 | - missing question marks when interpolating variables ([94e590d](https://github.com/tpluscode/ts-template/commit/94e590daf205af3c01aa7a8e8c9ca430758181e2))
342 |
343 | ### [0.0.6](https://github.com/tpluscode/ts-template/compare/v0.0.5...v0.0.6) (2020-02-23)
344 |
345 | ### [0.0.5](https://github.com/tpluscode/ts-template/compare/v0.0.4...v0.0.5) (2020-02-23)
346 |
347 | ### [0.0.4](https://github.com/tpluscode/ts-template/compare/v0.0.3...v0.0.4) (2020-02-23)
348 |
349 | ### [0.0.3](https://github.com/tpluscode/ts-template/compare/v0.0.2...v0.0.3) (2020-02-23)
350 |
351 | ### [0.0.2](https://github.com/tpluscode/ts-template/compare/v0.0.1...v0.0.2) (2020-02-23)
352 |
353 | ### Features
354 |
355 | - **delete:** chain multiple INSERT and DELETE calls ([d5659dc](https://github.com/tpluscode/ts-template/commit/d5659dc26829eb03b60928c872ee12b54f593abb))
356 | - **delete:** chaining multiple DATA calls ([c1d3c4c](https://github.com/tpluscode/ts-template/commit/c1d3c4cd9c507d56438f23175a05c2184140886d))
357 | - **delete:** support DELETE..WHERE updates ([6d8dc1f](https://github.com/tpluscode/ts-template/commit/6d8dc1f17a04940ae9a7eca2b5c15c1c53e902d0))
358 | - **insert:** chain multiple INSERT calls ([cc79fed](https://github.com/tpluscode/ts-template/commit/cc79fedf063615dbd0e1da1bd12e7186ac835913))
359 | - **insert:** chaining multiple DATA calls ([60d2b50](https://github.com/tpluscode/ts-template/commit/60d2b50d612a5827bafb0e05bad5c4b3660adc47))
360 | - **insert:** support INSERT..WHERE updates ([e8d254b](https://github.com/tpluscode/ts-template/commit/e8d254b17442aa689dbccd99e3c98adbb44f38de))
361 | - **where:** chaining multiple WHERE calls ([ca88d77](https://github.com/tpluscode/ts-template/commit/ca88d77b2aefcd9fcdc7761a03f29290d19b5678))
362 |
363 | ### 0.0.1 (2020-02-22)
364 |
365 | ### Features
366 |
367 | - moved all existing code from data-cube-curation ([71c1212](https://github.com/tpluscode/ts-template/commit/71c121246c1a61b7b23ad723da31c920fb5af778))
368 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > # @tpluscode/sparql-builder  [](https://codecov.io/gh/tpluscode/sparql-builder) [](https://badge.fury.io/js/%40tpluscode%2Fsparql-builder)
2 |
3 | Simple library to create SPARQL queries with tagged ES Template Strings
4 |
5 | ## How is it different from simply string concatenation/plain templates?
6 |
7 | 1. Focuses on graph patterns. No need to remember exact syntax
8 | 1. Still looks like SPARQL, having a familiar structure and little glue code
9 | 1. Has the IDE provide syntactic hints of valid SPARQL keywords
10 | 1. Ensures correct formatting of terms (URI nodes, literals variables) via [@tpluscode/rdf-string](https://github.com/tpluscode/rdf-string)
11 | 1. Automatically shortens URIs with [`@zazuko/prefixes`](http://npm.im/@zazuko/prefixes)
12 |
13 | ## Installation
14 |
15 | ```
16 | npm i -S @tpluscode/sparql-builder
17 | ```
18 |
19 | To be able to execute queries against a remote endpoint install a peer
20 | dependency:
21 |
22 | ```
23 | npm i -S sparql-http-client
24 | ```
25 |
26 | ## Usage
27 |
28 | ### Build a SPARQL query string
29 |
30 | ```js
31 | import rdf from '@zazuko/env'
32 | import { SELECT } from '@tpluscode/sparql-builder'
33 |
34 | const ex = rdf.namespace('http://example.com/')
35 | const { foaf } = rdf.ns
36 |
37 | /*
38 | PREFIX foaf:
39 |
40 | SELECT ?person ?name
41 | FROM
42 | WHERE
43 | {
44 | ?person a foaf:Person ;
45 | foaf:name ?name .
46 | }
47 | */
48 | const person = rdf.variable('person')
49 | const query =
50 | SELECT`${person} ?name`
51 | .FROM(ex.People)
52 | .WHERE`
53 | ${person} a ${foaf.Person} ;
54 | ${foaf.name} ?name .
55 | `.build()
56 | ```
57 |
58 | ### Executing a query
59 |
60 | Using [`sparql-http-client`](https://github.com/zazuko/sparql-http-client)
61 |
62 | ```js
63 | import rdf from '@zazuko/env'
64 | import SparqlHttp from 'sparql-http-client'
65 | import { ASK } from '@tpluscode/sparql-builder'
66 |
67 | const { dbo } = rdf.ns
68 | const dbr = rdf.namespace('http://dbpedia.org/resource/')
69 |
70 | const client = new SparqlHttp({
71 | factory: rdf,
72 | endpointUrl: 'http://dbpedia.org/sparql',
73 | })
74 |
75 | const scoobyDoo = dbr('Scooby-Doo')
76 |
77 | /*
78 | PREFIX dbo:
79 |
80 | ASK {
81 | a dbo:Person .
82 | }
83 | */
84 | ASK`${scoobyDoo} a ${dbo.Person}`
85 | .execute(client)
86 | .then(isScoobyAPerson => {
87 | // Fun fact: DBpedia seems to claim that Scooby-Doo is indeed a Person...
88 | return isScoobyAPerson
89 | })
90 | ```
91 |
92 | ## Running examples
93 |
94 | The example in [`docs/examples`](docs/examples) can be executed locally. To do so, first replace the package import to point to the repository root.
95 |
96 | ```diff
97 | -const { /* */ } = require('@tpluscode/sparql-builder')
98 | +const { /* */ } = require('../..')
99 | ```
100 |
101 | Then simply `npm run example`.
102 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpluscode/sparql-builder/4e9073e79b53c0786827109975657b0cb3f14b4e/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/ASK.md:
--------------------------------------------------------------------------------
1 | # ASK
2 |
3 | Reference: https://www.w3.org/TR/sparql11-query/#ask
4 |
5 | ## Simple query with a `WHERE` clause
6 |
7 |
8 |
9 | ```js
10 | const { ASK } = require('@tpluscode/sparql-builder')
11 | const { variable } = require('@rdfjs/data-model')
12 | const { schema } = require('@tpluscode/rdf-ns-builders')
13 |
14 | const person = variable('person')
15 |
16 | ASK`${person} a ${schema.Person}`.build()
17 | ```
18 |
19 |
20 |
--------------------------------------------------------------------------------
/docs/CONSTRUCT.md:
--------------------------------------------------------------------------------
1 | # CONSTRUCT
2 |
3 | Construct builder is similar to [`SELECT`](./SELECT.md) but the main difference is that instead of variables valid triple patterns must return from the string template.
4 |
5 |
6 |
7 | ```js
8 | const { CONSTRUCT } = require('@tpluscode/sparql-builder')
9 | const { schema } = require('@tpluscode/rdf-ns-builders')
10 | const { namedNode } = require('@rdfjs/data-model')
11 |
12 | CONSTRUCT`?person ?p ?o`
13 | .FROM(namedNode('urn:graph:default'))
14 | .FROM().NAMED(namedNode('urn:graph:john'))
15 | .FROM().NAMED(namedNode('urn:graph:jane'))
16 | .WHERE`GRAPH ?g {
17 | ?person a ${schema.Person} .
18 | ?person ?p ?o .
19 | }`.build()
20 | ```
21 |
22 |
23 |
--------------------------------------------------------------------------------
/docs/DELETE-DATA.md:
--------------------------------------------------------------------------------
1 | # DELETE DATA
2 |
3 | TBD
4 |
--------------------------------------------------------------------------------
/docs/DELETE.md:
--------------------------------------------------------------------------------
1 | # DELETE
2 |
3 | TBD
4 |
--------------------------------------------------------------------------------
/docs/DESCRIBE.md:
--------------------------------------------------------------------------------
1 | # DESCRIBE
2 |
3 | Reference: https://www.w3.org/TR/sparql11-query/#describe
4 |
5 | ## Typical usage
6 |
7 |
8 |
9 | ```js
10 | const { DESCRIBE } = require('@tpluscode/sparql-builder')
11 | const { variable } = require('@rdf-esm/data-model')
12 | const { schema } = require('@tpluscode/rdf-ns-builders')
13 |
14 | const person = variable('person')
15 |
16 | DESCRIBE`${person}`
17 | .WHERE`
18 | ?person a ${schema.Person}
19 | `
20 | .build()
21 | ```
22 |
23 |
24 |
25 | ## URIs, Without `WHERE'
26 |
27 |
28 |
29 | ```js
30 | const { DESCRIBE } = require('@tpluscode/sparql-builder')
31 | const { namedNode } = require('@rdf-esm/data-model')
32 |
33 | const person = namedNode('http://example.com/person')
34 |
35 | DESCRIBE`${person}`.build()
36 | ```
37 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/INSERT-DATA.md:
--------------------------------------------------------------------------------
1 | # INSERT DATA
2 |
3 | TBD
4 |
--------------------------------------------------------------------------------
/docs/INSERT.md:
--------------------------------------------------------------------------------
1 | # INSERT
2 |
3 | TBD
4 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # About
2 |
3 | This package helps create SPARQL Queries/Updates in JavaScript by doing a
4 | little magic with ES Strings Templates.
5 |
6 | ## Installation
7 |
8 | ```
9 | npm i -S @tpluscode/sparql-builder
10 | ```
11 |
12 | ## What it can do?
13 |
14 | * Provides an API which looks like SPARQL
15 | * Uses [@tpluscode/rdf-string](https://github.com/tpluscode/rdf-string) to correctly format interpolated RDF/JS terms
16 | * Shortens URIs from [common vocabularies](https://github.com/zazuko/rdf-vocabularies/tree/master/ontologies) and introduces `PREFIX` statements
17 | * Execute queries against remote SPARQL endpoint
18 |
19 | ## What it does not do?
20 |
21 | * Provide API for keywords other than top-level (such as `OPTIONAL` or `GRAPH`)
22 | * Verify syntax
23 | * Process the constant parts (ie. not the static parts of string templates)
24 |
25 | ## Compatibility
26 |
27 | Package as both commonjs and ES modules, the package can be used in node as
28 | well as browsers.
29 |
--------------------------------------------------------------------------------
/docs/SELECT.md:
--------------------------------------------------------------------------------
1 | # SELECT
2 |
3 | Reference: https://www.w3.org/TR/sparql11-query/#select
4 |
5 | ## Simple SELECT * query
6 |
7 |
8 |
9 | ```js
10 | const { SELECT } = require('@tpluscode/sparql-builder')
11 | const { variable } = require('@rdfjs/data-model')
12 | const { schema } = require('@tpluscode/rdf-ns-builders')
13 |
14 | const person = variable('person')
15 |
16 | SELECT.ALL.WHERE`${person} a ${schema.Person}`.build()
17 | ```
18 |
19 |
20 |
21 | ## `SELECT DISTINCT`
22 |
23 |
24 |
25 | ```js
26 | const { SELECT } = require('@tpluscode/sparql-builder')
27 | const { schema } = require('@tpluscode/rdf-ns-builders')
28 |
29 | SELECT.DISTINCT`?person`.WHERE`?person a ${schema.Person}`.build()
30 | ```
31 |
32 |
33 |
34 | ## `SELECT REDUCED`
35 |
36 |
37 |
38 | ```js
39 | const { SELECT } = require('@tpluscode/sparql-builder')
40 | const { schema } = require('@tpluscode/rdf-ns-builders')
41 |
42 | SELECT.REDUCED`?person`.WHERE`?person a ${schema.Person}`.build()
43 | ```
44 |
45 |
46 |
47 | ## `FROM (NAMED)`
48 |
49 |
50 |
51 | ```js
52 | const { SELECT } = require('@tpluscode/sparql-builder')
53 | const { schema } = require('@tpluscode/rdf-ns-builders')
54 | const { namedNode } = require('@rdfjs/data-model')
55 |
56 | SELECT`?person`
57 | .FROM(namedNode('urn:graph:default'))
58 | .FROM().NAMED(namedNode('urn:graph:john'))
59 | .FROM().NAMED(namedNode('urn:graph:jane'))
60 | .WHERE`GRAPH ?g {
61 | ?person a ${schema.Person}
62 | }`.build()
63 | ```
64 |
65 |
66 |
67 | ## `GROUP BY/HAVING`
68 |
69 | Simple grouping. Argument to `BY` can be a RDF/JS variable or string
70 |
71 |
72 |
73 | ```js
74 | const { SELECT } = require('@tpluscode/sparql-builder')
75 | const { schema } = require('@tpluscode/rdf-ns-builders')
76 |
77 | SELECT`?person (AVG(?age) as ?avg)`
78 | .WHERE`GRAPH ?g {
79 | ?person a ${schema.Person} ;
80 | ${schema.parent} ?parent .
81 | ?parent ${schema.age} ?age
82 | }`
83 | .GROUP().BY('person')
84 | .HAVING`AVG(?age) < 20`
85 | .build()
86 | ```
87 |
88 |
89 |
90 | Grouping can be done by expression and using binding keyword.
91 | Similarly, argument to `AS` can be a RDF/JS variable or string
92 |
93 |
94 |
95 | ```js
96 | const { SELECT } = require('@tpluscode/sparql-builder')
97 | const { schema } = require('@tpluscode/rdf-ns-builders')
98 |
99 | SELECT`?name (AVG(?age) as ?avg)`
100 | .WHERE`GRAPH ?g {
101 | ?person a ${schema.Person} ;
102 | ${schema.parent} ?parent .
103 | ?parent ${schema.age} ?age
104 | }`
105 | .GROUP()
106 | .BY`REPLACE(STR(?person), "^(.+)/[^/]$", "")`
107 | .AS('name')
108 | .HAVING`AVG(?age) < 20`
109 | .build()
110 | ```
111 |
112 |
113 |
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | * Getting started
2 | * [About](/)
3 | * [How it works](overview.md)
4 | * Examples
5 | * SPARQL Query
6 | * [SELECT](SELECT.md)
7 | * [ASK](ASK.md)
8 | * [DESCRIBE](DESCRIBE.md)
9 | * [CONSTRUCT](CONSTRUCT.md)
10 | * [Solution Modifiers](modifiers.md)
11 | * SPARQL Update
12 | * [INSERT](INSERT.md)
13 | * [DELETE](DELETE.md)
14 | * [INSERT DATA](INSERT-DATA.md)
15 | * [DELETE DATA](DELETE-DATA.md)
16 | * [Query prologue](prologue.md)
17 | * [Executing a query](execute.md)
18 | * [Expressions and operators](expressions.md)
19 | * References
20 | * [API](/api)
21 |
--------------------------------------------------------------------------------
/docs/examples/execute-construct.js:
--------------------------------------------------------------------------------
1 | const ParsingClient = require('sparql-http-client/ParsingClient')
2 | const { CONSTRUCT, SELECT } = require('@tpluscode/sparql-builder')
3 | const { variable } = require('@rdfjs/data-model')
4 | const { dataset } = require('@rdfjs/dataset')
5 | const { dbo, foaf } = require('@tpluscode/rdf-ns-builders')
6 | const { turtle } = require('@tpluscode/rdf-string')
7 |
8 | const client = new ParsingClient({
9 | endpointUrl: 'http://dbpedia.org/sparql',
10 | })
11 |
12 | const person = variable('person')
13 | const peopleBornInBerlin = SELECT`${person}`
14 | .WHERE` ${person} ${dbo.birthPlace} `
15 | .LIMIT(100)
16 |
17 | const quads = await CONSTRUCT`${person} ?p ?o`
18 | .WHERE`
19 | VALUES ?p { ${dbo.birthDate} ${dbo.deathDate} ${foaf.name} }
20 | ${person} ?p ?o .
21 |
22 | {
23 | ${peopleBornInBerlin}
24 | }
25 | `
26 | .execute(client.query)
27 |
28 | turtle`${dataset(quads)}`.toString()
29 |
--------------------------------------------------------------------------------
/docs/examples/execute-select.js:
--------------------------------------------------------------------------------
1 | const ParsingClient = require('sparql-http-client/ParsingClient')
2 | const { SELECT } = require('@tpluscode/sparql-builder')
3 | const { variable } = require('@rdfjs/data-model')
4 | const { dbo, foaf } = require('@tpluscode/rdf-ns-builders')
5 |
6 | const client = new ParsingClient({
7 | endpointUrl: 'http://dbpedia.org/sparql',
8 | })
9 |
10 | const name = variable('name')
11 | const birth = variable('birth')
12 | const death = variable('death')
13 | const person = variable('person')
14 | const maxBirth = new Date(1900, 1, 1)
15 |
16 | const results = await SELECT`${name} ${birth} ${death} ${person}`
17 | .WHERE`
18 | ${person} ${dbo.birthPlace} .
19 | ${person} ${dbo.birthDate} ${birth} .
20 | ${person} ${foaf.name} ${name} .
21 | ${person} ${dbo.deathDate} ${death} .
22 | `
23 | .LIMIT(20)
24 | // .FILTER`${birth} < ${maxBirth}`
25 | .ORDER().BY(name)
26 | .execute(client.query)
27 |
28 | results.map(r => ({
29 | person: r.person.value,
30 | name: r.name.value,
31 | birth: r.birth.value,
32 | death: r.death.value,
33 | }))
34 |
--------------------------------------------------------------------------------
/docs/execute.md:
--------------------------------------------------------------------------------
1 | # Execute a SPARQL Query
2 |
3 | It is possible to run queries with a simple [sparql-http-client][client] library.
4 | It is a peer dependency so it must be installed explicitly.
5 |
6 | ```
7 | npm i -S sparql-http-client
8 | ```
9 |
10 | [client]: https://npm.im/sparql-http-client
11 |
12 | ## API
13 |
14 | The query is executed by invoking an `execute` method of a query builder instance. It takes a `SparqlHttpClient` as first, required parameter and an optional second which is defined as:
15 |
16 | ```typescript
17 | import { QueryOptions } from 'sparql-http-client'
18 |
19 | type SparqlExecuteOptions = QueryOptions & {
20 | base?: string
21 | }
22 | ```
23 |
24 | In other words, the underlying HTTP fetch request can be full controlled by the `RequestInit` part and the optional `base` property can be set to be used as a `BASE` directive in the resulting SPARQL.
25 |
26 | ## DBpedia examples
27 |
28 | The queries below are adapted from DBpedias [Online Access](https://wiki.dbpedia.org/OnlineAccess) examples.
29 |
30 | ### SELECT
31 |
32 |
33 |
34 | [select](examples/execute-select.js ':include')
35 |
36 |
37 |
38 | ### CONSTRUCT/DESCRIBE
39 |
40 |
41 |
42 | [select](examples/execute-construct.js ':include')
43 |
44 |
45 |
--------------------------------------------------------------------------------
/docs/expressions.md:
--------------------------------------------------------------------------------
1 | # Expressions and operators
2 |
3 | `@tpluscode/sparql-builder/expressions` exports some SPARQL function and expressions so that they are more easily incorporated into the constructed query.
4 |
5 | ## `IN`
6 |
7 | Reference: https://www.w3.org/TR/sparql11-query/#func-in
8 |
9 |
10 |
11 | ```js
12 | const { SELECT } = require('@tpluscode/sparql-builder')
13 | const { IN } = require('@tpluscode/sparql-builder/expressions')
14 | const { schema } = require('@tpluscode/rdf-ns-builders')
15 |
16 | const names = ['John', 'Jane']
17 |
18 | SELECT.ALL.WHERE`
19 | ?person a ${schema.Person} ; ?person ${schema.name} ?name .
20 |
21 | FILTER (
22 | ?name ${IN(...names)}
23 | )
24 | `.build()
25 | ```
26 |
27 |
28 |
29 | ## `VALUES`
30 |
31 | Reference: https://www.w3.org/TR/sparql11-query/#inline-data
32 |
33 |
34 |
35 | ```js
36 | const { SELECT } = require('@tpluscode/sparql-builder')
37 | const { VALUES } = require('@tpluscode/sparql-builder/expressions')
38 | const { schema, dcterms } = require('@tpluscode/rdf-ns-builders')
39 | const { namedNode } = require('@rdf-esm/data-model')
40 |
41 | // null or undefined values
42 | // will serialize as UNDEF
43 | const values = [
44 | { book: null, title: 'SPARQL Tutorial' },
45 | { book: namedNode('http://example.org/book') }
46 | ]
47 |
48 | SELECT.ALL.WHERE`
49 | ${VALUES(...values)}
50 |
51 | ?book ${dcterms.title} ?title ;
52 | ${schema.price} ?price .
53 | `.build()
54 | ```
55 |
56 |
57 |
58 |
59 | ## `UNION`
60 |
61 |
62 |
63 | ```js
64 | const { sparql, SELECT } = require('@tpluscode/sparql-builder')
65 | const { UNION } = require('@tpluscode/sparql-builder/expressions')
66 | const { schema, foaf } = require('@tpluscode/rdf-ns-builders')
67 |
68 | const schemaName = sparql`?resource ${schema.name} ?name`
69 | const foafName = sparql`?resource ${foaf.name} ?name`
70 |
71 | SELECT`?name`.WHERE`
72 | ${UNION(schemaName, foafName)}
73 | `.build()
74 | ```
75 |
76 |
77 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @tpluscode/sparql-builder - Simple JS library to build SPARQL queries
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/docs/modifiers.md:
--------------------------------------------------------------------------------
1 | # Modifiers
2 |
3 | Reference: https://www.w3.org/TR/sparql11-query/#solutionModifiers
4 |
5 | ## LIMIT/OFFSET
6 |
7 | https://www.w3.org/TR/sparql11-query/#modOffset
8 |
9 |
10 |
11 | ```js
12 | const { CONSTRUCT } = require('@tpluscode/sparql-builder')
13 | const { variable } = require('@rdfjs/data-model')
14 | const { schema } = require('@tpluscode/rdf-ns-builders')
15 |
16 | const person = variable('person')
17 |
18 | CONSTRUCT`${person} a ${schema.Person}`.LIMIT(30).build()
19 | ```
20 |
21 |
22 |
--------------------------------------------------------------------------------
/docs/overview.md:
--------------------------------------------------------------------------------
1 | # How it works
2 |
3 | The API takes top-level keywords of SPARQL Query/Update and exports them
4 | as builder. The intention is to closely match the syntax of SPARQL within
5 | JavaScript source code.
6 |
7 | For example, here's a slightly more complex query which uses a number of
8 | SPARQL's constructs:
9 |
10 | ```sparql
11 | BASE
12 | PREFIX foaf:
13 | PREFIX schema:
14 |
15 | SELECT DISTINCT ?person ?name
16 | FROM
17 | WHERE {
18 | ?person a foaf:Person .
19 | ?person schema:name ?name .
20 | }
21 | LIMIT 10 OFFSET 130
22 | ```
23 |
24 | This could be written in JS as follows.
25 |
26 |
27 |
28 | ```js
29 | const { SELECT } = require('@tpluscode/sparql-builder')
30 | const { foaf, schema } = require('@tpluscode/rdf-ns-builders')
31 | const RDF = require('@rdfjs/data-model')
32 |
33 | const people = RDF.namedNode('http://example.com/people')
34 | const person = RDF.variable('person')
35 | const name = RDF.variable('name')
36 |
37 | SELECT.DISTINCT`${person} ${name}`
38 | .FROM(people)
39 | .WHERE`
40 | ${person} a ${foaf.Person} .
41 | ${person} ${schema.name} ${name} .
42 | `
43 | .LIMIT(10).OFFSET(130).build({
44 | base: 'http://example.com/',
45 | })
46 | ```
47 |
48 |
49 |
--------------------------------------------------------------------------------
/docs/prologue.md:
--------------------------------------------------------------------------------
1 | # Query prologue
2 |
3 | The builder automatically handles interpolated named nodes and extracts their namespace into
4 | the list of prefixes. However, it may be desired to insert additional contents at the beginning
5 | of a query, such a comment, base URL or additional prefixes, such as modifiers that AllegroGraph
6 | uses.
7 |
8 | ## Typical usage
9 |
10 |
11 |
12 | ```js
13 | const { DESCRIBE } = require('@tpluscode/sparql-builder')
14 | const { variable, namedNode } = require('@rdf-esm/data-model')
15 | const namespace = require('@rdf-esm/namespace@0.5.0')
16 |
17 | const ns = namespace('http://example.com/')
18 | const person = variable('person')
19 |
20 | DESCRIBE`${person}`
21 | .WHERE`
22 | ?person a
23 | `
24 | .prologue`#pragma describe.strategy cbd`
25 | .prologue`BASE ${ns()}`
26 | .build()
27 | ```
28 |
29 |
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tpluscode/sparql-builder",
3 | "version": "3.0.1",
4 | "description": "Simple JS library to build SPARQL queries",
5 | "main": "index.js",
6 | "module": "index.js",
7 | "type": "module",
8 | "types": "index.d.ts",
9 | "sideEffects": false,
10 | "files": [
11 | "CHANGELOG.md",
12 | "lib",
13 | "*.js",
14 | "*.d.ts",
15 | "*.d.ts.map"
16 | ],
17 | "exports": {
18 | ".": "./index.js",
19 | "./expressions": "./expressions.js"
20 | },
21 | "scripts": {
22 | "prepare": "husky",
23 | "lint": "eslint . --ext .ts --quiet --ignore-path .gitignore --ignore-path .eslintignore",
24 | "test": "c8 --reporter=lcov mocha test/**/*.test.ts test/*.test.ts",
25 | "prepack": "npm run build",
26 | "build": "tsc -p tsconfig.json",
27 | "docs": "docsify serve docs",
28 | "release": "changeset publish"
29 | },
30 | "dependencies": {
31 | "@rdfjs/data-model": "^2",
32 | "@rdfjs/term-set": "^2",
33 | "@rdfjs/types": "*",
34 | "@tpluscode/rdf-string": "^1.3.0",
35 | "@types/sparql-http-client": "^3.0.0"
36 | },
37 | "peerDependencies": {
38 | "sparql-http-client": "^3.0.0"
39 | },
40 | "devDependencies": {
41 | "@changesets/cli": "^2.16.0",
42 | "@tpluscode/eslint-config": "^0.4.5",
43 | "@types/chai": "^4.3.4",
44 | "@types/chai-as-promised": "^7.1.5",
45 | "@types/chai-snapshot-matcher": "^1.0.1",
46 | "@types/mocha": "^10.0.1",
47 | "@types/node": "^18.15.3",
48 | "@types/sinon": "^10.0.13",
49 | "@types/sinon-chai": "^3.2.9",
50 | "@types/sparqljs": "^3.1.2",
51 | "@typescript-eslint/eslint-plugin": "^7.3.1",
52 | "@typescript-eslint/parser": "^7.3.1",
53 | "@zazuko/env": "^2.0.6",
54 | "c8": "^7.13.0",
55 | "chai": "^4.5.0",
56 | "chai-as-promised": "^7.1.1",
57 | "docsify-cli": "^4.4.3",
58 | "eslint-import-resolver-typescript": "^3.5.3",
59 | "husky": "^9.0.11",
60 | "get-stream": "^9.0.1",
61 | "isomorphic-fetch": "^3.0.0",
62 | "lint-staged": "^15.2.2",
63 | "mocha": "^10.7.3",
64 | "mocha-chai-jest-snapshot": "^1.1.4",
65 | "npm-run-all": "^4.1.5",
66 | "sinon": "^17.0.1",
67 | "sinon-chai": "^3.7.0",
68 | "sparql-http-client": "^3.0.0",
69 | "sparqljs": "^3.0.1",
70 | "ts-node": "^10.9.1",
71 | "typedoc": "^0.23.27",
72 | "typescript": "^5.0.2"
73 | },
74 | "repository": {
75 | "type": "git",
76 | "url": "git+https://github.com/tpluscode/sparql-builder.git"
77 | },
78 | "author": "Tomasz Pluskiewicz",
79 | "license": "MIT",
80 | "bugs": {
81 | "url": "https://github.com/tpluscode/sparql-builder/issues"
82 | },
83 | "homepage": "https://github.com/tpluscode/sparql-builder#readme",
84 | "publishConfig": {
85 | "access": "public"
86 | },
87 | "lint-staged": {
88 | "*.{js,ts}": [
89 | "eslint --fix --quiet"
90 | ]
91 | },
92 | "mocha": {
93 | "loader": "ts-node/esm/transpile-only",
94 | "require": [
95 | "test/mochaSetup.js"
96 | ]
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/expressions.ts:
--------------------------------------------------------------------------------
1 | export { IN } from './lib/expressions/in.js'
2 | export { VALUES } from './lib/expressions/values.js'
3 | export { UNION } from './lib/expressions/union.js'
4 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { sparql } from '@tpluscode/rdf-string'
2 | export { DELETE } from './lib/DeleteBuilder.js'
3 | export type { DeleteInsertQuery as DeleteInsert, DeleteData } from './lib/DeleteBuilder.js'
4 | export { INSERT } from './lib/InsertBuilder.js'
5 | export type { InsertQuery as Insert, InsertData } from './lib/InsertBuilder.js'
6 | export { ASK } from './lib/AskBuilder.js'
7 | export type { AskQuery as Ask } from './lib/AskBuilder.js'
8 | export { DESCRIBE } from './lib/DescribeBuilder.js'
9 | export type { DescribeQuery as Describe } from './lib/DescribeBuilder.js'
10 | export { CONSTRUCT } from './lib/ConstructBuilder.js'
11 | export type { ConstructQuery as Construct } from './lib/ConstructBuilder.js'
12 | export { SELECT } from './lib/SelectBuilder.js'
13 | export type { SelectQuery as Select } from './lib/SelectBuilder.js'
14 | export type { WithQuery } from './lib/WithBuilder.js'
15 | export { WITH } from './lib/WithBuilder.js'
16 | export { prefixes } from '@tpluscode/rdf-string'
17 | export type { SparqlTemplateResult } from '@tpluscode/rdf-string'
18 | export type {
19 | SparqlAskExecutable, SparqlGraphQueryExecutable, SparqlQueryExecutable, SparqlUpdateExecutable,
20 | } from './lib/index.js'
21 |
--------------------------------------------------------------------------------
/src/lib/AskBuilder.ts:
--------------------------------------------------------------------------------
1 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
2 | import { ask } from './execute.js'
3 | import LIMIT, { LimitOffsetBuilder } from './partials/LIMIT.js'
4 | import FROM, { FromBuilder } from './partials/FROM.js'
5 | import Builder, { SparqlQuery, SparqlAskExecutable } from './index.js'
6 |
7 | export type AskQuery = SparqlQuery
8 | & SparqlAskExecutable
9 | & FromBuilder
10 | & LimitOffsetBuilder & {
11 | readonly patterns: SparqlTemplateResult
12 | }
13 |
14 | export const ASK = (strings: TemplateStringsArray, ...values: SparqlValue[]): AskQuery => ({
15 | ...Builder('ASK'),
16 | ...ask,
17 | ...LIMIT(),
18 | ...FROM(),
19 | patterns: sparql(strings, ...values),
20 | _getTemplateResult(): SparqlTemplateResult {
21 | return sparql`ASK ${this.fromClause()} { ${this.patterns} }
22 | ${this.limitOffsetClause()}`
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/src/lib/ConstructBuilder.ts:
--------------------------------------------------------------------------------
1 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
2 | import { graph } from './execute.js'
3 | import WHERE, { WhereBuilder } from './partials/WHERE.js'
4 | import LIMIT, { LimitOffsetBuilder } from './partials/LIMIT.js'
5 | import FROM, { FromBuilder } from './partials/FROM.js'
6 | import Builder, { SparqlGraphQueryExecutable, SparqlQuery } from './index.js'
7 |
8 | export type ConstructQuery = SparqlQuery
9 | & SparqlGraphQueryExecutable
10 | & WhereBuilder
11 | & FromBuilder
12 | & LimitOffsetBuilder & {
13 | readonly constructTemplate: SparqlTemplateResult
14 | readonly shorthand: boolean
15 | }
16 |
17 | interface ConstructBuilder {
18 | (strings: TemplateStringsArray, ...values: SparqlValue[]): ConstructQuery
19 | WHERE(strings: TemplateStringsArray, ...values: SparqlValue[]): ConstructQuery
20 | }
21 |
22 | const builder = (strings: TemplateStringsArray, ...values: SparqlValue[]): ConstructQuery => ({
23 | ...Builder('CONSTRUCT'),
24 | ...graph,
25 | ...WHERE({
26 | required: true,
27 | }),
28 | ...LIMIT(),
29 | ...FROM(),
30 | shorthand: false,
31 | constructTemplate: sparql(strings, ...values),
32 | _getTemplateResult(): SparqlTemplateResult {
33 | if (this.shorthand) {
34 | return sparql`CONSTRUCT
35 | ${this.fromClause()}
36 | WHERE { ${this.constructTemplate} }
37 | ${this.limitOffsetClause()}`
38 | }
39 |
40 | return sparql`CONSTRUCT { ${this.constructTemplate} }
41 | ${this.fromClause()}
42 | ${this.whereClause()}
43 | ${this.limitOffsetClause()}`
44 | },
45 | })
46 |
47 | builder.WHERE = (strings: TemplateStringsArray, ...values: SparqlValue[]) => ({
48 | ...builder(strings, ...values),
49 | shorthand: true,
50 | })
51 |
52 | export const CONSTRUCT = builder as ConstructBuilder
53 |
--------------------------------------------------------------------------------
/src/lib/DeleteBuilder.ts:
--------------------------------------------------------------------------------
1 | import { Literal, NamedNode } from '@rdfjs/types'
2 | import { sparql, SparqlValue, SparqlTemplateResult } from '@tpluscode/rdf-string'
3 | import { update } from './execute.js'
4 | import DATA, { QuadDataBuilder } from './partials/DATA.js'
5 | import WHERE, { WhereBuilder } from './partials/WHERE.js'
6 | import INSERT, { InsertBuilder } from './partials/INSERT.js'
7 | import { concat } from './TemplateResult.js'
8 | import Builder, { SparqlQuery, SparqlUpdateExecutable } from './index.js'
9 |
10 | export type DeleteInsertQuery = InsertBuilder
11 | & WhereBuilder
12 | & SparqlQuery
13 | & SparqlUpdateExecutable & {
14 | readonly deletePatterns: SparqlTemplateResult
15 | readonly with?: NamedNode
16 | readonly using?: NamedNode[]
17 | readonly usingNamed?: NamedNode[]
18 | DELETE(strings: TemplateStringsArray, ...values: SparqlValue[]): DeleteInsertQuery
19 | }
20 |
21 | export type DeleteData = SparqlQuery & SparqlUpdateExecutable & QuadDataBuilder
22 |
23 | export const DELETE = (strings: TemplateStringsArray, ...values: SparqlValue[]): DeleteInsertQuery => ({
24 | ...Builder('UPDATE'),
25 | ...update,
26 | ...WHERE({
27 | required: true,
28 | }),
29 | ...INSERT(),
30 | deletePatterns: sparql(strings, ...values),
31 | DELETE(strings: TemplateStringsArray, ...values: SparqlValue[]): DeleteInsertQuery {
32 | return {
33 | ...this,
34 | deletePatterns: concat(this.deletePatterns, strings, values),
35 | }
36 | },
37 | _getTemplateResult() {
38 | if (!this.insertPatterns) {
39 | return sparql`DELETE { ${this.deletePatterns} } ${this.whereClause()}`
40 | }
41 |
42 | return sparql`DELETE { ${this.deletePatterns} } ${this.insertClause()} ${this.whereClause()}`
43 | },
44 | })
45 |
46 | DELETE.DATA = (strings: TemplateStringsArray, ...values: SparqlValue[]): DeleteData => ({
47 | ...Builder('UPDATE'),
48 | ...update,
49 | ...DATA(strings, values),
50 | _getTemplateResult() {
51 | return sparql`DELETE DATA {
52 | ${this.quadData}
53 | }`
54 | },
55 | })
56 |
--------------------------------------------------------------------------------
/src/lib/DescribeBuilder.ts:
--------------------------------------------------------------------------------
1 | import { NamedNode, Variable } from '@rdfjs/types'
2 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
3 | import { graph } from './execute.js'
4 | import WHERE, { WhereBuilder } from './partials/WHERE.js'
5 | import LIMIT, { LimitOffsetBuilder } from './partials/LIMIT.js'
6 | import FROM, { FromBuilder } from './partials/FROM.js'
7 | import ORDER, { OrderBuilder } from './partials/ORDER.js'
8 | import Builder, { SparqlGraphQueryExecutable, SparqlQuery } from './index.js'
9 |
10 | export type DescribeQuery = SparqlQuery
11 | & SparqlGraphQueryExecutable
12 | & WhereBuilder
13 | & OrderBuilder
14 | & FromBuilder
15 | & LimitOffsetBuilder & {
16 | readonly described: SparqlTemplateResult
17 | }
18 |
19 | export const DESCRIBE = (strings: TemplateStringsArray, ...values: SparqlValue[]): DescribeQuery => ({
20 | ...Builder('CONSTRUCT'),
21 | ...graph,
22 | ...WHERE({
23 | required: false,
24 | }),
25 | ...LIMIT(),
26 | ...FROM(),
27 | ...ORDER(),
28 | described: sparql(strings, ...values),
29 | _getTemplateResult() {
30 | return sparql`DESCRIBE ${this.described}
31 | ${this.fromClause()}
32 | ${this.whereClause()}
33 | ${this.orderClause()}
34 | ${this.limitOffsetClause()}`
35 | },
36 | })
37 |
--------------------------------------------------------------------------------
/src/lib/InsertBuilder.ts:
--------------------------------------------------------------------------------
1 | import { BlankNode, Literal, NamedNode } from '@rdfjs/types'
2 | import { sparql, SparqlValue } from '@tpluscode/rdf-string'
3 | import { update } from './execute.js'
4 | import DATA, { QuadDataBuilder } from './partials/DATA.js'
5 | import WHERE, { WhereBuilder } from './partials/WHERE.js'
6 | import InsertBuilderPartial, { InsertBuilder } from './partials/INSERT.js'
7 | import Builder, { SparqlQuery, SparqlUpdateExecutable } from './index.js'
8 |
9 | export type InsertQuery = SparqlQuery
10 | & SparqlUpdateExecutable
11 | & InsertBuilder
12 | & WhereBuilder & {
13 | readonly with?: NamedNode
14 | readonly using?: NamedNode[]
15 | readonly usingNamed?: NamedNode[]
16 | }
17 |
18 | export type InsertData = SparqlQuery & SparqlUpdateExecutable & QuadDataBuilder
19 |
20 | export const INSERT = (strings: TemplateStringsArray, ...values: SparqlValue[]): InsertQuery => ({
21 | ...Builder('UPDATE'),
22 | ...update,
23 | ...WHERE({
24 | required: true,
25 | }),
26 | ...InsertBuilderPartial(sparql(strings, ...values)),
27 | _getTemplateResult() {
28 | return sparql`${this.insertClause()} ${this.whereClause()}`
29 | },
30 | })
31 |
32 | INSERT.DATA = (strings: TemplateStringsArray, ...values: SparqlValue[]): InsertData => ({
33 | ...Builder('UPDATE'),
34 | ...update,
35 | ...DATA(strings, values),
36 | _getTemplateResult() {
37 | return sparql`INSERT DATA {
38 | ${this.quadData}
39 | }`
40 | },
41 | })
42 |
--------------------------------------------------------------------------------
/src/lib/QueryError.ts:
--------------------------------------------------------------------------------
1 | export class QueryError extends Error {
2 | constructor(public query: string, cause: unknown) {
3 | super(`Error executing query: ${(cause as Error)?.message}`)
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/lib/SelectBuilder.ts:
--------------------------------------------------------------------------------
1 | import { Variable } from '@rdfjs/types'
2 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
3 | import { select } from './execute.js'
4 | import WHERE, { WhereBuilder } from './partials/WHERE.js'
5 | import LIMIT, { LimitOffsetBuilder } from './partials/LIMIT.js'
6 | import ORDER, { OrderBuilder } from './partials/ORDER.js'
7 | import FROM, { FromBuilder } from './partials/FROM.js'
8 | import GROUP, { GroupBuilder } from './partials/GROUP.js'
9 | import HAVING, { HavingBuilder } from './partials/HAVING.js'
10 | import Builder, { SparqlQuery, SparqlQueryExecutable } from './index.js'
11 |
12 | export type SelectQuery = SparqlQuery
13 | & SparqlQueryExecutable
14 | & WhereBuilder
15 | & LimitOffsetBuilder
16 | & OrderBuilder
17 | & GroupBuilder
18 | & HavingBuilder
19 | & FromBuilder
20 | & {
21 | DISTINCT(): SelectQuery
22 | AND(strings: TemplateStringsArray, ...values: SparqlValue[]): SelectQuery
23 | readonly distinct: boolean
24 | readonly reduced: boolean
25 | readonly variables: SparqlTemplateResult
26 | }
27 |
28 | interface Select {
29 | (strings: TemplateStringsArray, ...values: SparqlValue[]): SelectQuery
30 | DISTINCT: (strings: TemplateStringsArray, ...values: SparqlValue[]) => SelectQuery
31 | REDUCED: (strings: TemplateStringsArray, ...values: SparqlValue[]) => SelectQuery
32 | ALL: SelectQuery
33 | }
34 |
35 | const SelectBuilder = (strings: TemplateStringsArray, ...values: SparqlValue[]): SelectQuery => ({
36 | ...Builder('SELECT'),
37 | ...select,
38 | ...WHERE({
39 | required: true,
40 | }),
41 | ...LIMIT(),
42 | ...ORDER(),
43 | ...GROUP(),
44 | ...HAVING(),
45 | ...FROM(),
46 | DISTINCT() {
47 | return {
48 | ...this,
49 | distinct: true,
50 | }
51 | },
52 | AND(strings: TemplateStringsArray, ...values: SparqlValue[]) {
53 | return {
54 | ...this,
55 | variables: sparql`${this.variables}${sparql(strings, ...values)}`,
56 | }
57 | },
58 | distinct: false,
59 | reduced: false,
60 | variables: sparql(strings, ...values),
61 | _getTemplateResult() {
62 | const modifier = this.distinct ? 'DISTINCT ' : this.reduced ? 'REDUCED ' : ''
63 |
64 | return sparql`SELECT ${modifier}${this.variables}
65 | ${this.fromClause()}
66 | ${this.whereClause()}
67 | ${this.orderClause()}
68 | ${this.groupByClause()}
69 | ${this.havingClause()}
70 | ${this.limitOffsetClause()}`
71 | },
72 | })
73 |
74 | SelectBuilder.DISTINCT = (strings: TemplateStringsArray, ...values: SparqlValue[]): SelectQuery => ({
75 | ...SelectBuilder(strings, ...values),
76 | distinct: true,
77 | })
78 |
79 | SelectBuilder.REDUCED = (strings: TemplateStringsArray, ...values: SparqlValue[]): SelectQuery => ({
80 | ...SelectBuilder(strings, ...values),
81 | reduced: true,
82 | })
83 |
84 | Object.defineProperty(SelectBuilder, 'ALL', {
85 | get() {
86 | return SelectBuilder`*`
87 | },
88 | })
89 |
90 | export const SELECT = SelectBuilder as Select
91 |
--------------------------------------------------------------------------------
/src/lib/TemplateResult.ts:
--------------------------------------------------------------------------------
1 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
2 |
3 | interface Options {
4 | wrapTemplate?: boolean
5 | newLineSeparator?: boolean
6 | }
7 |
8 | export function concat(
9 | current: SparqlTemplateResult | null | undefined,
10 | strings: TemplateStringsArray,
11 | values: SparqlValue[],
12 | { wrapTemplate = false, newLineSeparator = true }: Options = {}): SparqlTemplateResult {
13 | let next = sparql(strings, ...values)
14 | if (wrapTemplate) {
15 | next = sparql`( ${next} )`
16 | }
17 |
18 | if (!current) {
19 | return next
20 | }
21 |
22 | return sparql`${current}${newLineSeparator ? '\n' : ''}
23 | ${next}`
24 | }
25 |
--------------------------------------------------------------------------------
/src/lib/WithBuilder.ts:
--------------------------------------------------------------------------------
1 | import { NamedNode } from '@rdfjs/types'
2 | import { sparql } from '@tpluscode/rdf-string'
3 | import RDF from '@rdfjs/data-model'
4 | import { InsertQuery } from './InsertBuilder.js'
5 | import { DeleteInsertQuery } from './DeleteBuilder.js'
6 | import { update } from './execute.js'
7 | import Builder, { SparqlQuery, SparqlUpdateExecutable } from './index.js'
8 |
9 | export type WithQuery = SparqlUpdateExecutable & SparqlQuery
10 |
11 | export const WITH = (graph: NamedNode | string, query: DeleteInsertQuery | InsertQuery): WithQuery => ({
12 | ...Builder('UPDATE'),
13 | ...update,
14 | _getTemplateResult() {
15 | const graphNode = typeof graph === 'string' ? RDF.namedNode(graph) : graph
16 |
17 | return sparql`WITH ${graphNode}\n${query._getTemplateResult()}`
18 | },
19 | })
20 |
--------------------------------------------------------------------------------
/src/lib/execute.ts:
--------------------------------------------------------------------------------
1 | import type { Client } from 'sparql-http-client'
2 | import { QueryError } from './QueryError.js'
3 | import {
4 | SparqlAskExecutable,
5 | SparqlExecuteOptions,
6 | SparqlGraphQueryExecutable,
7 | SparqlQuery,
8 | SparqlQueryExecutable,
9 | SparqlUpdateExecutable,
10 | } from './index.js'
11 |
12 | interface QueryAction {
13 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
14 | (...args: unknown[]): any
15 | }
16 |
17 | function buildAndRun(builder: SparqlQuery, clientExecute: TAction, { logQuery, ...requestInit }: SparqlExecuteOptions = {}): ReturnType {
18 | const query = builder.build(requestInit)
19 | logQuery?.(query)
20 |
21 | try {
22 | return clientExecute(query, requestInit)
23 | } catch (e) {
24 | throw new QueryError(query, e)
25 | }
26 | }
27 |
28 | export const update: SparqlUpdateExecutable = {
29 | execute(this: SparqlQuery, client: TClient, requestInit: SparqlExecuteOptions): ReturnType {
30 | return buildAndRun(this, client.query.update.bind(client.query), requestInit)
31 | },
32 | }
33 |
34 | export const ask: SparqlAskExecutable = {
35 | execute(this: SparqlQuery, client: TClient, requestInit: SparqlExecuteOptions): ReturnType {
36 | return buildAndRun(this, client.query.ask.bind(client.query), requestInit)
37 | },
38 | }
39 |
40 | export const select: SparqlQueryExecutable = {
41 | execute(this: SparqlQuery, client: TClient, requestInit: SparqlExecuteOptions): ReturnType {
42 | return buildAndRun(this, client.query.select.bind(client.query), requestInit)
43 | },
44 | }
45 |
46 | export const graph: SparqlGraphQueryExecutable = {
47 | execute(this: SparqlQuery, client: TClient, requestInit: SparqlExecuteOptions): ReturnType {
48 | return buildAndRun(this, client.query.construct.bind(client.query), requestInit)
49 | },
50 | }
51 |
--------------------------------------------------------------------------------
/src/lib/expressions/in.ts:
--------------------------------------------------------------------------------
1 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
2 |
3 | export function IN(...items: Array): SparqlTemplateResult {
4 | const list = items.reduce((previous: SparqlTemplateResult | null, next) => {
5 | if (!previous) {
6 | return sparql`${next}`
7 | }
8 |
9 | return sparql`${previous}, ${next}`
10 | }, null)
11 |
12 | return sparql`IN ( ${list || ''} )`
13 | }
14 |
--------------------------------------------------------------------------------
/src/lib/expressions/union.ts:
--------------------------------------------------------------------------------
1 | import { SparqlTemplateResult, sparql, SparqlValue } from '@tpluscode/rdf-string'
2 |
3 | export function UNION(...[first, ...rest]: Array): SparqlTemplateResult {
4 | if (rest.length === 0) {
5 | return sparql`${first}` || ''
6 | }
7 |
8 | return rest.reduce((previousValue, currentValue) => {
9 | return sparql`${previousValue} UNION {
10 | ${currentValue}
11 | }`
12 | }, sparql`{
13 | ${first}
14 | }`)
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/expressions/values.ts:
--------------------------------------------------------------------------------
1 | import { SparqlTemplateResult, SparqlValue, sparql } from '@tpluscode/rdf-string'
2 | import RDF from '@rdfjs/data-model'
3 |
4 | type ValueMap = Record
5 |
6 | function toSingleLine(line: SparqlTemplateResult | undefined, value: SparqlValue): SparqlTemplateResult {
7 | return sparql`${line} ${value}`
8 | }
9 |
10 | export function VALUES(...values: Partial>[]): SparqlTemplateResult | string {
11 | if (values.length === 0) {
12 | return ''
13 | }
14 |
15 | const variables = [...new Set(values.map(Object.keys).flat())].map(RDF.variable)
16 |
17 | const vectors = values.reduce((previous, current: Record) => {
18 | const vector = variables.map((variable) => {
19 | const value = current[variable.value]
20 | if (value === null || typeof value === 'undefined') {
21 | return 'UNDEF'
22 | }
23 | if (typeof value === 'string') {
24 | return RDF.literal(value)
25 | }
26 | return value
27 | })
28 |
29 | return sparql`${previous}\n(${vector.reduce(toSingleLine, sparql``)} )`
30 | }, sparql``)
31 |
32 | return sparql`VALUES (${variables.reduce(toSingleLine, sparql``)} )
33 | {${vectors}
34 | }`
35 | }
36 |
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | import { QueryOptions, Client } from 'sparql-http-client'
2 | import { sparql, SparqlTemplateResult } from '@tpluscode/rdf-string'
3 | import type { NamespaceBuilder } from '@rdfjs/namespace'
4 | import type { SparqlOptions } from '@tpluscode/rdf-string'
5 | import prologue, { PrologueBuilder } from './partials/prologue.js'
6 |
7 | interface SparqlBuildOptions {
8 | base?: string
9 | prefixes?: Record
10 | }
11 |
12 | export type SparqlExecuteOptions = QueryOptions & SparqlBuildOptions & {
13 | logQuery?: (query: string) => void
14 | }
15 |
16 | export interface SparqlQuery extends PrologueBuilder {
17 | type: 'SELECT' | 'CONSTRUCT' | 'ASK' | 'UPDATE'
18 | build(options?: SparqlBuildOptions): string
19 | _getTemplateResult(): SparqlTemplateResult
20 | }
21 |
22 | export interface SparqlQueryExecutable {
23 | execute(client: TClient, requestInit?: SparqlExecuteOptions): ReturnType
24 | }
25 |
26 | export interface SparqlGraphQueryExecutable {
27 | execute(client: TClient, requestInit?: SparqlExecuteOptions): ReturnType
28 | }
29 |
30 | export interface SparqlUpdateExecutable {
31 | execute(client: TClient, requestInit?: SparqlExecuteOptions): ReturnType
32 | }
33 |
34 | export interface SparqlAskExecutable {
35 | execute(client: TClient, requestInit?: SparqlExecuteOptions): ReturnType
36 | }
37 |
38 | type TBuilder = Pick & Pick
39 |
40 | // eslint-disable-next-line no-unused-vars
41 | export default function Builder(type: SparqlQuery['type']): TBuilder & T {
42 | return {
43 | type,
44 | ...prologue(),
45 | build(this: SparqlQuery, { base, prefixes }: SparqlBuildOptions = {}): string {
46 | const queryResult = this._getTemplateResult().toString({
47 | base,
48 | prefixes,
49 | })
50 |
51 | if (this.prologueResult) {
52 | return `${this.prologueResult}\n\n${queryResult}`
53 | }
54 |
55 | return queryResult
56 | },
57 | _toPartialString(this: SparqlQuery, options: SparqlOptions) {
58 | let result = this._getTemplateResult()
59 | if (this.type === 'SELECT') {
60 | result = sparql`{ ${result} }`
61 | }
62 |
63 | return result._toPartialString(options)
64 | },
65 | } as TBuilder & T
66 | }
67 |
--------------------------------------------------------------------------------
/src/lib/partials/DATA.ts:
--------------------------------------------------------------------------------
1 | import { Term } from '@rdfjs/types'
2 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
3 | import { SparqlQuery } from '../index.js'
4 | import { concat } from '../TemplateResult.js'
5 |
6 | export interface QuadDataBuilder {
7 | readonly quadData: SparqlTemplateResult
8 | DATA(strings: TemplateStringsArray, ...values: SparqlValue[]): T
9 | }
10 |
11 | // eslint-disable-next-line no-use-before-define
12 | export default , TTerm extends Term = Term>(strings: TemplateStringsArray, values: SparqlValue[]): QuadDataBuilder => ({
13 | quadData: sparql(strings, ...values),
14 | DATA(strings: TemplateStringsArray, ...values): T {
15 | return {
16 | ...this,
17 | quadData: concat(this.quadData, strings, values),
18 | } as T
19 | },
20 | })
21 |
--------------------------------------------------------------------------------
/src/lib/partials/FROM.ts:
--------------------------------------------------------------------------------
1 | import { DefaultGraph, NamedNode } from '@rdfjs/types'
2 | import TermSet from '@rdfjs/term-set'
3 | import { sparql, SparqlTemplateResult } from '@tpluscode/rdf-string'
4 | import { SparqlQuery } from '../index.js'
5 |
6 | interface FromNamed {
7 | NAMED(graph: NamedNode): T
8 | }
9 |
10 | export interface FromBuilder {
11 | readonly defaultGraph: Set
12 | readonly fromNamed: Set
13 | FROM(defaultGraph: NamedNode | DefaultGraph): T
14 | FROM(): FromNamed
15 | fromClause(): SparqlTemplateResult
16 | }
17 |
18 | export default function > (): FromBuilder {
19 | const builder = {
20 | defaultGraph: new TermSet(),
21 | fromNamed: new TermSet(),
22 | fromClause(): SparqlTemplateResult {
23 | const clause = [...this.defaultGraph.values()].reduce((current, graph) => sparql`${current}\nFROM ${graph}`, sparql``)
24 |
25 | return [...this.fromNamed.values()].reduce((current, graph) => sparql`${current}\nFROM NAMED ${graph}`, clause)
26 | },
27 | }
28 |
29 | return {
30 | ...builder,
31 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
32 | FROM(defaultGraph?: NamedNode | DefaultGraph): any {
33 | if (!defaultGraph) {
34 | return {
35 | NAMED: (graph: NamedNode) => {
36 | this.fromNamed.add(graph)
37 | return this
38 | },
39 | }
40 | }
41 |
42 | if (defaultGraph.termType === 'DefaultGraph') {
43 | this.defaultGraph.clear()
44 | } else {
45 | this.defaultGraph.add(defaultGraph)
46 | }
47 | return this
48 | },
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/lib/partials/GROUP.ts:
--------------------------------------------------------------------------------
1 | import { Variable } from '@rdfjs/types'
2 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
3 | import RDF from '@rdfjs/data-model'
4 | import { SparqlQuery } from '../index.js'
5 |
6 | interface Grouping {
7 | by: SparqlTemplateResult
8 | as?: Variable
9 | }
10 |
11 | interface ThenGroupByBuilder {
12 | // eslint-disable-next-line no-use-before-define
13 | THEN: GroupByBuilder
14 | }
15 |
16 | interface BoundGroupBuilder extends ThenGroupByBuilder {
17 | AS(variable: Variable | string): T & ThenGroupByBuilder
18 | }
19 |
20 | interface GroupByBuilder {
21 | BY(strings: TemplateStringsArray, ...values: SparqlValue[]): T & BoundGroupBuilder
22 | BY(variable: string | Variable, ...values: SparqlValue[]): T & BoundGroupBuilder
23 | }
24 |
25 | export interface GroupBuilder {
26 | groupings: Grouping[]
27 | groupByClause(): SparqlTemplateResult
28 | GROUP(): GroupByBuilder
29 | }
30 |
31 | function isTemplateArray(arg: TemplateStringsArray | unknown): arg is TemplateStringsArray {
32 | return Array.isArray(arg)
33 | }
34 |
35 | function addGrouping>(builder: GroupBuilder) {
36 | return (strings: TemplateStringsArray | string | Variable, ...values: SparqlValue[]): T & BoundGroupBuilder => {
37 | let by: SparqlTemplateResult
38 | if (isTemplateArray(strings)) {
39 | by = sparql(strings, ...values)
40 | } else if (typeof strings === 'string') {
41 | by = sparql`${RDF.variable(strings)}`
42 | } else {
43 | by = sparql`${strings}`
44 | }
45 |
46 | const grouping: Grouping = { by }
47 | const childBuilder = {
48 | ...builder,
49 | groupings: [...builder.groupings, grouping],
50 | } as T & GroupBuilder
51 | const thenBuilder: T & ThenGroupByBuilder = {
52 | ...childBuilder,
53 | THEN: {
54 | BY: addGrouping(childBuilder),
55 | },
56 | }
57 |
58 | return {
59 | ...thenBuilder,
60 | AS(variable: string | Variable) {
61 | if (typeof variable === 'string') {
62 | grouping.as = RDF.variable(variable)
63 | } else {
64 | grouping.as = variable
65 | }
66 |
67 | return thenBuilder
68 | },
69 | } as T & BoundGroupBuilder
70 | }
71 | }
72 |
73 | export default >(): GroupBuilder => ({
74 | groupings: [],
75 | GROUP(): GroupByBuilder {
76 | return {
77 | BY: addGrouping(this),
78 | }
79 | },
80 | groupByClause() {
81 | if (this.groupings.some(Boolean)) {
82 | return this.groupings.reduce((result, grouping) => {
83 | if (grouping.as) {
84 | return sparql`${result}\n (${grouping.by} as ${grouping.as})`
85 | }
86 |
87 | return sparql`${result}\n (${grouping.by})`
88 | },
89 | sparql`GROUP BY`)
90 | }
91 |
92 | return sparql``
93 | },
94 | })
95 |
--------------------------------------------------------------------------------
/src/lib/partials/HAVING.ts:
--------------------------------------------------------------------------------
1 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
2 | import { SparqlQuery } from '../index.js'
3 | import { concat } from '../TemplateResult.js'
4 |
5 | export interface HavingBuilder {
6 | readonly havings: SparqlTemplateResult | null
7 | havingClause(): SparqlTemplateResult
8 | HAVING(strings: TemplateStringsArray, ...values: SparqlValue[]): T
9 | }
10 |
11 | export default >(): HavingBuilder => ({
12 | havings: null,
13 | havingClause() {
14 | if (this.havings) {
15 | return sparql`HAVING ${this.havings}`
16 | }
17 |
18 | return sparql``
19 | },
20 | HAVING(strings: TemplateStringsArray, ...values: SparqlValue[]): T {
21 | return {
22 | ...this,
23 | havings: concat(this.havings, strings, values, { wrapTemplate: true }),
24 | } as T
25 | },
26 | })
27 |
--------------------------------------------------------------------------------
/src/lib/partials/INSERT.ts:
--------------------------------------------------------------------------------
1 | import { SparqlTemplateResult, sparql, SparqlValue } from '@tpluscode/rdf-string'
2 | import { SparqlQuery } from '../index.js'
3 | import { concat } from '../TemplateResult.js'
4 |
5 | export interface InsertBuilder {
6 | readonly insertPatterns: SparqlTemplateResult | null
7 | insertClause(): SparqlTemplateResult
8 | INSERT(strings: TemplateStringsArray, ...values: SparqlValue[]): T
9 | }
10 |
11 | export default >(insertPatterns: SparqlTemplateResult | null = null): InsertBuilder => ({
12 | insertPatterns,
13 | insertClause(): SparqlTemplateResult {
14 | return sparql`INSERT{
15 | ${this.insertPatterns}
16 | }`
17 | },
18 | INSERT(strings: TemplateStringsArray, ...values: SparqlValue[]) {
19 | return {
20 | ...this,
21 | insertPatterns: concat(this.insertPatterns, strings, values),
22 | } as T
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/src/lib/partials/LIMIT.ts:
--------------------------------------------------------------------------------
1 | import { SparqlQuery } from '../index.js'
2 |
3 | export interface LimitOffsetBuilder {
4 | offset: number | null
5 | limit: number | null
6 | LIMIT(limit: number): T
7 | OFFSET(offset: number): T
8 | limitOffsetClause(): string
9 | }
10 |
11 | export default >(): LimitOffsetBuilder => ({
12 | limit: null,
13 | offset: null,
14 | limitOffsetClause() {
15 | let clause = ''
16 | if (this.limit != null) {
17 | clause += `LIMIT ${this.limit} `
18 | }
19 |
20 | if (this.offset != null) {
21 | clause += `OFFSET ${this.offset}`
22 | }
23 |
24 | return clause
25 | },
26 | LIMIT(limit: number): T {
27 | return {
28 | ...this,
29 | limit,
30 | } as T
31 | },
32 | OFFSET(offset: number): T {
33 | return {
34 | ...this,
35 | offset,
36 | } as T
37 | },
38 | })
39 |
--------------------------------------------------------------------------------
/src/lib/partials/ORDER.ts:
--------------------------------------------------------------------------------
1 | import { Variable } from '@rdfjs/types'
2 | import { sparql, SparqlTemplateResult } from '@tpluscode/rdf-string'
3 | import { SparqlQuery } from '../index.js'
4 |
5 | interface OrderCondition {
6 | variable: Variable
7 | desc: boolean
8 | }
9 |
10 | interface ThenOrderByBuilder {
11 | // eslint-disable-next-line no-use-before-define
12 | THEN: OrderByBuilder
13 | }
14 |
15 | interface OrderByBuilder {
16 | BY(variable: Variable, desc?: boolean): T & ThenOrderByBuilder
17 | }
18 |
19 | export interface OrderBuilder {
20 | orderConditions: OrderCondition[]
21 | orderClause(): SparqlTemplateResult
22 | ORDER(): OrderByBuilder
23 | }
24 |
25 | function addOrder>(builder: OrderBuilder) {
26 | return (variable: Variable, desc: boolean): T & ThenOrderByBuilder => {
27 | const thenBuilder = {
28 | ...builder,
29 | orderConditions: [
30 | ...builder.orderConditions, {
31 | variable,
32 | desc,
33 | }],
34 | }
35 |
36 | return {
37 | ...thenBuilder,
38 | THEN: {
39 | BY: addOrder(thenBuilder),
40 | },
41 | } as T & ThenOrderByBuilder
42 | }
43 | }
44 |
45 | export default >(): OrderBuilder => ({
46 | orderConditions: [],
47 | ORDER(): OrderByBuilder {
48 | return {
49 | BY: addOrder(this),
50 | }
51 | },
52 | orderClause() {
53 | if (this.orderConditions.some(Boolean)) {
54 | return this.orderConditions.reduce((result, condition) => {
55 | if (condition.desc) {
56 | return sparql`${result} desc(${condition.variable})`
57 | }
58 |
59 | return sparql`${result} ${condition.variable}`
60 | },
61 | sparql`ORDER BY`)
62 | }
63 |
64 | return sparql``
65 | },
66 | })
67 |
--------------------------------------------------------------------------------
/src/lib/partials/WHERE.ts:
--------------------------------------------------------------------------------
1 | import { sparql, SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
2 | import { SparqlQuery } from '../index.js'
3 | import { concat } from '../TemplateResult.js'
4 |
5 | export interface WhereBuilder {
6 | readonly patterns: SparqlTemplateResult | null
7 | whereClause(): SparqlTemplateResult
8 | WHERE(strings: TemplateStringsArray, ...values: SparqlValue[]): T
9 | }
10 |
11 | export default >({ required }: { required: boolean }): WhereBuilder => ({
12 | patterns: null,
13 | whereClause() {
14 | if (this.patterns) {
15 | return sparql`WHERE {
16 | ${this.patterns}
17 | }`
18 | }
19 |
20 | if (required) {
21 | return sparql`WHERE {}`
22 | }
23 |
24 | return sparql``
25 | },
26 | WHERE(strings: TemplateStringsArray, ...values: SparqlValue[]): T {
27 | return {
28 | ...this,
29 | patterns: concat(this.patterns, strings, values),
30 | } as T
31 | },
32 | })
33 |
--------------------------------------------------------------------------------
/src/lib/partials/prologue.ts:
--------------------------------------------------------------------------------
1 | import { SparqlTemplateResult, SparqlValue } from '@tpluscode/rdf-string'
2 | import { SparqlQuery } from '../index.js'
3 | import { concat } from '../TemplateResult.js'
4 |
5 | export interface PrologueBuilder {
6 | readonly prologueResult: SparqlTemplateResult | null
7 | prologue(strings: TemplateStringsArray, ...values: SparqlValue[]): this
8 | }
9 |
10 | export default (): PrologueBuilder => ({
11 | prologueResult: null,
12 | prologue(strings: TemplateStringsArray, ...values): T {
13 | return {
14 | ...this,
15 | prologueResult: concat(this.prologueResult, strings, values, { newLineSeparator: false }),
16 | } as T
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/test/ASK.test.ts:
--------------------------------------------------------------------------------
1 | import RDF from '@zazuko/env'
2 | import chai, { expect } from 'chai'
3 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'
4 | import { ASK } from '../src/index.js'
5 | import { sparqlClient } from './_mocks.js'
6 | import './sparql.js'
7 |
8 | describe('ASK', () => {
9 | chai.use(jestSnapshotPlugin())
10 |
11 | it('creates expected query', () => {
12 | // given
13 | const expected = `ASK {
14 | ?s ?p ?o .
15 | ?x ?y ?z .
16 | }`
17 |
18 | // when
19 | const query = ASK`?s ?p ?o . ?x ?y ?z .`.build()
20 |
21 | // then
22 | expect(query).to.be.query(expected)
23 | })
24 |
25 | it('executes as ask', async () => {
26 | // given
27 | const client = sparqlClient()
28 |
29 | // when
30 | await ASK``.execute(client)
31 |
32 | // then
33 | expect(client.query.ask).to.have.been.called
34 | })
35 |
36 | it('can have additional prologue', function () {
37 | // given
38 | const base = RDF.namedNode('http://foo.bar/baz')
39 |
40 | // when
41 | const query = ASK`?s ?p ?o .`
42 | .prologue`#pragma join.hash off`
43 | .prologue`BASE ${base}`
44 | .build()
45 |
46 | // then
47 | expect(query).toMatchSnapshot()
48 | })
49 |
50 | it('supports LIMIT/OFFSET', () => {
51 | // given
52 | const expected = `ASK {
53 | ?s ?p ?o .
54 | } LIMIT 10 OFFSET 20`
55 |
56 | // when
57 | const query = ASK`?s ?p ?o .`.LIMIT(10).OFFSET(20).build()
58 |
59 | // then
60 | expect(query).to.be.query(expected)
61 | })
62 |
63 | it('supports FROM (NAMED)', () => {
64 | // given
65 | const expected = `ASK
66 | FROM
67 | FROM NAMED
68 | {
69 | ?s ?p ?o .
70 | }`
71 |
72 | // when
73 | const query = ASK`?s ?p ?o .`
74 | .FROM(RDF.namedNode('http://example.com/foo'))
75 | .FROM().NAMED(RDF.namedNode('http://example.com/bar'))
76 | .build()
77 |
78 | // then
79 | expect(query).to.be.query(expected)
80 | })
81 |
82 | it('can be constructed with a base', () => {
83 | // given
84 | const ns = RDF.namespace('http://example.com/')
85 | const expected = `BASE
86 |
87 | ASK {
88 | a
89 | }`
90 |
91 | // when
92 | const query = ASK`${ns.person} a ${ns.Person} .`.build({
93 | base: 'http://example.com/',
94 | })
95 |
96 | // then
97 | expect(query).to.be.query(expected)
98 | })
99 | })
100 |
--------------------------------------------------------------------------------
/test/CONSTRUCT.test.ts:
--------------------------------------------------------------------------------
1 | import RDF from '@zazuko/env'
2 | import chai, { expect } from 'chai'
3 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'
4 | import { CONSTRUCT, SELECT } from '../src/index.js'
5 | import { sparqlClient } from './_mocks.js'
6 | import './sparql.js'
7 |
8 | const { dbo, foaf, schema, skos } = RDF.ns
9 |
10 | describe('CONSTRUCT', () => {
11 | chai.use(jestSnapshotPlugin())
12 |
13 | it('executes as construct', () => {
14 | // given
15 | const client = sparqlClient()
16 |
17 | // when
18 | CONSTRUCT``.execute(client)
19 |
20 | // then
21 | expect(client.query.construct).to.have.been.called
22 | })
23 |
24 | it('can have additional prologue', function () {
25 | // given
26 | const base = RDF.namedNode('http://foo.bar/baz')
27 |
28 | // when
29 | const query = CONSTRUCT`?s ?p ?o .`
30 | .prologue`#pragma join.hash off`
31 | .prologue`BASE ${base}`
32 | .build()
33 |
34 | // then
35 | expect(query).toMatchSnapshot()
36 | })
37 |
38 | it('generates empty WHERE clause by default', () => {
39 | // given
40 | const expected = `PREFIX schema:
41 | CONSTRUCT { a schema:Person } WHERE {}`
42 |
43 | // when
44 | const actual = CONSTRUCT` a ${schema.Person}`.build()
45 |
46 | // then
47 | expect(actual).to.be.query(expected)
48 | })
49 |
50 | it('support shorthand syntax', () => {
51 | // given
52 | const expected = `PREFIX schema:
53 | CONSTRUCT WHERE { ?person a schema:Person }`
54 |
55 | // when
56 | const actual = CONSTRUCT.WHERE`?person a ${schema.Person}`.build()
57 |
58 | // then
59 | expect(actual).to.be.query(expected)
60 | })
61 |
62 | it('correctly interpolate shorthand syntax', () => {
63 | // given
64 | const root = RDF.namedNode('https://example.com/')
65 | const expected = `PREFIX skos: <${skos().value}>
66 | CONSTRUCT WHERE {
67 | skos:narrower ?narrower .
68 | ?narrower skos:prefLabel ?label .
69 | }`
70 |
71 | // when
72 | const actual = CONSTRUCT.WHERE`
73 | ${root} ${skos.narrower} ?narrower .
74 | ?narrower ${skos.prefLabel} ?label .
75 | `.build()
76 |
77 | // then
78 | expect(actual).to.be.query(expected)
79 | })
80 |
81 | it('support FROM in shorthand syntax', () => {
82 | // given
83 | const expected = `PREFIX schema:
84 | CONSTRUCT FROM WHERE { ?person a schema:Person }`
85 |
86 | // when
87 | const actual = CONSTRUCT.WHERE`?person a ${schema.Person}`
88 | .FROM(RDF.namedNode('http://example.com/graph')).build()
89 |
90 | // then
91 | expect(actual).to.be.query(expected)
92 | })
93 |
94 | it('supports LIMIT/OFFSET', () => {
95 | // given
96 | const expected = `PREFIX schema:
97 | CONSTRUCT { a schema:Person } WHERE {}
98 | LIMIT 5 OFFSET 305`
99 |
100 | // when
101 | const actual = CONSTRUCT` a ${schema.Person}`.LIMIT(5).OFFSET(305).build()
102 |
103 | // then
104 | expect(actual).to.be.query(expected)
105 | })
106 |
107 | it('can be constructed with a base', () => {
108 | // given
109 | const ns = RDF.namespace('http://example.com/')
110 | const expected = `BASE
111 |
112 | CONSTRUCT {
113 | a
114 | } WHERE {}`
115 |
116 | // when
117 | const actual = CONSTRUCT`${ns.person} a ${ns.Person}`.build({
118 | base: 'http://example.com/',
119 | })
120 |
121 | // then
122 | expect(actual).to.be.query(expected)
123 | })
124 |
125 | it('can be combined with another query to create a subquery', () => {
126 | // given
127 | const person = RDF.variable('person')
128 | const selectPeopleBornInBerlin = SELECT`${person}`
129 | .WHERE`${person} ${dbo.birthPlace} `
130 | .LIMIT(100)
131 | const expected = `PREFIX dbo:
132 | PREFIX foaf:
133 |
134 | CONSTRUCT { ?person ?p ?o }
135 | WHERE {
136 |
137 | VALUES ?p { dbo:birthDate dbo:deathDate foaf:name }
138 | ?person ?p ?o .
139 |
140 | {
141 | SELECT ?person
142 |
143 | WHERE {
144 | ?person dbo:birthPlace
145 | }
146 | LIMIT 100
147 | }
148 | }`
149 |
150 | // when
151 | const construct = CONSTRUCT`${person} ?p ?o`
152 | .WHERE`
153 | VALUES ?p { ${dbo.birthDate} ${dbo.deathDate} ${foaf.name} }
154 | ${person} ?p ?o .
155 |
156 | ${selectPeopleBornInBerlin}
157 | `.build()
158 |
159 | // then
160 | expect(construct).to.be.query(expected)
161 | })
162 |
163 | it('adds FROM when default graph set', () => {
164 | // given
165 | const expected = 'CONSTRUCT { ?s ?p ?o } FROM WHERE { ?s ?p ?o }'
166 |
167 | // when
168 | const actual = CONSTRUCT`?s ?p ?o`.FROM(RDF.namedNode('urn:foo:bar')).WHERE`?s ?p ?o`.build()
169 |
170 | // then
171 | expect(actual).to.be.query(expected)
172 | })
173 |
174 | it('supports multiple FROM NAMED', () => {
175 | // given
176 | const expected = `CONSTRUCT { ?s ?p ?o }
177 | FROM NAMED
178 | FROM NAMED
179 | WHERE { ?s ?p ?o }`
180 |
181 | // when
182 | const actual = CONSTRUCT`?s ?p ?o`
183 | .FROM().NAMED(RDF.namedNode('urn:foo:bar'))
184 | .FROM().NAMED(RDF.namedNode('urn:foo:baz'))
185 | .WHERE`?s ?p ?o`.build()
186 |
187 | // then
188 | expect(actual).to.be.query(expected)
189 | })
190 |
191 | it('supports multiple FROM', () => {
192 | // given
193 | const expected = `CONSTRUCT { ?s ?p ?o }
194 | FROM
195 | FROM
196 | WHERE { ?s ?p ?o }`
197 |
198 | // when
199 | const actual = CONSTRUCT`?s ?p ?o`
200 | .FROM(RDF.namedNode('urn:foo:bar'))
201 | .FROM(RDF.namedNode('urn:foo:baz'))
202 | .WHERE`?s ?p ?o`.build()
203 |
204 | // then
205 | expect(actual).to.be.query(expected)
206 | })
207 |
208 | it('allows mixing FROM and FROM NAMED', () => {
209 | // given
210 | const expected = `CONSTRUCT { ?s ?p ?o }
211 | FROM
212 | FROM NAMED
213 | WHERE { ?s ?p ?o }`
214 |
215 | // when
216 | const actual = CONSTRUCT`?s ?p ?o`
217 | .FROM(RDF.namedNode('urn:foo:bar'))
218 | .FROM().NAMED(RDF.namedNode('urn:foo:baz'))
219 | .WHERE`?s ?p ?o`.build()
220 |
221 | // then
222 | expect(actual).to.be.query(expected)
223 | })
224 | })
225 |
--------------------------------------------------------------------------------
/test/DELETE.test.ts:
--------------------------------------------------------------------------------
1 | import RDF from '@zazuko/env'
2 | import chai, { expect } from 'chai'
3 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'
4 | import { DELETE } from '../src/index.js'
5 | import { sparqlClient } from './_mocks.js'
6 | import './sparql.js'
7 |
8 | const { foaf, owl, schema } = RDF.ns
9 |
10 | describe('DELETE', () => {
11 | chai.use(jestSnapshotPlugin())
12 |
13 | it('adds an empty WHERE if no pattern provided', () => {
14 | // given
15 | const expected = `DELETE {
16 | ?s ?p ?o .
17 | } WHERE {}`
18 |
19 | // when
20 | const query = DELETE`?s ?p ?o .`.build()
21 |
22 | // then
23 | expect(query).to.be.query(expected)
24 | })
25 |
26 | it('can have additional prologue', function () {
27 | // given
28 | const base = RDF.namedNode('http://foo.bar/baz')
29 |
30 | // when
31 | const query = DELETE` ${owl.sameAs} .`
32 | .prologue`#pragma join.hash off`
33 | .prologue`BASE ${base}`
34 | .build()
35 |
36 | // then
37 | expect(query).toMatchSnapshot()
38 | })
39 |
40 | it('combines multiple DELETE calls', () => {
41 | // given
42 | const expected = `PREFIX owl:
43 |
44 | DELETE {
45 | owl:sameAs .
46 | owl:sameAs .
47 | } WHERE {}`
48 |
49 | // when
50 | const query = DELETE` ${owl.sameAs} .`
51 | .DELETE` ${owl.sameAs} .`
52 | .build()
53 |
54 | // then
55 | expect(query).to.be.query(expected)
56 | })
57 |
58 | it('skips empty INSERT clause', () => {
59 | // when
60 | const query = DELETE` ${owl.sameAs} .`
61 | .build()
62 |
63 | // then
64 | expect(query).not.to.include('INSERT')
65 | })
66 |
67 | it('has a WHERE method', () => {
68 | // given
69 | const expected = `PREFIX schema:
70 |
71 | DELETE {
72 | ?s ?p ?o .
73 | } WHERE {
74 | ?s a schema:Person ; ?p ?o
75 | }`
76 |
77 | // when
78 | const query = DELETE`?s ?p ?o .`
79 | .WHERE`?s a ${schema.Person} ; ?p ?o`
80 | .build()
81 |
82 | // then
83 | expect(query).to.be.query(expected)
84 | })
85 |
86 | it('complete DELETE/INSERT/WHERE', () => {
87 | // given
88 | const expected = `
89 | PREFIX foaf:
90 | PREFIX schema:
91 |
92 | DELETE {
93 | ?s a foaf:Person .
94 | } INSERT {
95 | ?s a schema:Person .
96 | } WHERE {
97 | ?s a foaf:Person .
98 | }`
99 |
100 | // when
101 | const query = DELETE`?s a ${foaf.Person}`
102 | .INSERT`?s a ${schema.Person}`
103 | .WHERE`?s a ${foaf.Person}`
104 | .build()
105 |
106 | // then
107 | expect(query).to.be.query(expected)
108 | })
109 |
110 | it('executes as update', async () => {
111 | // given
112 | const client = sparqlClient()
113 |
114 | // when
115 | await DELETE``.execute(client)
116 |
117 | // then
118 | expect(client.query.update).to.have.been.called
119 | })
120 | })
121 |
--------------------------------------------------------------------------------
/test/DELETE_DATA.test.ts:
--------------------------------------------------------------------------------
1 | import RDF from '@zazuko/env'
2 | import chai, { expect } from 'chai'
3 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'
4 | import { DELETE } from '../src/index.js'
5 | import { sparqlClient } from './_mocks.js'
6 | import './sparql.js'
7 |
8 | describe('DELETE DATA', () => {
9 | chai.use(jestSnapshotPlugin())
10 |
11 | it('builds correct query', () => {
12 | // given
13 | const expected = `PREFIX owl:
14 | DELETE DATA { owl:sameAs . }`
15 |
16 | // when
17 | const actual = DELETE.DATA` ${RDF.ns.owl.sameAs} `.build()
18 |
19 | // then
20 | expect(actual).to.be.query(expected)
21 | })
22 |
23 | it('executes as update', async () => {
24 | // given
25 | const client = sparqlClient()
26 |
27 | // when
28 | await DELETE.DATA` owl:sameAs `.execute(client)
29 |
30 | // then
31 | expect(client.query.update).to.have.been.called
32 | })
33 |
34 | it('can have additional prologue', function () {
35 | // given
36 | const base = RDF.namedNode('http://foo.bar/baz')
37 |
38 | // when
39 | const query = DELETE.DATA` ${RDF.ns.owl.sameAs} .`
40 | .prologue`#pragma join.hash off`
41 | .prologue`BASE ${base}`
42 | .build()
43 |
44 | // then
45 | expect(query).toMatchSnapshot()
46 | })
47 |
48 | it('can delete triples', function () {
49 | // given
50 | const data = RDF.quad(
51 | RDF.namedNode('http://example.com/bar'),
52 | RDF.ns.owl.sameAs,
53 | RDF.namedNode('http://example.com/bar'),
54 | )
55 |
56 | // when
57 | const query = DELETE.DATA`${data}`.build()
58 |
59 | // then
60 | expect(query).toMatchSnapshot()
61 | })
62 |
63 | it('can delete quads', function () {
64 | // given
65 | const data = RDF.quad(
66 | RDF.namedNode('http://example.com/bar'),
67 | RDF.ns.owl.sameAs,
68 | RDF.namedNode('http://example.com/bar'),
69 | RDF.namedNode('http://example.com/G'),
70 | )
71 |
72 | // when
73 | const query = DELETE.DATA`${data}`.build()
74 |
75 | // then
76 | expect(query).toMatchSnapshot()
77 | })
78 |
79 | it('can delete dataset', function () {
80 | // given
81 | const data = RDF.dataset([RDF.quad(
82 | RDF.namedNode('http://example.com/bar'),
83 | RDF.ns.owl.sameAs,
84 | RDF.namedNode('http://example.com/bar'),
85 | )])
86 |
87 | // when
88 | const query = DELETE.DATA`${data}`.build()
89 |
90 | // then
91 | expect(query).toMatchSnapshot()
92 | })
93 |
94 | it('can chain multiple quad data calls', () => {
95 | // given
96 | const expected = `PREFIX owl:
97 | DELETE DATA {
98 | owl:sameAs .
99 | owl:sameAs .
100 | }`
101 |
102 | // when
103 | const actual = DELETE
104 | .DATA` ${RDF.ns.owl.sameAs} .`
105 | .DATA` ${RDF.ns.owl.sameAs} .`
106 | .build()
107 |
108 | // then
109 | expect(actual).to.be.query(expected)
110 | })
111 | })
112 |
--------------------------------------------------------------------------------
/test/DESCRIBE.test.ts:
--------------------------------------------------------------------------------
1 | import RDF from '@zazuko/env'
2 | import chai, { expect } from 'chai'
3 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'
4 | import { DESCRIBE } from '../src/index.js'
5 | import { sparqlClient } from './_mocks.js'
6 | import './sparql.js'
7 |
8 | describe('DESCRIBE', () => {
9 | chai.use(jestSnapshotPlugin())
10 |
11 | it('executes a construct', async () => {
12 | // given
13 | const client = sparqlClient()
14 |
15 | // when
16 | await DESCRIBE``.execute(client)
17 |
18 | // then
19 | expect(client.query.construct).to.have.been.called
20 | })
21 |
22 | it('builds a DESCRIBE without WHERE', () => {
23 | // given
24 | const expected = 'DESCRIBE '
25 |
26 | // when
27 | const actual = DESCRIBE`${RDF.namedNode('urn:foo:bar')}`.build()
28 |
29 | // then
30 | expect(actual).to.be.query(expected)
31 | })
32 |
33 | it('can have additional prologue', function () {
34 | // given
35 | const base = RDF.namedNode('http://foo.bar/baz')
36 |
37 | // when
38 | const query = DESCRIBE`?foo`
39 | .prologue`#pragma join.hash off`
40 | .prologue`BASE ${base}`
41 | .WHERE`?foo a ?bar`
42 | .build()
43 |
44 | // then
45 | expect(query).toMatchSnapshot()
46 | })
47 |
48 | it('supports LIMIT/OFFSET', () => {
49 | // given
50 | const expected = 'DESCRIBE ?foo LIMIT 100 OFFSET 200'
51 |
52 | // when
53 | const actual = DESCRIBE`${RDF.variable('foo')}`.LIMIT(100).OFFSET(200).build()
54 |
55 | // then
56 | expect(actual).to.be.query(expected)
57 | })
58 |
59 | it('supports ORDER BY', () => {
60 | // given
61 | const expected = 'DESCRIBE ?foo WHERE { ?foo a ?bar } ORDER BY ?bar LIMIT 100 OFFSET 200'
62 | const foo = RDF.variable('foo')
63 | const bar = RDF.variable('bar')
64 |
65 | // when
66 | const actual = DESCRIBE`${foo}`
67 | .WHERE`${foo} a ${bar}`
68 | .ORDER().BY(bar)
69 | .LIMIT(100)
70 | .OFFSET(200)
71 | .build()
72 |
73 | // then
74 | expect(actual).to.be.query(expected)
75 | })
76 |
77 | it('supports FROM (NAMED)', () => {
78 | // given
79 | const expected = `DESCRIBE ?foo
80 | FROM
81 | FROM NAMED `
82 |
83 | // when
84 | const actual = DESCRIBE`${RDF.variable('foo')}`
85 | .FROM(RDF.namedNode('http://example.com/foo'))
86 | .FROM().NAMED(RDF.namedNode('http://example.com/bar')).build()
87 |
88 | // then
89 | expect(actual).to.be.query(expected)
90 | })
91 |
92 | it('can be constructed with a base', () => {
93 | // given
94 | const ns = RDF.namespace('http://example.com/')
95 | const expected = `BASE
96 |
97 | DESCRIBE `
98 |
99 | // when
100 | const actual = DESCRIBE`${ns.person}`.build({
101 | base: 'http://example.com/',
102 | })
103 |
104 | // then
105 | expect(actual).to.be.query(expected)
106 | })
107 | })
108 |
--------------------------------------------------------------------------------
/test/INSERT.test.ts:
--------------------------------------------------------------------------------
1 | import RDF from '@zazuko/env'
2 | import chai, { expect } from 'chai'
3 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'
4 | import { INSERT } from '../src/index.js'
5 | import { sparqlClient } from './_mocks.js'
6 | import './sparql.js'
7 |
8 | const { owl, schema } = RDF.ns
9 |
10 | describe('INSERT', () => {
11 | chai.use(jestSnapshotPlugin())
12 |
13 | it('adds an empty WHERE if no pattern provided', () => {
14 | // given
15 | const expected = `INSERT {
16 | ?s ?p ?o .
17 | } WHERE {}`
18 |
19 | // when
20 | const query = INSERT`?s ?p ?o .`.build()
21 |
22 | // then
23 | expect(query).to.be.query(expected)
24 | })
25 |
26 | it('combines multiple INSERT calls', () => {
27 | // given
28 | const expected = `PREFIX owl:
29 |
30 | INSERT {
31 | owl:sameAs .
32 | owl:sameAs .
33 | } WHERE {}`
34 |
35 | // when
36 | const query = INSERT` ${owl.sameAs} .`
37 | .INSERT` ${owl.sameAs} .`
38 | .build()
39 |
40 | // then
41 | expect(query).to.be.query(expected)
42 | })
43 |
44 | it('can have additional prologue', function () {
45 | // given
46 | const base = RDF.namedNode('http://foo.bar/baz')
47 |
48 | // when
49 | const query = INSERT` ${owl.sameAs} .`
50 | .prologue`#pragma join.hash off`
51 | .prologue`BASE ${base}`
52 | .build()
53 |
54 | // then
55 | expect(query).toMatchSnapshot()
56 | })
57 |
58 | it('has a WHERE method', () => {
59 | // given
60 | const expected = `PREFIX schema:
61 |
62 | INSERT {
63 | ?s ?p ?o .
64 | } WHERE {
65 | ?s a schema:Person ; ?p ?o
66 | }`
67 |
68 | // when
69 | const query = INSERT`?s ?p ?o .`
70 | .WHERE`?s a ${schema.Person} ; ?p ?o`
71 | .build()
72 |
73 | // then
74 | expect(query).to.be.query(expected)
75 | })
76 |
77 | it('executes as update', async () => {
78 | // given
79 | const client = sparqlClient()
80 |
81 | // when
82 | await INSERT``.execute(client)
83 |
84 | // then
85 | expect(client.query.update).to.have.been.called
86 | })
87 | })
88 |
--------------------------------------------------------------------------------
/test/INSERT_DATA.test.ts:
--------------------------------------------------------------------------------
1 | import RDF from '@zazuko/env'
2 | import chai, { expect } from 'chai'
3 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'
4 | import { INSERT } from '../src/index.js'
5 | import { sparqlClient } from './_mocks.js'
6 | import './sparql.js'
7 |
8 | describe('INSERT DATA', () => {
9 | chai.use(jestSnapshotPlugin())
10 |
11 | it('builds correct query', () => {
12 | // given
13 | const expected = `PREFIX owl:
14 | INSERT DATA { owl:sameAs . }`
15 |
16 | // when
17 | const actual = INSERT.DATA` ${RDF.ns.owl.sameAs} `.build()
18 |
19 | // then
20 | expect(actual).to.be.query(expected)
21 | })
22 |
23 | it('executes as update', async () => {
24 | // given
25 | const client = sparqlClient()
26 |
27 | // when
28 | await INSERT.DATA` owl:sameAs `.execute(client)
29 |
30 | // then
31 | expect(client.query.update).to.have.been.called
32 | })
33 |
34 | it('can have additional prologue', function () {
35 | // given
36 | const base = RDF.namedNode('http://foo.bar/baz')
37 |
38 | // when
39 | const query = INSERT.DATA` ${RDF.ns.owl.sameAs} .`
40 | .prologue`#pragma join.hash off`
41 | .prologue`BASE ${base}`
42 | .build()
43 |
44 | // then
45 | expect(query).toMatchSnapshot()
46 | })
47 |
48 | it('can chain multiple quad data calls', () => {
49 | // given
50 | const expected = `PREFIX owl:
51 | INSERT DATA {
52 | owl:sameAs .
53 | owl:sameAs .
54 | }`
55 |
56 | // when
57 | const actual = INSERT
58 | .DATA` ${RDF.ns.owl.sameAs} .`
59 | .DATA` ${RDF.ns.owl.sameAs} .`
60 | .build()
61 |
62 | // then
63 | expect(actual).to.be.query(expected)
64 | })
65 | })
66 |
--------------------------------------------------------------------------------
/test/SELECT.test.ts:
--------------------------------------------------------------------------------
1 | import RDF from '@zazuko/env'
2 | import chai, { expect } from 'chai'
3 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'
4 | import { SELECT } from '../src/index.js'
5 | import { sparqlClient } from './_mocks.js'
6 | import './sparql.js'
7 |
8 | describe('SELECT', () => {
9 | chai.use(jestSnapshotPlugin())
10 |
11 | it('executes as select', () => {
12 | // given
13 | const client = sparqlClient()
14 |
15 | // when
16 | SELECT``.execute(client)
17 |
18 | // then
19 | expect(client.query.select).to.have.been.called
20 | })
21 |
22 | it('creates a simple select/where', () => {
23 | // given
24 | const expected = 'SELECT ?s ?p ?o WHERE { ?s ?p ?o }'
25 |
26 | // when
27 | const actual = SELECT`?s ?p ?o`.WHERE`?s ?p ?o`.build()
28 |
29 | // then
30 | expect(actual).to.be.query(expected)
31 | })
32 |
33 | it('can have additional prologue', function () {
34 | // given
35 | const base = RDF.namedNode('http://foo.bar/baz')
36 |
37 | // when
38 | const actual = SELECT`?s ?p ?o`
39 | .WHERE`?s ?p ?o`
40 | .prologue`#pragma join.hash off`
41 | .prologue`BASE ${base}`
42 | .build()
43 |
44 | // then
45 | expect(actual).toMatchSnapshot()
46 | })
47 |
48 | it('combines multiple WHERE clauses', () => {
49 | // given
50 | const expected = 'SELECT * WHERE { ?s ?p ?o. ?a ?b ?c }'
51 |
52 | // when
53 | const actual = SELECT`*`
54 | .WHERE`?s ?p ?o .`
55 | .WHERE`?a ?b ?c .`
56 | .build()
57 |
58 | // then
59 | expect(actual).to.be.query(expected)
60 | })
61 |
62 | it('adds FROM when default graph set', () => {
63 | // given
64 | const expected = 'SELECT * FROM WHERE { ?s ?p ?o }'
65 |
66 | // when
67 | const actual = SELECT`*`.FROM(RDF.namedNode('urn:foo:bar')).WHERE`?s ?p ?o`.build()
68 |
69 | // then
70 | expect(actual).to.be.query(expected)
71 | })
72 |
73 | it('supports multiple FROM', () => {
74 | // given
75 | const expected = `SELECT *
76 | FROM
77 | FROM
78 | WHERE { ?s ?p ?o }`
79 |
80 | // when
81 | const actual = SELECT`*`
82 | .FROM(RDF.namedNode('urn:foo:bar'))
83 | .FROM(RDF.namedNode('urn:foo:baz'))
84 | .WHERE`?s ?p ?o`.build()
85 |
86 | // then
87 | expect(actual).to.be.query(expected)
88 | })
89 |
90 | it('allows mixing FROM and FROM NAMED', () => {
91 | // given
92 | const expected = `SELECT *
93 | FROM
94 | FROM NAMED
95 | WHERE { ?s ?p ?o }`
96 |
97 | // when
98 | const actual = SELECT`*`
99 | .FROM(RDF.namedNode('urn:foo:bar'))
100 | .FROM().NAMED(RDF.namedNode('urn:foo:baz'))
101 | .WHERE`?s ?p ?o`.build()
102 |
103 | // then
104 | expect(actual).to.be.query(expected)
105 | })
106 |
107 | it('does not add FROM when graph is defaultGraph', () => {
108 | // given
109 | const expected = 'SELECT * WHERE { ?s ?p ?o }'
110 |
111 | // when
112 | const actual = SELECT`*`.FROM(RDF.defaultGraph()).WHERE`?s ?p ?o`.build()
113 |
114 | // then
115 | expect(actual).to.be.query(expected)
116 | })
117 |
118 | it('resets default graph when FROM default is called', () => {
119 | // given
120 | const expected = 'SELECT * WHERE { ?s ?p ?o }'
121 |
122 | // when
123 | const actual = SELECT`*`
124 | .FROM(RDF.namedNode('urn:foo:bar'))
125 | .FROM(RDF.defaultGraph())
126 | .WHERE`?s ?p ?o`.build()
127 |
128 | // then
129 | expect(actual).to.be.query(expected)
130 | })
131 |
132 | it('supports LIMIT/OFFSET', () => {
133 | // given
134 | const expected = 'SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 15 OFFSET 40'
135 |
136 | // when
137 | const actual = SELECT`?s ?p ?o`.WHERE`?s ?p ?o`.LIMIT(15).OFFSET(40).build()
138 |
139 | // then
140 | expect(actual).to.be.query(expected)
141 | })
142 |
143 | it('can be constructed with a base', () => {
144 | // given
145 | const ns = RDF.namespace('http://example.com/')
146 | const expected = `
147 | BASE
148 |
149 | SELECT *
150 | FROM
151 | WHERE {
152 | a
153 | }`
154 |
155 | // when
156 | const actual = SELECT`*`
157 | .FROM(ns.graph)
158 | .WHERE`${ns.person} a ${ns.Person}`
159 | .build({
160 | base: 'http://example.com/',
161 | })
162 |
163 | // then
164 | expect(actual).to.be.query(expected)
165 | })
166 |
167 | it('can be ordered by variable', () => {
168 | // given
169 | const expected = 'SELECT ?s ?p ?o WHERE { ?s ?p ?o } ORDER BY ?s'
170 |
171 | // when
172 | const s = RDF.variable('s')
173 | const actual = SELECT`${s} ?p ?o`
174 | .WHERE`${s} ?p ?o`
175 | .ORDER().BY(s)
176 | .build()
177 |
178 | // then
179 | expect(actual).to.be.query(expected)
180 | })
181 |
182 | it('can be ordered by desc(?variable)', () => {
183 | // given
184 | const expected = 'SELECT ?s ?p ?o WHERE { ?s ?p ?o } ORDER BY desc(?s)'
185 |
186 | // when
187 | const s = RDF.variable('s')
188 | const actual = SELECT`${s} ?p ?o`
189 | .WHERE`${s} ?p ?o`
190 | .ORDER().BY(s, true)
191 | .build()
192 |
193 | // then
194 | expect(actual).to.be.query(expected)
195 | })
196 |
197 | it('can be ordered by multiple variables', () => {
198 | // given
199 | const expected = 'SELECT ?s ?p ?o WHERE { ?s ?p ?o } ORDER BY desc(?s) ?o'
200 |
201 | // when
202 | const s = RDF.variable('s')
203 | const o = RDF.variable('o')
204 | const actual = SELECT`${s} ?p ${o}`
205 | .WHERE`${s} ?p ${o}`
206 | .ORDER().BY(s, true).THEN.BY(o)
207 | .build()
208 |
209 | // then
210 | expect(actual).to.be.query(expected)
211 | })
212 |
213 | it('can be ordered and limited, when calls are reversed', () => {
214 | // given
215 | const expected = 'SELECT ?s ?p ?o WHERE { ?s ?p ?o } ORDER BY ?s LIMIT 20'
216 |
217 | // when
218 | const s = RDF.variable('s')
219 | const actual = SELECT`${s} ?p ?o`
220 | .WHERE`${s} ?p ?o`
221 | .LIMIT(20)
222 | .ORDER().BY(s)
223 | .build()
224 |
225 | // then
226 | expect(actual).to.be.query(expected)
227 | })
228 |
229 | it('can be made distinct at any time', () => {
230 | // given
231 | const expected = 'SELECT DISTINCT ?s WHERE { ?s ?p ?o }'
232 |
233 | // when
234 | const actual = SELECT`?s`.WHERE`?s ?p ?o`.DISTINCT().build()
235 |
236 | // then
237 | expect(actual).to.be.query(expected)
238 | })
239 |
240 | it('can add more variables', () => {
241 | // given
242 | const expected = 'SELECT ?s ?p ?o WHERE { ?s ?p ?o }'
243 |
244 | // when
245 | const actual = SELECT`?s`.AND`?p`.AND`?o`.WHERE`?s ?p ?o`.build()
246 |
247 | // then
248 | expect(actual).to.be.query(expected)
249 | })
250 |
251 | it('wraps when interpolated in a template', () => {
252 | // given
253 | const expected = 'SELECT ?s ?p ?o WHERE { { SELECT ?s ?p ?o WHERE { ?s ?p ?o } } }'
254 |
255 | // when
256 | const subquery = SELECT`?s ?p ?o`.WHERE`?s ?p ?o`
257 | const actual = SELECT`?s ?p ?o`.WHERE`${subquery}`.build()
258 |
259 | // then
260 | expect(actual).to.be.query(expected)
261 | })
262 |
263 | describe('ALL', () => {
264 | it('is alias for SELECT *', () => {
265 | // given
266 | const expected = 'SELECT * WHERE { ?s ?p ?o } '
267 |
268 | // when
269 | const actual = SELECT.ALL
270 | .WHERE`?s ?p ?o`
271 | .build()
272 |
273 | // then
274 | expect(actual).to.be.query(expected)
275 | })
276 | })
277 |
278 | describe('DISTINCT', () => {
279 | it('creates correct SPARQL', () => {
280 | // given
281 | const expected = 'SELECT DISTINCT * WHERE { ?s ?p ?o }'
282 |
283 | // when
284 | const actual = SELECT.DISTINCT`*`.WHERE`?s ?p ?o`.build()
285 |
286 | // then
287 | expect(actual).to.be.query(expected)
288 | })
289 | })
290 |
291 | describe('REDUCED', () => {
292 | it('creates correct SPARQL', () => {
293 | // given
294 | const expected = 'SELECT REDUCED * WHERE { ?s ?p ?o }'
295 |
296 | // when
297 | const actual = SELECT.REDUCED`*`.WHERE`?s ?p ?o`.build()
298 |
299 | // then
300 | expect(actual).to.be.query(expected)
301 | })
302 | })
303 |
304 | describe('GROUP BY', () => {
305 | it('can be grouped by variable', () => {
306 | // given
307 | const expected = 'SELECT (SUM(?s) as ?sum) WHERE { ?s ?p ?o } GROUP BY (?s)'
308 |
309 | // when
310 | const s = RDF.variable('s')
311 | const actual = SELECT`(SUM(${s}) as ?sum)`
312 | .WHERE`${s} ?p ?o`
313 | .GROUP().BY(s)
314 | .build()
315 |
316 | // then
317 | expect(actual).to.be.query(expected)
318 | })
319 |
320 | it('can be grouped by variable name (string)', () => {
321 | // given
322 | const expected = 'SELECT (SUM(?s) as ?sum) WHERE { ?s ?p ?o } GROUP BY (?s)'
323 |
324 | // when
325 | const s = RDF.variable('s')
326 | const actual = SELECT`(SUM(${s}) as ?sum)`
327 | .WHERE`${s} ?p ?o`
328 | .GROUP().BY('s')
329 | .build()
330 |
331 | // then
332 | expect(actual).to.be.query(expected)
333 | })
334 |
335 | it('can be grouped by variable with binding keyword as variable name', () => {
336 | // given
337 | const expected = 'SELECT (SUM(?x) as ?sum) WHERE { ?s ?p ?o } GROUP BY (?s as ?x)'
338 |
339 | // when
340 | const s = RDF.variable('s')
341 | const actual = SELECT`(SUM(?x) as ?sum)`
342 | .WHERE`${s} ?p ?o`
343 | .GROUP().BY(s).AS('x')
344 | .build()
345 |
346 | // then
347 | expect(actual).to.be.query(expected)
348 | })
349 |
350 | it('can be grouped by expression with binding keyword as variable', () => {
351 | // given
352 | const expected = 'SELECT (SUM(?x) as ?sum) WHERE { ?s ?p ?o } GROUP BY (?s + ?p as ?x)'
353 |
354 | // when
355 | const s = RDF.variable('s')
356 | const x = RDF.variable('x')
357 | const actual = SELECT`(SUM(${x}) as ?sum)`
358 | .WHERE`${s} ?p ?o`
359 | .GROUP().BY`${s} + ?p`.AS(x)
360 | .build()
361 |
362 | // then
363 | expect(actual).to.be.query(expected)
364 | })
365 |
366 | it('can be grouped multiple times', () => {
367 | // given
368 | const expected = 'SELECT (SUM(?s) as ?sum) WHERE { ?s ?p ?o } GROUP BY (?s) (?x as ?z)'
369 |
370 | // when
371 | const s = RDF.variable('s')
372 | const x = RDF.variable('x')
373 | const actual = SELECT`(SUM(${s}) as ?sum)`
374 | .WHERE`${s} ?p ?o`
375 | .GROUP().BY(s).THEN.BY(x).AS('z')
376 | .build()
377 |
378 | // then
379 | expect(actual).to.be.query(expected)
380 | })
381 | })
382 |
383 | describe('HAVING', () => {
384 | it('can build multiple HAVING clauses', () => {
385 | // given
386 | const expected = 'SELECT * WHERE { ?s ?p ?o } HAVING (AVG(?s) > 10) (SUM(?p) = 0)'
387 |
388 | // when
389 | const s = RDF.variable('s')
390 | const actual = SELECT.ALL
391 | .WHERE`?s ?p ?o`
392 | .HAVING`AVG(${s}) > 10`
393 | .HAVING`SUM(?p) = 0`
394 | .build()
395 |
396 | // then
397 | expect(actual).to.be.query(expected)
398 | })
399 | })
400 | })
401 |
--------------------------------------------------------------------------------
/test/WITH.test.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai'
2 | import RDF from '@zazuko/env'
3 | import { DELETE, INSERT, WITH } from '../src/index.js'
4 | import './sparql.js'
5 |
6 | describe('WITH', () => {
7 | it('prepends WITH clause given as string', () => {
8 | // given
9 | const expected = 'WITH DELETE { ?s ?p ?o } WHERE { ?s ?p ?o }'
10 |
11 | // when
12 | const actual = WITH('http://test.graph/', DELETE`?s ?p ?o`.WHERE`?s ?p ?o`).build()
13 |
14 | // then
15 | expect(actual).to.be.query(expected)
16 | })
17 |
18 | it('prepends WITH clause given as named node', () => {
19 | // given
20 | const expected = 'WITH INSERT { ?s ?p ?o } WHERE { ?s ?p ?o }'
21 |
22 | // when
23 | const actual = WITH(RDF.namedNode('http://test.graph/'), INSERT`?s ?p ?o`.WHERE`?s ?p ?o`).build()
24 |
25 | // then
26 | expect(actual).to.be.query(expected)
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/test/__snapshots__/ASK.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ASK can have additional prologue 1`] = `
4 | "#pragma join.hash off
5 | BASE
6 |
7 | ASK { ?s ?p ?o . }
8 | "
9 | `;
10 |
--------------------------------------------------------------------------------
/test/__snapshots__/CONSTRUCT.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`CONSTRUCT can have additional prologue 1`] = `
4 | "#pragma join.hash off
5 | BASE
6 |
7 | CONSTRUCT { ?s ?p ?o . }
8 |
9 | WHERE {}
10 | "
11 | `;
12 |
--------------------------------------------------------------------------------
/test/__snapshots__/DELETE.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`DELETE can have additional prologue 1`] = `
4 | "#pragma join.hash off
5 | BASE
6 |
7 | PREFIX owl:
8 |
9 | DELETE { owl:sameAs . } WHERE {}"
10 | `;
11 |
--------------------------------------------------------------------------------
/test/__snapshots__/DELETE_DATA.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`DELETE DATA can delete dataset 1`] = `
4 | "PREFIX owl:
5 |
6 | DELETE DATA {
7 |
8 |
9 | owl:sameAs .
10 | }"
11 | `;
12 |
13 | exports[`DELETE DATA can delete quads 1`] = `
14 | "PREFIX owl:
15 |
16 | DELETE DATA {
17 | GRAPH { owl:sameAs . }
18 | }"
19 | `;
20 |
21 | exports[`DELETE DATA can delete triples 1`] = `
22 | "PREFIX owl:
23 |
24 | DELETE DATA {
25 | owl:sameAs .
26 | }"
27 | `;
28 |
29 | exports[`DELETE DATA can have additional prologue 1`] = `
30 | "#pragma join.hash off
31 | BASE
32 |
33 | PREFIX owl:
34 |
35 | DELETE DATA {
36 | owl:sameAs .
37 | }"
38 | `;
39 |
--------------------------------------------------------------------------------
/test/__snapshots__/DESCRIBE.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`DESCRIBE can have additional prologue 1`] = `
4 | "#pragma join.hash off
5 | BASE
6 |
7 | DESCRIBE ?foo
8 |
9 | WHERE {
10 | ?foo a ?bar
11 | }
12 |
13 | "
14 | `;
15 |
--------------------------------------------------------------------------------
/test/__snapshots__/INSERT.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`INSERT can have additional prologue 1`] = `
4 | "#pragma join.hash off
5 | BASE
6 |
7 | PREFIX owl:
8 |
9 | INSERT{
10 | owl:sameAs .
11 | } WHERE {}"
12 | `;
13 |
--------------------------------------------------------------------------------
/test/__snapshots__/INSERT_DATA.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`INSERT DATA can have additional prologue 1`] = `
4 | "#pragma join.hash off
5 | BASE
6 |
7 | PREFIX owl:
8 |
9 | INSERT DATA {
10 |