Skip to content

Commit

Permalink
feat: add stream query parameter (#152)
Browse files Browse the repository at this point in the history
* refactor: sort fields

* refactor: add optimizeForSpeed

* feat: add stream mode

* fix: ignore build files
  • Loading branch information
Kikobeats authored Mar 17, 2024
1 parent d8a12c4 commit 7b74413
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 76 deletions.
67 changes: 31 additions & 36 deletions lightweight/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ type ScreenshotOverlay = {
}

type PdfMargin = {
bottom?: string | number;
left?: string | number;
right?: string | number;
top?: string | number;
bottom?: string | number;
left?: string | number;
right?: string | number;
}

type PdfOptions = {
Expand All @@ -28,18 +28,19 @@ type PdfOptions = {

type ScreenshotOptions = {
codeScheme?: string;
omitBackground?: boolean;
type?: "jpeg" | "png";
element?: string;
fullPage?: boolean;
overlay?: ScreenshotOverlay
omitBackground?: boolean;
optimizeForSpeed?: boolean;
overlay?: ScreenshotOverlay,
type?: "jpeg" | "png";
}

type MqlClientOptions = {
endpoint?: string;
apiKey?: string;
retry?: number;
cache?: Map<string, any>;
endpoint?: string;
retry?: number;
}

type MqlQuery = {
Expand Down Expand Up @@ -83,8 +84,9 @@ type MicrolinkApiOptions = {
screenshot?: boolean | ScreenshotOptions;
scripts?: string | string[];
scroll?: string;
styles?: string | string[];
staleTtl?: string | number;
stream?: boolean;
styles?: string | string[];
timeout?: number;
ttl?: string | number;
video?: boolean;
Expand All @@ -100,41 +102,33 @@ type IframeInfo = {
}

type MediaInfo = {
url: string;
type?: string;
palette?: string[];
alternative_color?: string;
background_color?: string;
color?: string;
alternative_color?: string;
width?: number;
height?: number;
duration?: number;
duration_pretty?: string;
duration?: number;
height?: number;
palette?: string[];
type?: string;
url: string;
width?: number;
}

type MqlResponseData = {
// A human-readable representation of the author's name.
audio?: MediaInfo | null;
author?: string | null;
// An ISO 8601 representation of the date the article was published.
date?: string | null;
// The publisher's chosen description of the article.
description?: string | null;
// An ISO 639-1 representation of the url content language.
function?: MqlFunctionResult;
iframe?: IframeInfo | null;
image?: MediaInfo | null;
lang?: string | null;
// An image URL that best represents the publisher brand.
logo?: MediaInfo | null;
// A human-readable representation of the publisher's name.
publisher?: string | null;
// The publisher's chosen title of the article.
screenshot?: MediaInfo | null;
title?: string | null;
// The URL of the article.
url?: string;
image?: MediaInfo | null;
screenshot?: MediaInfo | null;
video?: MediaInfo | null;
audio?: MediaInfo | null;
iframe?: IframeInfo | null;
function?: MqlFunctionResult;
}

type MqlFunctionResult = {
Expand Down Expand Up @@ -165,25 +159,26 @@ type HTTPResponseRaw = HTTPResponse & { body: ArrayBuffer };
export type MqlResponse = MqlPayload & { response: HTTPResponseWithBody };

export type MqlError = {
headers: { [key: string]: string };
code: string;
data?: MqlResponseData;
statusCode?: number;
name: string;
message: string;
description: string;
status: MqlStatus;
code: string;
headers: { [key: string]: string };
message: string;
more: string;
name: string;
status: MqlStatus;
statusCode?: number;
url: string;
}

export type MqlOptions = MqlClientOptions & MicrolinkApiOptions;

interface mql {
(url: string, opts?: MqlOptions & { stream: true }, gotOpts?: object): Promise<Response>;
(url: string, opts?: MqlOptions, gotOpts?: object): Promise<MqlResponse>;
arrayBuffer: (url: string, opts?: MqlOptions, gotOpts?: object) => Promise<HTTPResponseRaw>;
extend: (gotOpts?: object) => mql;
stream: (input: RequestInfo, init?: RequestInit) => ReadableStream;
arrayBuffer: (url: string, opts?: MqlOptions, gotOpts?: object) => Promise<HTTPResponseRaw>;
}

declare const mql: mql;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
"standard": {
"ignore": [
"lightweight/index.js",
"lightweight/index.umd.js",
"src/node.mjs"
]
},
Expand Down
16 changes: 13 additions & 3 deletions src/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ const parseBody = (input, error, url) => {
}
}

const factory = ({ VERSION, MicrolinkError, urlHttp, got, flatten }) => {
const factory = streamResponseType => ({
VERSION,
MicrolinkError,
urlHttp,
got,
flatten
}) => {
const assertUrl = (url = '') => {
if (!urlHttp(url)) {
const message = `The \`url\` as \`${url}\` is not valid. Ensure it has protocol (http or https) and hostname.`
Expand All @@ -55,8 +61,8 @@ const factory = ({ VERSION, MicrolinkError, urlHttp, got, flatten }) => {
const fetchFromApi = async (apiUrl, opts = {}) => {
try {
const response = await got(apiUrl, opts)
return opts.responseType === 'buffer'
? { body: response.body, response }
return opts.responseType === streamResponseType
? response
: { ...response.body, response }
} catch (err) {
const { response = {} } = err
Expand Down Expand Up @@ -100,6 +106,10 @@ const factory = ({ VERSION, MicrolinkError, urlHttp, got, flatten }) => {
const headers = isPro
? { ...gotHeaders, 'x-api-key': apiKey }
: { ...gotHeaders }

if (opts.stream) {
responseType = streamResponseType
}
return [apiUrl, { ...gotOpts, responseType, cache, retry, headers }]
}

Expand Down
13 changes: 7 additions & 6 deletions src/lightweight.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

const urlHttp = require('url-http/lightweight')
const { flattie: flatten } = require('flattie')
const factory = require('./factory')
const { default: ky } = require('ky')

const factory = require('./factory')('arrayBuffer')

class MicrolinkError extends Error {
constructor (props) {
super()
Expand All @@ -24,10 +25,10 @@ const got = async (url, { responseType, ...opts }) => {
const body = await response[responseType]()
const { headers, status: statusCode } = response
return { url: response.url, body, headers, statusCode }
} catch (err) {
if (err.response) {
const { response } = err
err.response = {
} catch (error) {
if (error.response) {
const { response } = error
error.response = {
...response,
headers: Array.from(response.headers.entries()).reduce(
(acc, [key, value]) => {
Expand All @@ -40,7 +41,7 @@ const got = async (url, { responseType, ...opts }) => {
body: await response.text()
}
}
throw err
throw error
}
}

Expand Down
18 changes: 8 additions & 10 deletions src/node.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ import { MqlPayload, MqlOptions } from '../lightweight';

export { MqlError, MqlPayload } from '../lightweight'

type HTTPResponse = {
url: string;
isFromCache?: boolean;
statusCode: number;
headers: { [key: string]: string };
};
import { Response, Options as GotOpts } from 'got/dist/source/core'

type HTTPResponse = Response<Buffer>

type HTTPResponseWithBody = HTTPResponse & { body: MqlPayload };

Expand All @@ -16,10 +13,11 @@ type HTTPResponseRaw = HTTPResponse & { body: Buffer };
type MqlResponse = MqlPayload & { response: HTTPResponseWithBody };

interface Mql {
(url: string, opts?: MqlOptions, gotOpts?: object): Promise<MqlResponse>;
extend: (gotOpts?: object) => Mql;
stream: (url: string, gotOpts?: object) => NodeJS.ReadableStream;
buffer: (url: string, opts?: MqlOptions, gotOpts?: object) => Promise<HTTPResponseRaw>;
(url: string, opts?: MqlOptions & { stream: true }, gotOpts?: GotOpts): Promise<HTTPResponse>;
(url: string, opts?: MqlOptions, gotOpts?: GotOpts): Promise<MqlResponse>;
extend: (gotOpts?: GotOpts) => Mql;
stream: (url: string, gotOpts?: GotOpts) => NodeJS.ReadableStream;
buffer: (url: string, opts?: MqlOptions, gotOpts?: GotOpts) => Promise<HTTPResponseRaw>;
}

declare const mql: Mql;
Expand Down
2 changes: 1 addition & 1 deletion src/node.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const mql = require('./factory')({
const mql = require('./factory')('buffer')({
MicrolinkError: require('whoops')('MicrolinkError'),
urlHttp: require('url-http/lightweight'),
got: require('got').extend({ headers: { 'user-agent': undefined } }),
Expand Down
49 changes: 33 additions & 16 deletions test/lightweight.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,28 @@ import type { MqlError } from '../lightweight'

/** mql */

mql('https://example.com', {
endpoint: 'https://pro.microlink.io',
apiKey: '123',
retry: 2,
cache: new Map()
})
;async () => {
const result = await mql('https://example.com', {
endpoint: 'https://pro.microlink.io',
apiKey: '123',
retry: 2,
cache: new Map()
})

console.log(result.status)
console.log(result.data)
console.log(result.statusCode)
console.log(result.headers)
console.log(result.response)
}

;(async () => {
const response = await mql('https://example.com', {
stream: true,
screenshot: true
})
console.log(response.body)
})()

/** data */

Expand All @@ -35,8 +51,7 @@ mql('https://github.com/microlinkhq', {
type: 'number'
},
stars: {
selector:
'.js-responsive-underlinenav a[data-tab-item="stars"] span',
selector: '.js-responsive-underlinenav a[data-tab-item="stars"] span',
type: 'number'
}
}
Expand Down Expand Up @@ -73,6 +88,7 @@ mql('https://example.com', {
screenshot: {
codeScheme: 'atom-dark',
type: 'png',
optimizeForSpeed: true,
overlay: {
background: '#000',
browser: 'light'
Expand All @@ -99,7 +115,6 @@ console.log(result.statusCode)
console.log(result.headers)

/** error */

;({
status: 'error',
data: { url: 'fetch failed' },
Expand All @@ -111,8 +126,7 @@ console.log(result.headers)
name: 'MicrolinkError',
message: 'EFATALCLIENT, fetch failed',
description: 'fetch failed'
}) as MqlError

} as MqlError)
;({
status: 'fail',
code: 'EAUTH',
Expand All @@ -128,7 +142,8 @@ console.log(result.headers)
'content-type': 'application/json',
date: 'Thu, 05 Oct 2023 11:02:35 GMT',
nel: '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}',
'report-to': '{"endpoints":[{"url":"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=6tEv%2Fk7XkC0so782muCCxAfbFeaMusFvyv839c8Xv74aKQFy1jD%2Fd8hRrldtfntrhuzCi5HG8W%2FlBxk1a9qKqxHObl79FhxBnK6pAOF6gGXc9Vi0wHnXb1hayCkTxolfpR7yoH89el9W34r1T8E%3D"}],"group":"cf-nel","max_age":604800}',
'report-to':
'{"endpoints":[{"url":"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=6tEv%2Fk7XkC0so782muCCxAfbFeaMusFvyv839c8Xv74aKQFy1jD%2Fd8hRrldtfntrhuzCi5HG8W%2FlBxk1a9qKqxHObl79FhxBnK6pAOF6gGXc9Vi0wHnXb1hayCkTxolfpR7yoH89el9W34r1T8E%3D"}],"group":"cf-nel","max_age":604800}',
server: 'cloudflare',
'transfer-encoding': 'chunked',
vary: 'Accept-Encoding',
Expand All @@ -141,17 +156,19 @@ console.log(result.headers)
'x-cache': 'Error from cloudfront'
},
name: 'MicrolinkError',
message: 'EAUTH, Invalid API key. Make sure you are attaching your API key as `x-api-key` header.',
description: 'Invalid API key. Make sure you are attaching your API key as `x-api-key` header.'
}) as MqlError
message:
'EAUTH, Invalid API key. Make sure you are attaching your API key as `x-api-key` header.',
description:
'Invalid API key. Make sure you are attaching your API key as `x-api-key` header.'
} as MqlError)

/* extend */

mql.extend({ responseType: 'text' })

/* stream */

mql.stream('https://example.com', { headers: { 'user-agent': 'foo' }})
mql.stream('https://example.com', { headers: { 'user-agent': 'foo' } })

/* arrraBuffer */

Expand Down
Loading

0 comments on commit 7b74413

Please sign in to comment.