Skip to content

Commit 7a06792

Browse files
authored
Updates to dependencies and GitHub actions, and code clean-up
* Move from TSLint to ESLint, use yarn@4.1.1, bump dependencies * Fixes to linting errors in code, typing, and error handling * Update unit tests * Revert result type changes * Revert to yarn@1.22.22 for Node 16 * Revert customFetch to fetchCb to avoid major version * Remove additional helper type for fetch * Add timeout test for fetchRawStream * Remove unnecessary .yarnrc.yml * Fix eslint version in package, add missing newline * Add @types/n3 to dependencies since they end up in index.d.ts * Use version 3 of @rubensworks/eslint-config * Remove rule overrides * Add project-specific exceptions to rules * Fix linter errors * Add --ignore-engines flag due to new linter rules * Add back ReadableStream workaround for Node 16 * Move readable-stream-node-to-web import below the ReadableStream workaround * Fix AbortController timeout test on Node 16 * Fix additional URL params on Node 16 * Fix url params size check on Node 16 * Restore CLI tool to previous usage options * Fix CLI usage in test-docker.sh * Remove matrix from Docker step and rename it * Make --query optional again * Use version 2 of coveralls action instead of master branch * Add back return type annotation for getQueryType --------- Co-authored-by: surilindur <surilindur@users.noreply.github.com>
1 parent b5e33f2 commit 7a06792

15 files changed

+4310
-1966
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
with:
2323
node-version: 20.x
2424
- run: corepack enable
25-
- run: yarn install --frozen-lockfile
25+
- run: yarn install --ignore-engines --frozen-lockfile
2626
- run: yarn run lint
2727

2828
test:
@@ -47,10 +47,10 @@ jobs:
4747
path: '**/node_modules'
4848
key: ${{ runner.os }}-test-modules-${{ hashFiles('**/yarn.lock') }}
4949
- run: corepack enable
50-
- run: yarn install --frozen-lockfile
50+
- run: yarn install --ignore-engines --frozen-lockfile
5151
- run: yarn run build
5252
- run: yarn run test
53-
- uses: coverallsapp/github-action@master
53+
- uses: coverallsapp/github-action@v2
5454
with:
5555
github-token: ${{ secrets.github_token }}
5656
flag-name: run-${{ matrix.node-version }}
@@ -61,24 +61,18 @@ jobs:
6161
runs-on: ubuntu-latest
6262
steps:
6363
- name: Consolidate test coverage from different jobs
64-
uses: coverallsapp/github-action@master
64+
uses: coverallsapp/github-action@v2
6565
with:
6666
github-token: ${{ secrets.github_token }}
6767
parallel-finished: true
6868

69-
docker:
69+
cli:
7070
needs: test
71-
runs-on: ${{ matrix.os }}
72-
strategy:
73-
matrix:
74-
os:
75-
- ubuntu-latest
76-
node-version:
77-
- 20.x
71+
runs-on: ubuntu-latest
7872
steps:
7973
- uses: actions/setup-node@v4
8074
with:
81-
node-version: ${{ matrix.node-version }}
75+
node-version: 20.x
8276
- run: git config --global core.autocrlf input
8377
- uses: actions/checkout@v4
8478
- run: .github/workflows/start-docker.sh
@@ -87,7 +81,7 @@ jobs:
8781
path: '**/node_modules'
8882
key: ${{ runner.os }}-docker-modules-${{ hashFiles('**/yarn.lock') }}
8983
- run: corepack enable
90-
- run: yarn install --frozen-lockfile
84+
- run: yarn install --ignore-engines --frozen-lockfile
9185
- run: yarn build
9286
- run: .github/workflows/test-docker.sh
9387
- run: .github/workflows/stop-docker.sh
@@ -105,5 +99,5 @@ jobs:
10599
with:
106100
node-version: 20.x
107101
- run: corepack enable
108-
- run: yarn install --frozen-lockfile
102+
- run: yarn install --ignore-engines --frozen-lockfile
109103
- run: npx webpack

