From b58133440246099f3c9afb6e7ae4fe51d7bf6b3f Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Thu, 29 Aug 2024 15:10:05 +0200 Subject: [PATCH] Refactor to make code more TypeScript compatible (#183) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change contains some refactors, mostly to make the code better compatible with TypeScript. All `require()` calls were changed to the form `const identifier = require('specifier')`. This is compatible with TypeScript’s `verbatimModuleSyntax` option, which is strongly recommended in order to produce valid library types. All uses of `lodash` were changed to native JavaScript implementations. The `lodash` dependency was removed. Static class assignments were changed to static class property syntax. `util.promisify` usage was replaced with Node.js promise APIs. --- package.json | 1 - src/Transloadit.js | 57 +++++++++++++---------- src/tus.js | 18 ++++--- test/generate-coverage-badge.js | 2 +- test/integration/live-api.test.js | 26 +++++------ test/testserver.js | 18 +++---- test/tunnel.js | 24 +++++----- test/unit/test-pagination-stream.test.js | 11 +---- test/unit/test-transloadit-client.test.js | 10 ++-- yarn.lock | 8 ---- 10 files changed, 87 insertions(+), 88 deletions(-) diff --git a/package.json b/package.json index 3f22878..257c0fb 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "got": "^11.8.1", "into-stream": "^6.0.0", "is-stream": "^2.0.0", - "lodash": "^4.17.20", "p-map": "^4.0.0", "tus-js-client": "^3.0.1", "uuid": "^8.3.2" diff --git a/src/Transloadit.js b/src/Transloadit.js index 9eaf063..7820e9e 100644 --- a/src/Transloadit.js +++ b/src/Transloadit.js @@ -1,11 +1,9 @@ const got = require('got') const FormData = require('form-data') const crypto = require('crypto') -const fromPairs = require('lodash/fromPairs') const fs = require('fs') -const { access } = require('fs').promises -const log = require('debug')('transloadit') -const logWarn = require('debug')('transloadit:warn') +const fsPromises = require('fs/promises') +const debug = require('debug') const intoStream = require('into-stream') const isStream = require('is-stream') const assert = require('assert') @@ -14,9 +12,12 @@ const uuid = require('uuid') const InconsistentResponseError = require('./InconsistentResponseError') const PaginationStream = require('./PaginationStream') -const { version } = require('../package.json') +const pkg = require('../package.json') const tus = require('./tus') +const log = debug('transloadit') +const logWarn = debug('transloadit:warn') + function decorateHttpError(err, body) { if (!body) return err @@ -70,6 +71,24 @@ function checkResult(result) { } class TransloaditClient { + // See https://github.com/sindresorhus/got#errors + // Expose relevant errors + static RequestError = got.RequestError + + static ReadError = got.ReadError + + static ParseError = got.ParseError + + static UploadError = got.UploadError + + static HTTPError = got.HTTPError + + static MaxRedirectsError = got.MaxRedirectsError + + static TimeoutError = got.TimeoutError + + static InconsistentResponseError = InconsistentResponseError + constructor(opts = {}) { if (opts.authKey == null) { throw new Error('Please provide an authKey') @@ -156,13 +175,15 @@ class TransloaditClient { const promise = (async () => { this._lastUsedAssemblyUrl = `${this._endpoint}${urlSuffix}` - // eslint-disable-next-line no-bitwise - await pMap(Object.entries(files), async ([, path]) => access(path, fs.F_OK | fs.R_OK), { - concurrency: 5, - }) + await pMap( + Object.entries(files), + // eslint-disable-next-line no-bitwise + async ([, path]) => fsPromises.access(path, fs.constants.F_OK | fs.constants.R_OK), + { concurrency: 5 } + ) // Convert uploads to streams - const streamsMap = fromPairs( + const streamsMap = Object.fromEntries( Object.entries(uploads).map(([label, value]) => { const isReadable = isStream.readable(value) if (!isReadable && isStream(value)) { @@ -175,7 +196,7 @@ class TransloaditClient { ) // Wrap in object structure (so we can know if it's a pathless stream or not) - const allStreamsMap = fromPairs( + const allStreamsMap = Object.fromEntries( Object.entries(streamsMap).map(([label, stream]) => [label, { stream }]) ) @@ -679,7 +700,7 @@ class TransloaditClient { body: form, timeout, headers: { - 'Transloadit-Client': `node-sdk:${version}`, + 'Transloadit-Client': `node-sdk:${pkg.version}`, 'User-Agent': undefined, // Remove got's user-agent ...headers, }, @@ -726,16 +747,4 @@ class TransloaditClient { } } -// See https://github.com/sindresorhus/got#errors -// Expose relevant errors -TransloaditClient.RequestError = got.RequestError -TransloaditClient.ReadError = got.ReadError -TransloaditClient.ParseError = got.ParseError -TransloaditClient.UploadError = got.UploadError -TransloaditClient.HTTPError = got.HTTPError -TransloaditClient.MaxRedirectsError = got.MaxRedirectsError -TransloaditClient.TimeoutError = got.TimeoutError - -TransloaditClient.InconsistentResponseError = InconsistentResponseError - module.exports = TransloaditClient diff --git a/src/tus.js b/src/tus.js index bbed4e8..1a18ee1 100644 --- a/src/tus.js +++ b/src/tus.js @@ -1,10 +1,11 @@ -const log = require('debug')('transloadit') -const sumBy = require('lodash/sumBy') -const { basename } = require('path') +const debug = require('debug') +const nodePath = require('path') const tus = require('tus-js-client') -const { stat: fsStat } = require('fs').promises +const fsPromises = require('fs/promises') const pMap = require('p-map') +const log = debug('transloadit') + async function sendTusRequest({ streamsMap, assembly, @@ -28,7 +29,7 @@ async function sendTusRequest({ const { path } = streamsMap[label] if (path) { - const { size } = await fsStat(path) + const { size } = await fsPromises.stat(path) sizes[label] = size totalBytes += size } @@ -58,7 +59,10 @@ async function sendTusRequest({ uploadProgresses[label] = bytesUploaded // get all uploaded bytes for all files - const uploadedBytes = sumBy(streamLabels, (l) => uploadProgresses[l]) + let uploadedBytes = 0 + for (const l of streamLabels) { + uploadedBytes += uploadProgresses[l] + } // don't send redundant progress if (lastEmittedProgress < uploadedBytes) { @@ -72,7 +76,7 @@ async function sendTusRequest({ } } - const filename = path ? basename(path) : label + const filename = path ? nodePath.basename(path) : label await new Promise((resolve, reject) => { const tusOptions = { diff --git a/test/generate-coverage-badge.js b/test/generate-coverage-badge.js index 843fd74..592079d 100644 --- a/test/generate-coverage-badge.js +++ b/test/generate-coverage-badge.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const fs = require('fs').promises +const fs = require('fs/promises') const { makeBadge } = require('badge-maker') // eslint-disable-next-line import/newline-after-import diff --git a/test/integration/live-api.test.js b/test/integration/live-api.test.js index fff9686..71d8aed 100644 --- a/test/integration/live-api.test.js +++ b/test/integration/live-api.test.js @@ -1,17 +1,14 @@ -// https://github.com/axios/axios/issues/2654 -const keyBy = require('lodash/keyBy') const querystring = require('querystring') const temp = require('temp') -const { createWriteStream } = require('fs') -const { join } = require('path') -const { promisify } = require('util') -const { pipeline: streamPipeline } = require('stream') +const fs = require('fs') +const nodePath = require('path') +const nodeStream = require('stream/promises') const got = require('got') const intoStream = require('into-stream') const uuid = require('uuid') -const debug = require('debug')('transloadit:live-api') +const debug = require('debug') -const pipeline = promisify(streamPipeline) +const log = debug('transloadit:live-api') const Transloadit = require('../../src/Transloadit') @@ -19,7 +16,7 @@ const { createTestServer } = require('../testserver') async function downloadTmpFile(url) { const { path } = await temp.open('transloadit') - await pipeline(got.stream(url), createWriteStream(path)) + await nodeStream.pipeline(got.stream(url), fs.createWriteStream(path)) return path } @@ -119,10 +116,10 @@ beforeAll(async () => { if (req.url === '') req.url = '/' handler(req, res) } else { - debug('request handler for UUID not found', id) + log('request handler for UUID not found', id) } } else { - debug('Invalid path match', req.url) + log('Invalid path match', req.url) } }) }, 100000) @@ -133,7 +130,7 @@ afterAll(async () => { async function createVirtualTestServer(handler) { const id = uuid.v4() - debug('Adding virtual server handler', id) + log('Adding virtual server handler', id) const url = `${testServer.url}/${id}` handlers.set(id, handler) @@ -275,7 +272,8 @@ describe('API integration', { timeout: 30000 }, () => { original_path: '/', original_md5hash: '1b199e02dd833b2278ce2a0e75480b14', }) - const uploadsKeyed = keyBy(result.uploads, 'name') // Because order is not same as input + // Because order is not same as input + const uploadsKeyed = Object.fromEntries(result.uploads, (upload) => [upload.name, upload]) expect(uploadsKeyed.file1).toMatchObject(getMatchObject({ name: 'file1' })) expect(uploadsKeyed.file2).toMatchObject(getMatchObject({ name: 'file2' })) expect(uploadsKeyed.file3).toMatchObject(getMatchObject({ name: 'file3' })) @@ -390,7 +388,7 @@ describe('API integration', { timeout: 30000 }, () => { }, }, files: { - file: join(__dirname, './fixtures/zerobytes.jpg'), + file: nodePath.join(__dirname, './fixtures/zerobytes.jpg'), }, waitForCompletion: true, } diff --git a/test/testserver.js b/test/testserver.js index 8fe4918..04cab10 100644 --- a/test/testserver.js +++ b/test/testserver.js @@ -1,6 +1,8 @@ const http = require('http') const got = require('got') -const debug = require('debug')('transloadit:testserver') +const debug = require('debug') + +const log = debug('transloadit:testserver') const createTunnel = require('./tunnel') @@ -13,7 +15,7 @@ async function createHttpServer(handler) { // Find a free port to use function tryListen() { server.listen(port, '127.0.0.1', () => { - debug(`server listening on port ${port}`) + log(`server listening on port ${port}`) resolve({ server, port }) }) } @@ -45,7 +47,7 @@ async function createTestServer(onRequest) { let tunnel const handleHttpRequest = (req, res) => { - debug('HTTP request handler', req.method, req.url) + log('HTTP request handler', req.method, req.url) if (!initialized) { if (req.url !== expectedPath) throw new Error(`Unexpected path ${req.url}`) @@ -62,17 +64,17 @@ async function createTestServer(onRequest) { async function close() { if (tunnel) await tunnel.close() await new Promise((resolve) => server.close(() => resolve())) - debug('closed tunnel') + log('closed tunnel') } try { tunnel = createTunnel({ cloudFlaredPath: process.env.CLOUDFLARED_PATH, port }) - debug('waiting for tunnel to be created') + log('waiting for tunnel to be created') const tunnelPublicUrl = await tunnel.urlPromise - debug('tunnel created', tunnelPublicUrl) + log('tunnel created', tunnelPublicUrl) - debug('Waiting for tunnel to allow requests to pass through') + log('Waiting for tunnel to allow requests to pass through') // eslint-disable-next-line no-inner-declarations async function sendTunnelRequest() { @@ -100,7 +102,7 @@ async function createTestServer(onRequest) { sendTunnelRequest(), ]) - debug('Tunnel ready', tunnelPublicUrl) + log('Tunnel ready', tunnelPublicUrl) return { port, diff --git a/test/tunnel.js b/test/tunnel.js index 09c83ae..d20c464 100644 --- a/test/tunnel.js +++ b/test/tunnel.js @@ -1,10 +1,11 @@ const execa = require('execa') const readline = require('readline') -const { Resolver } = require('dns') -const { promisify } = require('util') -const debug = require('debug')('transloadit:cloudflared-tunnel') +const dns = require('dns/promises') +const debug = require('debug') const pRetry = require('p-retry') +const log = debug('transloadit:cloudflared-tunnel') + async function startTunnel({ cloudFlaredPath, port }) { const process = execa( cloudFlaredPath, @@ -39,7 +40,7 @@ async function startTunnel({ cloudFlaredPath, port }) { ] rl.on('line', (line) => { - debug(line) + log(line) fullStderr += `${line}\n` if ( @@ -72,7 +73,7 @@ async function startTunnel({ cloudFlaredPath, port }) { } } -module.exports = ({ cloudFlaredPath = 'cloudflared', port }) => { +function createTunnel({ cloudFlaredPath = 'cloudflared', port }) { let process const urlPromise = (async () => { @@ -80,23 +81,22 @@ module.exports = ({ cloudFlaredPath = 'cloudflared', port }) => { ;({ process } = tunnel) const { url } = tunnel - debug('Found url', url) + log('Found url', url) // We need to wait for DNS to be resolvable. // If we don't, the operating system's dns cache will be poisoned by the not yet valid resolved entry // and it will forever fail for that subdomain name... - const resolver = new Resolver() + const resolver = new dns.Resolver() resolver.setServers(['1.1.1.1']) // use cloudflare's dns server. if we don't explicitly specify DNS server, it will also poison our OS' dns cache - const resolve4 = promisify(resolver.resolve4.bind(resolver)) for (let i = 0; i < 10; i += 1) { try { const host = new URL(url).hostname - debug('checking dns', host) - await resolve4(host) + log('checking dns', host) + await resolver.resolve4(host) return url } catch (err) { - debug('dns err', err.message) + log('dns err', err.message) await new Promise((resolve) => setTimeout(resolve, 3000)) } } @@ -117,3 +117,5 @@ module.exports = ({ cloudFlaredPath = 'cloudflared', port }) => { close, } } + +module.exports = createTunnel diff --git a/test/unit/test-pagination-stream.test.js b/test/unit/test-pagination-stream.test.js index 3954a66..72975dd 100644 --- a/test/unit/test-pagination-stream.test.js +++ b/test/unit/test-pagination-stream.test.js @@ -1,5 +1,4 @@ const { Writable } = require('stream') -const _ = require('lodash') const PaginationStream = require('../../src/PaginationStream') @@ -27,10 +26,7 @@ describe('PaginationStream', () => { await new Promise((resolve) => { stream.pipe( toArray((array) => { - const expected = _.flatten( - Array.from(pages).map(({ items }) => items), - true - ) + const expected = pages.flatMap(({ items }) => items) expect(array).toEqual(expected) resolve() @@ -56,10 +52,7 @@ describe('PaginationStream', () => { await new Promise((resolve) => { stream.pipe( toArray((array) => { - const expected = _.flatten( - Array.from(pages).map(({ items }) => items), - true - ) + const expected = pages.flatMap(({ items }) => items) expect(array).toEqual(expected) resolve() diff --git a/test/unit/test-transloadit-client.test.js b/test/unit/test-transloadit-client.test.js index 8d280d0..fa0edb3 100644 --- a/test/unit/test-transloadit-client.test.js +++ b/test/unit/test-transloadit-client.test.js @@ -1,10 +1,10 @@ -const { Readable: ReadableStream } = require('stream') +const stream = require('stream') const FormData = require('form-data') const got = require('got') const tus = require('../../src/tus') const Transloadit = require('../../src/Transloadit') -const packageVersion = require('../../package.json').version +const pkg = require('../../package.json') const mockedExpiresDate = '2021-01-06T21:11:07.883Z' const mockGetExpiresDate = (client) => @@ -108,8 +108,8 @@ describe('Transloadit', () => { it('should append all required fields to the request form', () => { const client = new Transloadit({ authKey: 'foo_key', authSecret: 'foo_secret' }) - const stream1 = new ReadableStream() - const stream2 = new ReadableStream() + const stream1 = new stream.Readable() + const stream2 = new stream.Readable() const streamsMap = { stream1: { stream: stream1 }, @@ -280,7 +280,7 @@ describe('Transloadit', () => { expect(get).toHaveBeenCalledWith( expect.any(String), - expect.objectContaining({ headers: { 'Transloadit-Client': `node-sdk:${packageVersion}` } }) + expect.objectContaining({ headers: { 'Transloadit-Client': `node-sdk:${pkg.version}` } }) ) }) }) diff --git a/yarn.lock b/yarn.lock index 9239a7c..d61b92e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3848,13 +3848,6 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.20": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c - languageName: node - linkType: hard - "log-symbols@npm:^4.0.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" @@ -5761,7 +5754,6 @@ __metadata: got: "npm:^11.8.1" into-stream: "npm:^6.0.0" is-stream: "npm:^2.0.0" - lodash: "npm:^4.17.20" nock: "npm:^13.0.5" npm-run-all: "npm:^4.1.5" p-map: "npm:^4.0.0"