Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module.exports = {
"env": {
"es2021": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"plugins": ["jest", "node", "@typescript-eslint"],
"extends": [
"eslint:recommended",
"prettier",
"plugin:node/recommended",
"plugin:jest/all"
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"settings": {
"node": {
"tryExtensions": [".ts", ".js", ".json", ".node"]
}
},
"rules": {
"jest/no-untyped-mock-factory": "off",
"jest/unbound-method": "off",
"node/no-extraneous-import": "off",
"node/no-unsupported-features/es-syntax": [
"error",
{ ignores: ["modules"] }
],
"jest/prefer-expect-assertions": "off",
"jest/expect-expect": "off",
"jest/no-hooks": "off",
"jest/prefer-inline-snapshots": "off"
}
}
28 changes: 0 additions & 28 deletions .eslintrc.json

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ dist

# TernJS port file
.tern-port

# emacs
*~
24 changes: 24 additions & 0 deletions dist/constants.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export declare const PREFIXES: {
ALL: string;
TI: string;
AU: string;
ABS: string;
CO: string;
JR: string;
CAT: string;
RN: string;
};
export declare const SEPARATORS: {
AND: string;
OR: string;
ANDNOT: string;
};
export declare const SORT_BY: {
RELEVANCE: string;
LAST_UPDATED_DATE: string;
SUBMITTED_DATE: string;
};
export declare const SORT_ORDER: {
ASCENDING: string;
DESCENDING: string;
};
27 changes: 27 additions & 0 deletions dist/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SORT_ORDER = exports.SORT_BY = exports.SEPARATORS = exports.PREFIXES = void 0;
exports.PREFIXES = {
ALL: 'all',
TI: 'ti', // Title
AU: 'au', // Author
ABS: 'abs', // Abstract
CO: 'co', // Comment
JR: 'jr', // Journal Reference
CAT: 'cat', // Subject Category
RN: 'rn', // Report Number
};
exports.SEPARATORS = {
AND: '+AND+',
OR: '+OR+',
ANDNOT: '+ANDNOT+',
};
exports.SORT_BY = {
RELEVANCE: 'relevance',
LAST_UPDATED_DATE: 'lastUpdatedDate',
SUBMITTED_DATE: 'submittedDate',
};
exports.SORT_ORDER = {
ASCENDING: 'ascending',
DESCENDING: 'descending',
};
37 changes: 37 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
type TagData = {
name: string;
prefix?: string;
};
type SearchQueryParams = {
include: TagData[];
exclude?: TagData[];
};
type SearchApiType = {
searchQueryParams: SearchQueryParams[];
sortBy?: string;
sortOrder?: string;
start?: number;
maxResults?: number;
};
/**
* Fetch data from arXiv API
* @async
* @param {{searchQueryParams: Array.<{include: Array, exclude: Array}>, start: number, maxResults: number}} args
* @param {Array} searchQueryParams - array of search query.
* @param {string} sortBy - can be "relevance", "lastUpdatedDate", "submittedDate".
* @param {string} sortOrder - can be either "ascending" or "descending".
* @param {number} start - the index of the first returned result.
* @param {number} maxResults - the number of results returned by the query.
* @returns {Promise}
*/
declare function search({ searchQueryParams, sortBy, sortOrder, start, maxResults }: SearchApiType): Promise<{
id: string;
title: string;
summary: string;
authors: any[];
links: any[];
published: string;
updated: string;
categories: any[];
}[]>;
export default search;
113 changes: 113 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = __importDefault(require("axios"));
const _ = __importStar(require("lodash"));
const constants_1 = require("./constants");
const util_1 = __importDefault(require("util"));
const xml2js_1 = require("xml2js");
const parseStringPromisified = util_1.default.promisify(xml2js_1.parseString);
const get_arxiv_url = ({ searchQuery, sortBy, sortOrder, start, maxResults }) => {
return `http://export.arxiv.org/api/query?search_query=${searchQuery}&start=${start}&max_results=${maxResults}${sortBy ? `&sortBy=${sortBy}` : ''}${sortOrder ? `&sortOrder=${sortOrder}` : ''}`;
};
/**
* Parse arXiv entry object.
* @param {Object} entry.
* @returns {Object} formatted arXiv entry object.
*/
function parseArxivObject(entry) {
return {
id: _.get(entry, 'id[0]', ''),
title: _.get(entry, 'title[0]', ''),
summary: _.get(entry, 'summary[0]', '').trim(),
authors: _.get(entry, 'author', []).map((author) => author.name),
links: _.get(entry, 'link', []).map((link) => link.$),
published: _.get(entry, 'published[0]', ''),
updated: _.get(entry, 'updated[0]', ''),
categories: _.get(entry, 'category', []).map((category) => category.$),
};
}
/**
* Parse a tag to a query string.
* @param {{name: string, prefix: string}} tag
* @param {string} name - the name of the tag - mandatory.
* @param {string} prefix - one of PREFIXES - default to ALL.
* @returns {string} query string of a tag.
*/
function parseTag({ name, prefix = constants_1.PREFIXES.ALL }) {
if (!_.isString(name) || _.isEmpty(name)) {
throw new Error('you must specify tag name');
}
if (!Object.values(constants_1.PREFIXES).includes(prefix)) {
throw new Error(`unsupported prefix: ${prefix}`);
}
return `${prefix}:${name}`;
}
/**
* Parse include tags and exclude tags to a query string.
* @param {Array.<{include: Array, exclude: Array}>} tags
* @returns {string} query string between tags.
*/
function parseTags({ include, exclude = [] }) {
if (!Array.isArray(include) || !Array.isArray(exclude)) {
throw new Error('include and exclude must be arrays');
}
if (include.length === 0) {
throw new Error('include is a mandatory field');
}
return `${include.map(parseTag).join(constants_1.SEPARATORS.AND)}${exclude.length > 0 ? constants_1.SEPARATORS.ANDNOT : ''}${exclude
.map(parseTag)
.join(constants_1.SEPARATORS.ANDNOT)}`;
}
/**
* Fetch data from arXiv API
* @async
* @param {{searchQueryParams: Array.<{include: Array, exclude: Array}>, start: number, maxResults: number}} args
* @param {Array} searchQueryParams - array of search query.
* @param {string} sortBy - can be "relevance", "lastUpdatedDate", "submittedDate".
* @param {string} sortOrder - can be either "ascending" or "descending".
* @param {number} start - the index of the first returned result.
* @param {number} maxResults - the number of results returned by the query.
* @returns {Promise}
*/
async function search({ searchQueryParams, sortBy, sortOrder, start = 0, maxResults = 10 }) {
if (!Array.isArray(searchQueryParams)) {
throw new Error('query param must be an array');
}
if (sortBy && !Object.values(constants_1.SORT_BY).includes(sortBy)) {
throw new Error(`unsupported sort by option. should be one of: ${Object.values(constants_1.SORT_BY).join(' ')}`);
}
if (sortOrder && !Object.values(constants_1.SORT_ORDER).includes(sortOrder)) {
throw new Error(`unsupported sort order option. should be one of: ${Object.values(constants_1.SORT_ORDER).join(' ')}`);
}
const searchQuery = searchQueryParams.map(parseTags).join(constants_1.SEPARATORS.OR);
const response = await axios_1.default.get(get_arxiv_url({ searchQuery, sortBy, sortOrder, start, maxResults }));
const parsedData = await parseStringPromisified(response.data);
return _.get(parsedData, 'feed.entry', []).map(parseArxivObject);
}
exports.default = search;
1 change: 1 addition & 0 deletions dist/index.test.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
Loading