README.md

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,28 @@ This package also works out-of-the-box in browsers via tools such as [webpack](h
3232
#### Create a new fetcher
3333

3434
```js
35-
import {SparqlEndpointFetcher} from "fetch-sparql-endpoint";
35+
import { SparqlEndpointFetcher } from 'fetch-sparql-endpoint';
3636

3737
const myFetcher = new SparqlEndpointFetcher();
3838
```
3939

4040
Optionally, you can pass an options object with the following optional entries:
4141
```js
4242
const myFetcher = new SparqlEndpointFetcher({
43-
method: 'POST', // A custom HTTP method for issuing (non-update) queries, defaults to POST. Update queries are always issued via POST.
44-
additionalUrlParams: new URLSearchParams({'infer': 'true', 'sameAs': 'false'}); // A set of additional parameters that well be added to fetchAsk, fetchBindings & fetchTriples requests
45-
defaultHeaders: new Headers(), // Optional default headers that will be included in each request
46-
fetch: fetch, // A custom fetch-API-supporting function
47-
dataFactory: DataFactory, // A custom RDFJS data factory
48-
prefixVariableQuestionMark: false, // If variable names in bindings should be prefixed with '?', defaults to false
49-
timeout: 5000 // Timeout for setting up server connection (Once a connection has been made, and the response is being parsed, the timeout does not apply anymore).
43+
// A custom HTTP method for issuing (non-update) queries, defaults to POST. Update queries are always issued via POST.
44+
method: 'POST',
45+
// A set of additional parameters that well be added to fetchAsk, fetchBindings & fetchTriples requests
46+
additionalUrlParams: new URLSearchParams({ infer: 'true', sameAs: 'false' }),
47+
// Optional default headers that will be included in each request
48+
defaultHeaders: new Headers(),
49+
// A custom fetch-API-supporting function
50+
fetch,
51+
// A custom RDFJS data factory
52+
dataFactory: DataFactory,
53+
// If variable names in bindings should be prefixed with '?', defaults to false
54+
prefixVariableQuestionMark: false,
55+
// Timeout for setting up server connection (Once a connection has been made, and the response is being parsed, the timeout does not apply anymore).
56+
timeout: 5000,
5057
});
5158
```
5259

@@ -56,7 +63,7 @@ const myFetcher = new SparqlEndpointFetcher({
5663

5764
```js
5865
const bindingsStream = await fetcher.fetchBindings('https://dbpedia.org/sparql', 'SELECT * WHERE { ?s ?p ?o } LIMIT 100');
59-
bindingsStream.on('data', (bindings) => console.log(bindings));
66+
bindingsStream.on('data', bindings => console.log(bindings));
6067
```
6168

6269
This will output bindings in the following form,
@@ -72,9 +79,9 @@ and values and [RDFJS terms](http://rdf.js.org/#term-interface):
7279
Optionally, you can obtain a list of variables by listening to the `'variables'` event:
7380
```js
7481
const bindingsStream = await fetcher.fetchBindings('https://dbpedia.org/sparql', 'SELECT * WHERE { ?s ?p ?o } LIMIT 100');
75-
bindingsStream.on('data', (bindings) => console.log(bindings));
82+
bindingsStream.on('data', bindings => console.log(bindings));
7683
// Will print [ variable('s'), variable('p'), variable('o') ]
77-
bindingsStream.on('variables', (variables) => console.log(variables));
84+
bindingsStream.on('variables', variables => console.log(variables));
7885
```
7986

8087
### Fetch ask
@@ -94,7 +101,7 @@ queries returns a (promise to a) stream of triples.
94101

95102
```js
96103
const tripleStream = await fetcher.fetchTriples('https://dbpedia.org/sparql', 'CONSTRUCT { ?s ?p ?o } LIMIT 100');
97-
tripleStream.on('data', (triple) => console.log(triple));
104+
tripleStream.on('data', triple => console.log(triple));
98105
```
99106

100107
This will output [RDFJS triples](http://rdf.js.org/#triple-interface) as follows:
@@ -121,10 +128,13 @@ If you want to know the query type
121128
in order to determine which of the above fetch methods to call,
122129
then you can use the `getQueryType` method as follows:
123130

124-
```
125-
fetcher.getQueryType('SELECT * WHERE { ?s ?p ?o } LIMIT 100'); // Outputs 'SELECT'
126-
fetcher.getQueryType('ASK WHERE { ?s ?p ?o }'); // Outputs 'ASK'
127-
fetcher.getQueryType('CONSTRUCT { ?s ?p ?o } LIMIT 100'); // Outputs 'CONSTRUCT'
131+
```js
132+
// Outputs 'SELECT'
133+
fetcher.getQueryType('SELECT * WHERE { ?s ?p ?o } LIMIT 100');
134+
// Outputs 'ASK'
135+
fetcher.getQueryType('ASK WHERE { ?s ?p ?o }');
136+
// Outputs 'CONSTRUCT'
137+
fetcher.getQueryType('CONSTRUCT { ?s ?p ?o } LIMIT 100');
128138
```
129139

130140
This method will also throw an error if the query contains a syntax error.

bin/fetch-sparql-endpoint.ts

Lines changed: 95 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,111 @@
11
#!/usr/bin/env node
22

3-
import {readFileSync} from "fs";
4-
import minimist = require("minimist");
5-
import {quadToStringQuad, termToString} from "rdf-string";
6-
import {SparqlEndpointFetcher} from "../index";
3+
import { readFileSync } from 'node:fs';
4+
import { StreamWriter } from 'n3';
5+
import { termToString } from 'rdf-string';
6+
import * as streamToString from 'stream-to-string';
7+
import yargs from 'yargs';
8+
import { hideBin } from 'yargs/helpers';
9+
import { SparqlEndpointFetcher, type IBindings } from '..';
710

8-
// tslint:disable-next-line:no-var-requires
9-
const n3 = require('n3');
10-
11-
process.argv.splice(0, 2);
12-
const args = minimist(process.argv);
13-
if (args._.length === 0 || args._.length > 2 || args.h || args.help) {
14-
15-
// Print command usage
16-
process.stderr.write(`fetch-sparql-endpoint Sends a query to a SPARQL endpoint
17-
18-
Usage:
19-
fetch-sparql-endpoint https://dbpedia.org/sparql [-q] 'SELECT * WHERE { ?s ?p ?o } 100'
20-
fetch-sparql-endpoint https://dbpedia.org/sparql -f query.sparql
21-
cat query.sparql | fetch-sparql-endpoint https://dbpedia.org/sparql
22-
23-
Options:
24-
-q evaluate the given SPARQL query string
25-
-f evaluate the SPARQL query in the given file
26-
-g send query via HTTP GET instead of POST
27-
--help print this help message
28-
`);
29-
process.exit(1);
30-
}
31-
32-
async function getQuery(): Promise<string> {
33-
if (args._.length > 1) {
34-
return args._[1];
35-
} else if (args.q) {
36-
return args.q;
37-
} else if (args.f) {
38-
return readFileSync(args.f, { encoding: 'utf8' });
39-
} else {
40-
// tslint:disable-next-line:no-var-requires
41-
return await require('stream-to-string')(process.stdin);
11+
async function getQuery(query?: string, file?: string): Promise<string> {
12+
if (query) {
13+
return query;
4214
}
15+
if (file) {
16+
readFileSync(file, { encoding: 'utf-8' });
17+
}
18+
return streamToString(process.stdin);
4319
}
4420

45-
const endpoint = args._[0];
46-
47-
getQuery().then((query) => {
48-
const fetcher = new SparqlEndpointFetcher({
49-
method: args.g ? 'GET' : 'POST',
21+
function querySelect(endpoint: string, fetcher: SparqlEndpointFetcher, query: string): Promise<void> {
22+
return new Promise((resolve, reject) => {
23+
fetcher.fetchBindings(endpoint, query).then(bindingsStream => bindingsStream
24+
.on('data', (bindings: IBindings) => {
25+
process.stdout.write(`${JSON.stringify(Object.fromEntries(Object.entries(bindings).map(([ key, value ]) => [
26+
key,
27+
termToString(value),
28+
])))}\n`);
29+
})
30+
.on('error', reject)
31+
.on('end', resolve)).catch(reject);
5032
});
51-
const queryType = fetcher.getQueryType(query);
52-
switch (queryType) {
53-
case 'SELECT':
54-
querySelect(fetcher, query);
55-
break;
56-
case 'ASK':
57-
queryAsk(fetcher, query);
58-
break;
59-
case 'CONSTRUCT':
60-
queryConstruct(fetcher, query);
61-
break;
62-
case 'UNKNOWN':
63-
if (fetcher.getUpdateTypes(query) !== 'UNKNOWN') {
64-
update(fetcher, query);
65-
}
66-
break;
67-
}
68-
});
69-
70-
function querySelect(fetcher: SparqlEndpointFetcher, query: string) {
71-
fetcher.fetchBindings(endpoint, query)
72-
.then((bindingsStream) => {
73-
bindingsStream.on('data', (bindings) => {
74-
for (const variable of Object.keys(bindings)) {
75-
bindings[variable] = termToString(bindings[variable]);
76-
}
77-
process.stdout.write(JSON.stringify(bindings) + '\n');
78-
});
79-
bindingsStream.on('error', (error) => process.stderr.write(error.toString()));
80-
})
81-
.catch((error) => {
82-
process.stderr.write(error.message + '\n');
83-
process.exit(1);
84-
});
8533
}
8634

87-
function queryAsk(fetcher: SparqlEndpointFetcher, query: string) {
88-
fetcher.fetchAsk(endpoint, query)
89-
.then((answer) => {
90-
process.stdout.write(answer + '\n');
91-
})
92-
.catch((error) => {
93-
process.stderr.write(error.message + '\n');
94-
process.exit(1);
95-
});
35+
function queryAsk(endpoint: string, fetcher: SparqlEndpointFetcher, query: string): Promise<void> {
36+
return new Promise((resolve, reject) => {
37+
fetcher.fetchAsk(endpoint, query)
38+
.then((answer) => {
39+
process.stdout.write(`${answer}\n`);
40+
resolve();
41+
})
42+
.catch(reject);
43+
});
9644
}
9745

98-
function queryConstruct(fetcher: SparqlEndpointFetcher, query: string) {
99-
fetcher.fetchTriples(endpoint, query)
100-
.then((tripleStream) => {
101-
(<any> tripleStream)
102-
.on('error', (error: Error) => process.stderr.write(error.toString()))
103-
.pipe(new n3.StreamWriter(SparqlEndpointFetcher.CONTENTTYPE_TURTLE))
104-
.pipe(process.stdout);
105-
})
106-
.catch((error) => {
107-
process.stderr.write(error.message + '\n');
108-
process.exit(1);
109-
});
46+
function queryConstruct(endpoint: string, fetcher: SparqlEndpointFetcher, query: string): Promise<void> {
47+
return new Promise((resolve, reject) => {
48+
fetcher.fetchTriples(endpoint, query)
49+
.then(tripleStream => tripleStream
50+
.on('error', reject)
51+
.pipe(new StreamWriter(SparqlEndpointFetcher.CONTENTTYPE_TURTLE))
52+
.pipe(process.stdout)
53+
.on('end', resolve)).catch(reject);
54+
});
11055
}
11156

112-
function update(fetcher: SparqlEndpointFetcher, query: string) {
113-
fetcher.fetchUpdate(endpoint, query)
114-
.then(() => {
57+
function update(endpoint: string, fetcher: SparqlEndpointFetcher, query: string): Promise<void> {
58+
return new Promise((resolve, reject) => {
59+
fetcher.fetchUpdate(endpoint, query).then(() => {
11560
process.stdout.write('OK\n');
61+
resolve();
62+
}).catch(reject);
63+
});
64+
}
65+
66+
async function run(argv: string[]): Promise<void> {
67+
const args = await yargs(hideBin(argv))
68+
.example(
69+
'$0 https://dbpedia.org/sparql --query \'SELECT * WHERE { ?s ?p ?o } LIMIT 100\'',
70+
'Fetch 100 triples from the DBPedia SPARQL endpoint',
71+
)
72+
.example(
73+
'$0 https://dbpedia.org/sparql --file query.rq',
74+
'Run the SPARQL query from query.rq against the DBPedia SPARQL endpoint',
75+
)
76+
.example(
77+
'cat query.rq | $0 https://dbpedia.org/sparql',
78+
'Run the SPARQL query from query.rq against the DBPedia SPARQL endpoint',
79+
)
80+
.positional('endpoint', { type: 'string', describe: 'Send the query to this SPARQL endpoint', demandOption: true })
81+
.positional('query', { type: 'string', describe: 'The query to execute' })
82+
.options({
83+
query: { alias: 'q', type: 'string', describe: 'Evaluate the given SPARQL query string' },
84+
file: { alias: 'f', type: 'string', describe: 'Evaluate the SPARQL query in the given file' },
85+
get: { alias: 'g', type: 'boolean', describe: 'Send query via HTTP GET instead of POST', default: false },
11686
})
117-
.catch((error) => {
118-
process.stderr.write(error.message + '\n');
119-
process.exit(1);
120-
});
87+
.help()
88+
.parse();
89+
const endpoint = <string>args._[0];
90+
const queryString = await getQuery(args._.length > 1 ? <string>args._[1] : args.query, args.file);
91+
const fetcher = new SparqlEndpointFetcher({ method: args.get ? 'GET' : 'POST' });
92+
const queryType = fetcher.getQueryType(queryString);
93+
switch (queryType) {
94+
case 'SELECT':
95+
await querySelect(endpoint, fetcher, queryString);
96+
break;
97+
case 'ASK':
98+
await queryAsk(endpoint, fetcher, queryString);
99+
break;
100+
case 'CONSTRUCT':
101+
await queryConstruct(endpoint, fetcher, queryString);
102+
break;
103+
case 'UNKNOWN':
104+
if (fetcher.getUpdateTypes(queryString) !== 'UNKNOWN') {
105+
await update(endpoint, fetcher, queryString);
106+
}
107+
break;
108+
}
121109
}
110+
111+
run(process.argv).then().catch(error => process.stderr.write(`${error.toString()}\n`));

eslint.config.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const config = require('@rubensworks/eslint-config');
2+
3+
module.exports = config([
4+
{
5+
files: [ '**/*.ts' ],
6+
languageOptions: {
7+
parserOptions: {
8+
tsconfigRootDir: __dirname,
9+
project: [ './tsconfig.eslint.json' ],
10+
},
11+
},
12+
},
13+
{
14+
files: [ '**/*.ts', 'webpack.config.js' ],
15+
rules: {
16+
'import/no-nodejs-modules': 'off',
17+
// The naming convention rules complain about the public static properties, as well
18+
'ts/naming-convention': 'off',
19+
},
20+
},
21+
{
22+
// The readme contains examples with console logging
23+
files: [ 'README.md/*' ],
24+
rules: {
25+
'no-console': 'off',
26+
},
27+
},
28+
]);

0 commit comments

Comments
 (0)