diff --git a/.changeset/red-crews-draw.md b/.changeset/red-crews-draw.md new file mode 100644 index 00000000..6b836f5f --- /dev/null +++ b/.changeset/red-crews-draw.md @@ -0,0 +1,5 @@ +--- +"barnard59-http": minor +--- + +Add overload to `get` and `post` to match the signature of native `fetch` diff --git a/.changeset/smooth-lamps-rest.md b/.changeset/smooth-lamps-rest.md new file mode 100644 index 00000000..1d7c5e68 --- /dev/null +++ b/.changeset/smooth-lamps-rest.md @@ -0,0 +1,5 @@ +--- +"barnard59-http": patch +--- + +Added TS declarations diff --git a/package-lock.json b/package-lock.json index dbac8545..68528930 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11952,7 +11952,9 @@ } }, "node_modules/docker-compose": { - "version": "0.24.7", + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", "dev": true, "license": "MIT", "dependencies": { @@ -28212,7 +28214,7 @@ "barnard59-core": "^6.0.1", "barnard59-env": "^1.2.3", "barnard59-test-support": "*", - "docker-compose": "^0.24.7", + "docker-compose": "^0.24.8", "express-as-promise": "^1.2.0", "get-stream": "^6.0.1", "is-stream": "^3", @@ -28262,6 +28264,7 @@ "readable-stream": ">=3" }, "devDependencies": { + "@types/duplexify": "^3.6.4", "express-as-promise": "^1.2.0", "get-stream": "^6.0.1", "is-stream": "^3.0.0" diff --git a/packages/cli/test/barnard59.test.js b/packages/cli/test/barnard59.test.js index 427739a0..15febee0 100644 --- a/packages/cli/test/barnard59.test.js +++ b/packages/cli/test/barnard59.test.js @@ -12,7 +12,7 @@ describe('barnard59', function () { describe('run', () => { it('should suggest alternatives when multiple root pipelines exist', () => { const pipelineFile = filenamePipelineDefinition('multiple-root') - const command = `barnard59 run ${pipelineFile}` + const command = `npx barnard59 run ${pipelineFile}` const result = shell.exec(command, { silent: true, cwd }) @@ -21,7 +21,7 @@ describe('barnard59', function () { }) it('should exit with error code 0 if there are no error while processing the pipeline', () => { const pipelineFile = filenamePipelineDefinition('simple') - const command = `barnard59 run --pipeline=http://example.org/pipeline/ ${pipelineFile}` + const command = `npx barnard59 run --pipeline=http://example.org/pipeline/ ${pipelineFile}` const result = shell.exec(command, { silent: true, cwd }) @@ -30,7 +30,7 @@ describe('barnard59', function () { it('should exit with error code 1 when an error in the pipeline occurs', () => { const pipelineFile = filenamePipelineDefinition('error') - const command = `barnard59 run --pipeline=http://example.org/pipeline/ ${pipelineFile}` + const command = `npx barnard59 run --pipeline=http://example.org/pipeline/ ${pipelineFile}` const result = shell.exec(command, { silent: true, cwd }) @@ -40,7 +40,7 @@ describe('barnard59', function () { describe('verbose', () => { it('should log info messages if verbose flag is set', () => { const pipelineFile = filenamePipelineDefinition('logs') - const command = `barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile}` + const command = `npx barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile}` const result = stripAnsi(shell.exec(command, { cwd }).stderr) @@ -49,7 +49,7 @@ describe('barnard59', function () { it('all logs suppressed with --quiet flag', () => { const pipelineFile = filenamePipelineDefinition('logs') - const command = `barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile} -q` + const command = `npx barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile} -q` const result = stripAnsi(shell.exec(command, { silent: true, cwd }).stderr) @@ -58,7 +58,7 @@ describe('barnard59', function () { it('should log info messages if verbose flag is set before command', () => { const pipelineFile = filenamePipelineDefinition('logs') - const command = `barnard59 --verbose run --pipeline=http://example.org/pipeline/ ${pipelineFile}` + const command = `npx barnard59 --verbose run --pipeline=http://example.org/pipeline/ ${pipelineFile}` const result = stripAnsi(shell.exec(command, { silent: true, cwd }).stderr) @@ -67,7 +67,7 @@ describe('barnard59', function () { it('should not log debug messages if verbose flag is set', () => { const pipelineFile = filenamePipelineDefinition('logs') - const command = `barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile}` + const command = `npx barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile}` const result = stripAnsi(shell.exec(command, { silent: true, cwd }).stderr) @@ -76,7 +76,7 @@ describe('barnard59', function () { it('should log trace messages if verbose flag is set 4 times', () => { const pipelineFile = filenamePipelineDefinition('logs') - const command = `barnard59 run --pipeline=http://example.org/pipeline/ -vvvv ${pipelineFile}` + const command = `npx barnard59 run --pipeline=http://example.org/pipeline/ -vvvv ${pipelineFile}` const result = stripAnsi(shell.exec(command, { silent: true, cwd }).stderr) @@ -87,7 +87,7 @@ describe('barnard59', function () { describe('variable', () => { it('should set the given variable to the given value', () => { const pipelineFile = filenamePipelineDefinition('simple') - const command = `barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile} --variable=abc=123` + const command = `npx barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile} --variable=abc=123` const result = stripAnsi(shell.exec(command, { silent: true, cwd }).stderr) @@ -96,7 +96,7 @@ describe('barnard59', function () { it('should set the given variable to the given value before command', () => { const pipelineFile = filenamePipelineDefinition('simple') - const command = `barnard59 --variable=abc=123 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile}` + const command = `npx barnard59 --variable=abc=123 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile}` const result = stripAnsi(shell.exec(command, { silent: true, cwd }).stderr) @@ -105,7 +105,7 @@ describe('barnard59', function () { it('should set the given variable to the value of the environment variable with the same name', () => { const pipelineFile = filenamePipelineDefinition('simple') - const command = `abc=123 barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile} --variable=abc` + const command = `abc=123 npx barnard59 run --pipeline=http://example.org/pipeline/ --verbose ${pipelineFile} --variable=abc` const result = stripAnsi(shell.exec(command, { silent: true, cwd }).stderr) @@ -125,7 +125,7 @@ describe('barnard59', function () { context(`${optionsBefore} run ${optionsAfter}`, () => { it(title, () => { const pipelineFile = filenamePipelineDefinition('simple') - const command = `${env} barnard59 ${optionsBefore} run --pipeline=http://example.org/pipeline/ -vv ${pipelineFile} ${optionsAfter}` + const command = `${env} npx barnard59 ${optionsBefore} run --pipeline=http://example.org/pipeline/ -vv ${pipelineFile} ${optionsAfter}` const result = stripAnsi(shell.exec(command, { silent: true }).stderr) @@ -138,34 +138,31 @@ describe('barnard59', function () { }) describe('examples', function () { - // Examples can be a bit slow to run - this.timeout(5000) - it('should run the fetch-json-to-ntriples.json example without error', () => { const pipelineFile = (new URL('../examples/fetch-json-to-ntriples.json', import.meta.url)).pathname - const command = `barnard59 run --pipeline=http://example.org/pipeline/cet ${pipelineFile}` + const command = `npx barnard59 run --pipeline=http://example.org/pipeline/cet ${pipelineFile}` const result = shell.exec(command, { silent: true, cwd }) - strictEqual(result.code, 0) + strictEqual(result.code, 0, result.stderr) }) it('should run the fetch-json-to-ntriples.ttl example without error', () => { const pipelineFile = (new URL('../examples/fetch-json-to-ntriples.ttl', import.meta.url)).pathname - const command = `barnard59 run --pipeline=http://example.org/pipeline/utc ${pipelineFile}` + const command = `npx barnard59 run --pipeline=http://example.org/pipeline/utc ${pipelineFile}` const result = shell.exec(command, { silent: true, cwd }) - strictEqual(result.code, 0) + strictEqual(result.code, 0, result.stderr) }) it('should run the parse-csvw.ttl example without error', () => { const pipelineFile = (new URL('../examples/parse-csvw.ttl', import.meta.url)).pathname - const command = `barnard59 run -vv --pipeline=http://example.org/pipeline/parseCsvw ${pipelineFile}` + const command = `npx barnard59 run -vv --pipeline=http://example.org/pipeline/parseCsvw ${pipelineFile}` const result = shell.exec(command, { silent: true, cwd }) - strictEqual(result.code, 0) + strictEqual(result.code, 0, result.stderr) }) }) }) diff --git a/packages/graph-store/package.json b/packages/graph-store/package.json index 370f9bd6..72614e49 100644 --- a/packages/graph-store/package.json +++ b/packages/graph-store/package.json @@ -42,7 +42,7 @@ "barnard59-core": "^6.0.1", "barnard59-env": "^1.2.3", "barnard59-test-support": "*", - "docker-compose": "^0.24.7", + "docker-compose": "^0.24.8", "express-as-promise": "^1.2.0", "get-stream": "^6.0.1", "is-stream": "^3", diff --git a/packages/graph-store/test/pipeline.test.js b/packages/graph-store/test/pipeline.test.js index 6ef909d7..ad563472 100644 --- a/packages/graph-store/test/pipeline.test.js +++ b/packages/graph-store/test/pipeline.test.js @@ -1,6 +1,6 @@ import { strictEqual } from 'node:assert' import ParsingClient from 'sparql-http-client/ParsingClient.js' -import * as compose from 'docker-compose' +import { upAll } from 'docker-compose/dist/v2.js' import waitOn from 'wait-on' import { pipelineDefinitionLoader } from 'barnard59-test-support/loadPipelineDefinition.js' import env from 'barnard59-env/index.ts' @@ -20,7 +20,7 @@ const endpoint = 'http://localhost:3030/test' describe('graph-store pipeline', function () { before(async function () { this.timeout(100000) - await compose.upAll({ + await upAll({ cwd: support, }) await waitOn({ diff --git a/packages/http/.gitignore b/packages/http/.gitignore new file mode 100644 index 00000000..a6c7c285 --- /dev/null +++ b/packages/http/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/packages/http/.npmignore b/packages/http/.npmignore new file mode 100644 index 00000000..19991582 --- /dev/null +++ b/packages/http/.npmignore @@ -0,0 +1,3 @@ +*.ts +!*.d.ts +test/ diff --git a/packages/http/get.js b/packages/http/get.js deleted file mode 100644 index 3d694b53..00000000 --- a/packages/http/get.js +++ /dev/null @@ -1,7 +0,0 @@ -import fetch from './lib/fetch.js' - -function get(options) { - return fetch(options) -} - -export default get diff --git a/packages/http/get.ts b/packages/http/get.ts new file mode 100644 index 00000000..9b9f777e --- /dev/null +++ b/packages/http/get.ts @@ -0,0 +1,15 @@ +import type { RequestInit } from 'node-fetch' +import type { GetInit } from './lib/fetch.js' +import fetch from './lib/fetch.js' + +function get(options: GetInit): ReturnType +function get(url: string, options?: RequestInit): ReturnType +function get(urlOrOptions: GetInit | string, options: RequestInit = {}): ReturnType { + if (typeof urlOrOptions === 'string') { + return fetch({ ...options, url: urlOrOptions }) + } + + return fetch(urlOrOptions) +} + +export default get diff --git a/packages/http/index.js b/packages/http/index.ts similarity index 100% rename from packages/http/index.js rename to packages/http/index.ts diff --git a/packages/http/lib/fetch.js b/packages/http/lib/fetch.ts similarity index 59% rename from packages/http/lib/fetch.js rename to packages/http/lib/fetch.ts index 7c07b8e3..ac7521ca 100644 --- a/packages/http/lib/fetch.js +++ b/packages/http/lib/fetch.ts @@ -1,15 +1,19 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { SpanStatusCode } from '@opentelemetry/api' import toReadable from 'duplex-to/readable.js' +import type { RequestInit } from 'node-fetch' import nodeFetch from 'node-fetch' import tracer from './tracer.js' -async function fetch({ method = 'GET', url, ...options } = {}) { +export type GetInit = RequestInit & { url: string } + +export default async function fetch({ method = 'GET', url, ...options }: GetInit) { return await tracer.startActiveSpan('fetch', async span => { try { const response = await nodeFetch(url, { method, ...options }) - return toReadable(response.body) - } catch (e) { + return toReadable(response.body as any) + } catch (e: any) { span.recordException(e) span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }) } finally { @@ -17,5 +21,3 @@ async function fetch({ method = 'GET', url, ...options } = {}) { } }) } - -export default fetch diff --git a/packages/http/lib/tracer.js b/packages/http/lib/tracer.ts similarity index 100% rename from packages/http/lib/tracer.js rename to packages/http/lib/tracer.ts diff --git a/packages/http/lib/writableFetch.js b/packages/http/lib/writableFetch.ts similarity index 52% rename from packages/http/lib/writableFetch.js rename to packages/http/lib/writableFetch.ts index b1a0b444..d734959a 100644 --- a/packages/http/lib/writableFetch.js +++ b/packages/http/lib/writableFetch.ts @@ -1,17 +1,22 @@ -import duplexify from 'duplexify' -import nodeFetch from 'node-fetch' +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { Duplex } from 'node:stream' import { PassThrough } from 'readable-stream' +import type { RequestInit } from 'node-fetch' +import nodeFetch from 'node-fetch' +import duplexify from 'duplexify' import tracer from './tracer.js' -async function fetch({ method = 'POST', url, ...options } = {}) { - const inputStream = new PassThrough() - const outputStream = new PassThrough() +export type PostInit = RequestInit & { url: string } + +export default async function writableFetch({ method = 'POST', url, ...options }: PostInit): Promise { + const inputStream: any = new PassThrough() + const outputStream: any = new PassThrough() tracer.startActiveSpan('writableFetch', span => setTimeout(async () => { try { const response = await nodeFetch(url, { method, body: inputStream, ...options }) - response.body.pipe(outputStream) + response.body!.pipe(outputStream) } catch (err) { outputStream.emit('error', err) } finally { @@ -21,5 +26,3 @@ async function fetch({ method = 'POST', url, ...options } = {}) { return duplexify(inputStream, outputStream) } - -export default fetch diff --git a/packages/http/package.json b/packages/http/package.json index 76e7e533..e209f44e 100644 --- a/packages/http/package.json +++ b/packages/http/package.json @@ -5,7 +5,9 @@ "main": "index.js", "type": "module", "scripts": { - "test": "mocha" + "test": "mocha", + "build": "tsc", + "prepack": "npm run build" }, "repository": { "type": "git", @@ -27,6 +29,7 @@ "readable-stream": ">=3" }, "devDependencies": { + "@types/duplexify": "^3.6.4", "express-as-promise": "^1.2.0", "get-stream": "^6.0.1", "is-stream": "^3.0.0" diff --git a/packages/http/post.js b/packages/http/post.js deleted file mode 100644 index 08b050b7..00000000 --- a/packages/http/post.js +++ /dev/null @@ -1,7 +0,0 @@ -import writableFetch from './lib/writableFetch.js' - -function post(options) { - return writableFetch(options) -} - -export default post diff --git a/packages/http/post.ts b/packages/http/post.ts new file mode 100644 index 00000000..b98617b7 --- /dev/null +++ b/packages/http/post.ts @@ -0,0 +1,15 @@ +import type { RequestInit } from 'node-fetch' +import type { PostInit } from './lib/writableFetch.js' +import writableFetch from './lib/writableFetch.js' + +function post(options: PostInit): ReturnType +function post(url: string, options: RequestInit): ReturnType +function post(urlOrOptions: PostInit | string, options: RequestInit = {}): ReturnType { + if (typeof urlOrOptions === 'string') { + return writableFetch({ ...options, url: urlOrOptions }) + } + + return writableFetch(urlOrOptions) +} + +export default post diff --git a/packages/http/test/get.test.js b/packages/http/test/get.test.js index c6f75e1f..cb874c83 100644 --- a/packages/http/test/get.test.js +++ b/packages/http/test/get.test.js @@ -1,4 +1,4 @@ -import { strictEqual } from 'assert' +import { strictEqual } from 'node:assert' import withServer from 'express-as-promise/withServer.js' import getStream from 'get-stream' import { isReadableStream as isReadable, isWritableStream as isWritable } from 'is-stream' @@ -32,4 +32,18 @@ describe('get', () => { strictEqual(content, expected.content) }) }) + + it('can be called with 2 arguments', async () => { + await withServer(async server => { + server.app.get('/', (req, res) => { + res.send(req.headers['x-test']) + }) + + const baseUrl = await server.listen() + const response = await get(baseUrl, { headers: { 'x-test': 'test header' } }) + const content = await getStream(response) + + strictEqual(content, 'test header') + }) + }) }) diff --git a/packages/http/test/post.test.js b/packages/http/test/post.test.js index b691b091..b5f4acbd 100644 --- a/packages/http/test/post.test.js +++ b/packages/http/test/post.test.js @@ -1,4 +1,4 @@ -import { strictEqual } from 'assert' +import { strictEqual } from 'node:assert' import withServer from 'express-as-promise/withServer.js' import getStream from 'get-stream' import { isReadableStream as isReadable, isWritableStream as isWritable } from 'is-stream' @@ -45,4 +45,26 @@ describe('post', () => { strictEqual(content, expected.content) }) }) + + it('can be called with 2 arguments', async () => { + await withServer(async server => { + let response + const expected = chunksAndContent() + + server.app.post('/', async (req, res) => { + response = await getStream(req) + + res.status(204).end() + }) + + const baseUrl = await server.listen() + const stream = await post(baseUrl) + + expected.stream.pipe(stream) + + await getStream(stream) + + strictEqual(response, expected.content) + }) + }) }) diff --git a/packages/http/tsconfig.json b/packages/http/tsconfig.json new file mode 100644 index 00000000..b99ddc0a --- /dev/null +++ b/packages/http/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "files": ["./index.ts"], + "compilerOptions": { + "skipLibCheck": true + } +}