cqi.api.APIClient = class APIClient { /** * @param {string} host * @param {string} corpusId * @param {number} [timeout=60] timeout * @param {string} [version=0.1] version */ constructor(host, corpus_id, timeout = 60, version = '0.1') { this.host = host; this.timeout = timeout * 1000; // convert seconds to milliseconds this.version = version; this.socket = io( this.host, { auth: {corpus_id: corpus_id}, transports: ['websocket'], upgrade: false } ); } /** * @param {string} fn_name * @param {object} [fn_args={}] * @returns {Promise} */ #request(fn_name, fn_args = {}) { return new Promise((resolve, reject) => { // this.socket.timeout(this.timeout).emit('cqi', {fn_name: fn_name, fn_args: fn_args}, (timeoutError, response) => { // if (timeoutError) { // reject(timeoutError); // } this.socket.emit('cqi', fn_name, fn_args, (response) => { if (response.code === 200) { resolve(response.payload); } else if (response.code === 500) { reject(new Error(`[${response.code}] ${response.msg}`)); } else if (response.code === 502) { reject(new cqi.errors.lookup[response.payload.code]()); } }); }); } /** * @param {string} username * @param {string} password * @returns {Promise} */ async ctrl_connect(username, password) { const fn_name = 'ctrl_connect'; const fn_args = {username: username, password: password}; let payload = await this.#request(fn_name, fn_args); return new cqi.status.lookup[payload.code](); } /** * @returns {Promise} */ async ctrl_bye() { const fn_name = 'ctrl_bye'; let payload = await this.#request(fn_name); return new cqi.status.lookup[payload.code](); } /** * @returns {Promise} */ async ctrl_user_abort() { const fn_name = 'ctrl_user_abort'; return await this.#request(fn_name); } /** * @returns {Promise} */ async ctrl_ping() { const fn_name = 'ctrl_ping'; let payload = await this.#request(fn_name); return new cqi.status.lookup[payload.code](); } /** * Full-text error message for the last general error reported * by the CQi server * * @returns {Promise} */ async ctrl_last_general_error() { const fn_name = 'ctrl_last_general_error'; return await this.#request(fn_name); } /** * @returns {Promise} */ async ask_feature_cqi_1_0() { const fn_name = 'ask_feature_cqi_1_0'; return await this.#request(fn_name); } /** * @returns {Promise} */ async ask_feature_cl_2_3() { const fn_name = 'ask_feature_cl_2_3'; return await this.#request(fn_name); } /** * @returns {Promise} */ async ask_feature_cqp_2_3() { const fn_name = 'ask_feature_cqp_2_3'; return await this.#request(fn_name); } /** * @returns {Promise} */ async corpus_list_corpora() { const fn_name = 'corpus_list_corpora'; return await this.#request(fn_name); } /** * @param {string} corpus * @returns {Promise} */ async corpus_charset(corpus) { const fn_name = 'corpus_charset'; const fn_args = {corpus: corpus}; return await this.#request(fn_name, fn_args); } /** * @param {string} corpus * @returns {Promise} */ async corpus_properties(corpus) { const fn_name = 'corpus_properties'; const fn_args = {corpus: corpus}; return await this.#request(fn_name, fn_args); } /** * @param {string} corpus * @returns {Promise} */ async corpus_positional_attributes(corpus) { const fn_name = 'corpus_positional_attributes'; const fn_args = {corpus: corpus}; return await this.#request(fn_name, fn_args); } /** * @param {string} corpus * @returns {Promise} */ async corpus_structural_attributes(corpus) { const fn_name = 'corpus_structural_attributes'; const fn_args = {corpus: corpus}; return await this.#request(fn_name, fn_args); } /** * @param {string} corpus * @param {string} attribute * @returns {Promise} */ async corpus_structural_attribute_has_values(corpus, attribute) { const fn_name = 'corpus_structural_attribute_has_values'; const fn_args = {corpus: corpus, attribute: attribute}; return await this.#request(fn_name, fn_args); } /** * @param {string} corpus * @returns {Promise} */ async corpus_alignment_attributes(corpus) { const fn_name = 'corpus_alignment_attributes'; const fn_args = {corpus: corpus}; return await this.#request(fn_name, fn_args); } /** * the full name of as specified in its registry entry * * @param {string} corpus * @returns {Promise} */ async corpus_full_name(corpus) { const fn_name = 'corpus_full_name'; const fn_args = {corpus: corpus}; return await this.#request(fn_name, fn_args); } /** * returns the contents of the .info file of as a list of lines * * @param {string} corpus * @returns {Promise} */ async corpus_info(corpus) { const fn_name = 'corpus_info'; const fn_args = {corpus: corpus}; return await this.#request(fn_name, fn_args); } /** * try to unload a corpus and all its attributes from memory * * @param {string} corpus * @returns {Promise} */ async corpus_drop_corpus(corpus) { const fn_name = 'corpus_drop_corpus'; const fn_args = {corpus: corpus}; let payload = await this.#request(fn_name, fn_args); return new cqi.status.lookup[payload.code](); } /** * returns the size of : * - number of tokens (positional) * - number of regions (structural) * - number of alignments (alignment) * * @param {string} attribute * @returns {Promise} */ async cl_attribute_size(attribute) { const fn_name = 'cl_attribute_size'; const fn_args = {attribute: attribute}; return await this.#request(fn_name, fn_args); } /** * returns the number of entries in the lexicon of a positional attribute; * * valid lexicon IDs range from 0 .. (lexicon_size - 1) * * @param {string} attribute * @returns {Promise} */ async cl_lexicon_size(attribute) { const fn_name = 'cl_lexicon_size'; const fn_args = {attribute: attribute}; return await this.#request(fn_name, fn_args); } /** * unload attribute from memory * * @param {string} attribute * @returns {Promise} */ async cl_drop_attribute(attribute) { const fn_name = 'cl_drop_attribute'; const fn_args = {attribute: attribute}; let payload = await this.#request(fn_name, fn_args); return new cqi.status.lookup[payload.code](); } /** * NOTE: simple (scalar) mappings are applied to lists (the returned list * has exactly the same length as the list passed as an argument) */ /** * returns -1 for every string in that is not found in the lexicon * * @param {string} attribute * @param {strings[]} string * @returns {Promise} */ async cl_str2id(attribute, strings) { const fn_name = 'cl_str2id'; const fn_args = {attribute: attribute, strings: strings}; return await this.#request(fn_name, fn_args); } /** * returns "" for every ID in that is out of range * * @param {string} attribute * @param {number[]} id * @returns {Promise} */ async cl_id2str(attribute, id) { const fn_name = 'cl_id2str'; const fn_args = {attribute: attribute, id: id}; return await this.#request(fn_name, fn_args); } /** * returns 0 for every ID in that is out of range * * @param {string} attribute * @param {number[]} id * @returns {Promise} */ async cl_id2freq(attribute, id) { const fn_name = 'cl_id2freq'; const fn_args = {attribute: attribute, id: id}; return await this.#request(fn_name, fn_args); } /** * returns -1 for every corpus position in that is out of range * * @param {string} attribute * @param {number[]} cpos * @returns {Promise} */ async cl_cpos2id(attribute, cpos) { const fn_name = 'cl_cpos2id'; const fn_args = {attribute: attribute, cpos: cpos}; return await this.#request(fn_name, fn_args); } /** * returns "" for every corpus position in that is out of range * * @param {string} attribute * @param {number[]} cpos * @returns {Promise} */ async cl_cpos2str(attribute, cpos) { const fn_name = 'cl_cpos2str'; const fn_args = {attribute: attribute, cpos: cpos}; return await this.#request(fn_name, fn_args); } /** * returns -1 for every corpus position not inside a structure region * * @param {string} attribute * @param {number[]} cpos * @returns {Promise} */ async cl_cpos2struc(attribute, cpos) { const fn_name = 'cl_cpos2struc'; const fn_args = {attribute: attribute, cpos: cpos}; return await this.#request(fn_name, fn_args); } /** * NOTE: temporary addition for the Euralex2000 tutorial, but should * probably be included in CQi specs */ /** * returns left boundary of s-attribute region enclosing cpos, * -1 if not in region * * @param {string} attribute * @param {number[]} cpos * @returns {Promise} */ async cl_cpos2lbound(attribute, cpos) { const fn_name = 'cl_cpos2lbound'; const fn_args = {attribute: attribute, cpos: cpos}; return await this.#request(fn_name, fn_args); } /** * returns right boundary of s-attribute region enclosing cpos, * -1 if not in region * * @param {string} attribute * @param {number[]} cpos * @returns {Promise} */ async cl_cpos2rbound(attribute, cpos) { const fn_name = 'cl_cpos2rbound'; const fn_args = {attribute: attribute, cpos: cpos}; return await this.#request(fn_name, fn_args); } /** * returns -1 for every corpus position not inside an alignment * * @param {string} attribute * @param {number[]} cpos * @returns {Promise} */ async cl_cpos2alg(attribute, cpos) { const fn_name = 'cl_cpos2alg'; const fn_args = {attribute: attribute, cpos: cpos}; return await this.#request(fn_name, fn_args); } /** * returns annotated string values of structure regions in ; * "" if out of range * * check corpus_structural_attribute_has_values() first * * @param {string} attribute * @param {number[]} strucs * @returns {Promise} */ async cl_struc2str(attribute, strucs) { const fn_name = 'cl_struc2str'; const fn_args = {attribute: attribute, strucs: strucs}; return await this.#request(fn_name, fn_args); } /** * NOTE: the following mappings take a single argument and return multiple * values, including lists of arbitrary size */ /** * returns all corpus positions where the given token occurs * * @param {string} attribute * @param {number} id * @returns {Promise} */ async cl_id2cpos(attribute, id) { const fn_name = 'cl_id2cpos'; const fn_args = {attribute: attribute, id: id}; return await this.#request(fn_name, fn_args); } /** * returns all corpus positions where one of the tokens in occurs; * the returned list is sorted as a whole, not per token id * * @param {string} attribute * @param {number[]} id_list * @returns {Promise} */ async cl_idlist2cpos(attribute, id_list) { const fn_name = 'cl_idlist2cpos'; const fn_args = {attribute: attribute, id_list: id_list}; return await this.#request(fn_name, fn_args); } /** * returns lexicon IDs of all tokens that match ; * the returned list may be empty (size 0); * * @param {string} attribute * @param {string} regex * @returns {Promise} */ async cl_regex2id(attribute, regex) { const fn_name = 'cl_regex2id'; const fn_args = {attribute: attribute, regex: regex}; return await this.#request(fn_name, fn_args); } /** * returns start and end corpus positions of structure region * * @param {string} attribute * @param {number} struc * @returns {Promise<[number, number]>} */ async cl_struc2cpos(attribute, struc) { const fn_name = 'cl_struc2cpos'; const fn_args = {attribute: attribute, struc: struc}; return await this.#request(fn_name, fn_args); } /** * returns (src_start, src_end, target_start, target_end) * * @param {string} attribute * @param {number} alg * @returns {Promise<[number, number, number, number]>} */ async alg2cpos(attribute, alg) { const fn_name = 'alg2cpos'; const fn_args = {attribute: attribute, alg: alg}; return await this.#request(fn_name, fn_args); } /** * must include the ';' character terminating the query. * * @param {string} mother_corpus * @param {string} subcorpus_name * @param {string} query * @returns {Promise} */ async cqp_query(mother_corpus, subcorpus_name, query) { const fn_name = 'cqp_query'; const fn_args = {mother_corpus: mother_corpus, subcorpus_name: subcorpus_name, query: query}; let payload = await this.#request(fn_name, fn_args); return new cqi.status.lookup[payload.code](); } /** * @param {string} corpus * @returns {Promise} */ async cqp_list_subcorpora(corpus) { const fn_name = 'cqp_list_subcorpora'; const fn_args = {corpus: corpus}; return await this.#request(fn_name, fn_args); } /** * @param {string} subcorpus * @returns {Promise} */ async cqp_subcorpus_size(subcorpus) { const fn_name = 'cqp_subcorpus_size'; const fn_args = {subcorpus: subcorpus}; return await this.#request(fn_name, fn_args); } /** * @param {string} subcorpus * @param {number} field * @returns {Promise} */ async cqp_subcorpus_has_field(subcorpus, field) { const fn_name = 'cqp_subcorpus_has_field'; const fn_args = {subcorpus: subcorpus, field: field}; return await this.#request(fn_name, fn_args); } /** * Dump the values of for match ranges .. * in . is one of the CQI_CONST_FIELD_* constants. * * @param {string} subcorpus * @param {number} field * @param {number} first * @param {number} last * @returns {Promise} */ async cqp_dump_subcorpus(subcorpus, field, first, last) { const fn_name = 'cqp_dump_subcorpus'; const fn_args = {subcorpus: subcorpus, field: field, first: first, last: last}; return await this.#request(fn_name, fn_args); } /** * delete a subcorpus from memory * * @param {string} subcorpus * @returns {Promise} */ async cqp_drop_subcorpus(subcorpus) { const fn_name = 'cqp_drop_subcorpus'; const fn_args = {subcorpus: subcorpus}; let payload = await this.#request(fn_name, fn_args); return new cqi.status.lookup[payload.code](); } /** * NOTE: The following two functions are temporarily included for the * Euralex 2000 tutorial demo */ /** * frequency distribution of single tokens * * returns (id, frequency) pairs flattened into a list of size 2* * field is one of * - CQI_CONST_FIELD_MATCH * - CQI_CONST_FIELD_TARGET * - CQI_CONST_FIELD_KEYWORD * * NB: pairs are sorted by frequency desc. * * @param {string} subcorpus * @param {number} cutoff * @param {number} field * @param {string} attribute * @returns {Promise} */ async cqp_fdist_1(subcorpus, cutoff, field, attribute) { const fn_name = 'cqp_fdist_1'; const fn_args = {subcorpus: subcorpus, cutoff: cutoff, field: field, attribute: attribute}; return await this.#request(fn_name, fn_args); } /** * frequency distribution of pairs of tokens * * returns (id1, id2, frequency) pairs flattened into a list of * size 3* * * NB: triples are sorted by frequency desc. * * @param {string} subcorpus * @param {number} cutoff * @param {number} field1 * @param {string} attribute1 * @param {number} field2 * @param {string} attribute2 * @returns {Promise} */ async cqp_fdist_2(subcorpus, cutoff, field1, attribute1, field2, attribute2) { const fn_name = 'cqp_fdist_2'; const fn_args = {subcorpus: subcorpus, cutoff: cutoff, field1: field1, attribute1: attribute1, field2: field2, attribute2: attribute2}; return await this.#request(fn_name, fn_args); } /************************************************************************** * NOTE: The following is not included in the CQi specification. * **************************************************************************/ /************************************************************************** * Custom additions for nopaque * **************************************************************************/ /** * @param {string} corpus * @returns {Promise} */ async ext_corpus_update_db(corpus) { const fn_name = 'ext_corpus_update_db'; const fn_args = {corpus: corpus}; let payload = await this.#request(fn_name, fn_args); return new cqi.status.lookup[payload.code](); } /** * @param {string} corpus * @returns {Promise} */ async ext_corpus_static_data(corpus) { const fn_name = 'ext_corpus_static_data'; const fn_args = {corpus: corpus}; let compressedEncodedData = await this.#request(fn_name, fn_args); let encodedData = pako.inflate(compressedEncodedData); let textDecoder = new TextDecoder(); let data = textDecoder.decode(encodedData); return JSON.parse(data); } /** * @param {string} corpus * @param {number=} page * @param {number=} per_page * @returns {Promise} */ async ext_corpus_paginate_corpus(corpus, page, per_page) { const fn_name = 'ext_corpus_paginate_corpus'; const fn_args = {corpus: corpus} if (page !== undefined) {fn_args.page = page;} if (per_page !== undefined) {fn_args.per_page = per_page;} return await this.#request(fn_name, fn_args); } /** * @param {string} subcorpus * @param {number=} context * @param {number=} page * @param {number=} per_page * @returns {Promise} */ async ext_cqp_paginate_subcorpus(subcorpus, context, page, per_page) { const fn_name = 'ext_cqp_paginate_subcorpus'; const fn_args = {subcorpus: subcorpus} if (context !== undefined) {fn_args.context = context;} if (page !== undefined) {fn_args.page = page;} if (per_page !== undefined) {fn_args.per_page = per_page;} return await this.#request(fn_name, fn_args); } /** * @param {string} subcorpus * @param {number[]} match_id_list * @param {number=} context * @returns {Promise} */ async ext_cqp_partial_export_subcorpus(subcorpus, match_id_list, context) { const fn_name = 'ext_cqp_partial_export_subcorpus'; const fn_args = {subcorpus: subcorpus, match_id_list: match_id_list}; if (context !== undefined) {fn_args.context = context;} return await this.#request(fn_name, fn_args); } /** * @param {string} subcorpus * @param {number=} context * @returns {Promise} */ async ext_cqp_export_subcorpus(subcorpus, context) { const fn_name = 'ext_cqp_export_subcorpus'; const fn_args = {subcorpus: subcorpus}; if (context !== undefined) {fn_args.context = context;} return await this.#request(fn_name, fn_args); } };