UNPKG

isomorphic-git

Version:

A pure JavaScript reimplementation of git for node and browsers

238 lines (219 loc) 6.34 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var get = _interopDefault(require('simple-get')); /** * @typedef {Object} GitProgressEvent * @property {string} phase * @property {number} loaded * @property {number} total */ /** * @callback ProgressCallback * @param {GitProgressEvent} progress * @returns {void | Promise<void>} */ /** * @typedef {Object} GitHttpRequest * @property {string} url - The URL to request * @property {string} [method='GET'] - The HTTP method to use * @property {Object<string, string>} [headers={}] - Headers to include in the HTTP request * @property {Object} [agent] - An HTTP or HTTPS agent that manages connections for the HTTP client (Node.js only) * @property {AsyncIterableIterator<Uint8Array>} [body] - An async iterator of Uint8Arrays that make up the body of POST requests * @property {ProgressCallback} [onProgress] - Reserved for future use (emitting `GitProgressEvent`s) * @property {object} [signal] - Reserved for future use (canceling a request) */ /** * @typedef {Object} GitHttpResponse * @property {string} url - The final URL that was fetched after any redirects * @property {string} [method] - The HTTP method that was used * @property {Object<string, string>} [headers] - HTTP response headers * @property {AsyncIterableIterator<Uint8Array>} [body] - An async iterator of Uint8Arrays that make up the body of the response * @property {number} statusCode - The HTTP status code * @property {string} statusMessage - The HTTP status message */ /** * @callback HttpFetch * @param {GitHttpRequest} request * @returns {Promise<GitHttpResponse>} */ /** * @typedef {Object} HttpClient * @property {HttpFetch} request */ // Convert a value to an Async Iterator // This will be easier with async generator functions. function fromValue(value) { let queue = [value]; return { next() { return Promise.resolve({ done: queue.length === 0, value: queue.pop() }) }, return() { queue = []; return {} }, [Symbol.asyncIterator]() { return this }, } } function getIterator(iterable) { if (iterable[Symbol.asyncIterator]) { return iterable[Symbol.asyncIterator]() } if (iterable[Symbol.iterator]) { return iterable[Symbol.iterator]() } if (iterable.next) { return iterable } return fromValue(iterable) } // Currently 'for await' upsets my linters. async function forAwait(iterable, cb) { const iter = getIterator(iterable); while (true) { const { value, done } = await iter.next(); if (value) await cb(value); if (done) break } if (iter.return) iter.return(); } function asyncIteratorToStream(iter) { const { PassThrough } = require('readable-stream'); const stream = new PassThrough(); setTimeout(async () => { await forAwait(iter, chunk => stream.write(chunk)); stream.end(); }, 1); return stream } async function collect(iterable) { let size = 0; const buffers = []; // This will be easier once `for await ... of` loops are available. await forAwait(iterable, value => { buffers.push(value); size += value.byteLength; }); const result = new Uint8Array(size); let nextIndex = 0; for (const buffer of buffers) { result.set(buffer, nextIndex); nextIndex += buffer.byteLength; } return result } // Convert a Node stream to an Async Iterator function fromNodeStream(stream) { // Use native async iteration if it's available. const asyncIterator = Object.getOwnPropertyDescriptor( stream, Symbol.asyncIterator ); if (asyncIterator && asyncIterator.enumerable) { return stream } // Author's Note // I tried many MANY ways to do this. // I tried two npm modules (stream-to-async-iterator and streams-to-async-iterator) with no luck. // I tried using 'readable' and .read(), and .pause() and .resume() // It took me two loooong evenings to get to this point. // So if you are horrified that this solution just builds up a queue with no backpressure, // and turns Promises inside out, too bad. This is the first code that worked reliably. let ended = false; const queue = []; let defer = {}; stream.on('data', chunk => { queue.push(chunk); if (defer.resolve) { defer.resolve({ value: queue.shift(), done: false }); defer = {}; } }); stream.on('error', err => { if (defer.reject) { defer.reject(err); defer = {}; } }); stream.on('end', () => { ended = true; if (defer.resolve) { defer.resolve({ done: true }); defer = {}; } }); return { next() { return new Promise((resolve, reject) => { if (queue.length === 0 && ended) { return resolve({ done: true }) } else if (queue.length > 0) { return resolve({ value: queue.shift(), done: false }) } else if (queue.length === 0 && !ended) { defer = { resolve, reject }; } }) }, return() { stream.removeAllListeners(); if (stream.destroy) stream.destroy(); }, [Symbol.asyncIterator]() { return this }, } } /** * HttpClient * * @param {GitHttpRequest} request * @returns {Promise<GitHttpResponse>} */ async function request({ onProgress, url, method = 'GET', headers = {}, agent, body, }) { // If we can, we should send it as a single buffer so it sets a Content-Length header. if (body && Array.isArray(body)) { body = Buffer.from(await collect(body)); } else if (body) { body = asyncIteratorToStream(body); } return new Promise((resolve, reject) => { get( { url, method, headers, agent, body, }, (err, res) => { if (err) return reject(err) try { const iter = fromNodeStream(res); resolve({ url: res.url, method: res.method, statusCode: res.statusCode, statusMessage: res.statusMessage, body: iter, headers: res.headers, }); } catch (e) { reject(e); } } ); }) } var index = { request }; exports.default = index; exports.request = request;