<commit_before>/* tslint:disable:max-file-line-count */
import {
	decorateCtorWithPotionInstance,
	decorateCtorWithPotionURI,
	potionPromise,
	readonly
} from './metadata';
import {Item, ItemOptions} from './item';
import {Pagination, PaginationOptions} from './pagination';
import {
	entries,
	isArray,
	mapToObject,
	MemCache,
	omap,
	toCamelCase,
	toSnakeCase
} from '../utils';


/**
 * Item cache.
 * Dictates the implementation of the item cache.
 */
export interface ItemCache<T extends Item> {
	has(key: string): boolean;
	get(key: string): Promise<T>;
	put(key: string, item: Promise<T>): Promise<T>;
	remove(key: string): void;
}


/**
 * Common interfaces.
 */

export interface ParsedURI {
	uri: string;
	resource: typeof Item;
	params: string[];
}

export interface URLSearchParams {
	[key: string]: any;
}

export interface RequestOptions {
	method?: string;
	search?: URLSearchParams | null;
	data?: any;
	cache?: boolean;
}

export interface FetchOptions extends RequestOptions {
	paginate?: boolean;
}

export interface QueryOptions extends PaginationOptions {
	where?: any;
	sort?: any;
}

export interface PotionResponse {
	data: any;
	headers: any;
}


export interface PotionOptions {
	host?: string;
	prefix?: string;
	cache?: ItemCache<Item>;
}


/**
 * This class contains the main logic for interacting with the Flask Potion backend.
 * Note that this class does not contain the logic for making the HTTP requests,
 * it is up to the child class to implement the logic for that through the `request` method.
 * Furthermore, the child class also needs to provide the Promise class/fn as this class is set to use the native Promise only available from ES6.
 *
 * @example
 * class Potion extends PotionBase {
 *     protected request(uri, options?: RequestOptions): Promise<any> {
 *         // Here we need to implement the actual HTTP request
 *     };
 * }
 */
export abstract class PotionBase {
	readonly resources: {[key: string]: Item} = {};
	readonly cache: ItemCache<Item>;
	host: string;
	readonly prefix: string;

	private readonly Promise: typeof Promise = potionPromise(this); // NOTE: This is needed only to provide support for AngularJS.
	private pendingGETRequests: Map<string, any> = new Map();

	constructor({host = '', prefix = '', cache}: PotionOptions = {}) {
		this.cache = cache || new MemCache();
		this.host = host;
		this.prefix = prefix;
	}

	fetch(uri: string, fetchOptions?: FetchOptions, pagination?: Pagination<any>): Promise<Item | Item[] | Pagination<Item> | any> {
		const options: FetchOptions = {...fetchOptions};
		const {method, cache, paginate, data} = options;
		let {search} = options;
		const key = uri;
		const {Promise} = this;

		// Add the API prefix if not present
		const {prefix} = this;
		if (uri.indexOf(prefix) === -1) {
			uri = `${prefix}${uri}`;
		}

		if (paginate) {
			// If no page was provided set to first
			// Default to 25 items per page
			search = options.search = {page: 1, perPage: 25, ...search};
		}

		// Convert the {data, search} object props to snake case.
		// Serialize all values to Potion JSON.
		const fetch = () => this.request(`${this.host}${uri}`, {...options, ...{
				search: this.toPotionJSON(search),
				data: this.toPotionJSON(data)
			}})
			// Convert the data to Potion JSON
			.then(response => this.deserialize(response))
			.then(({headers, data}) => {
				// Return or update Pagination
				if (paginate) {
					const count = headers['x-total-count'] || data.length;
					if (!pagination) {
						return new Pagination<Item>({uri, potion: this}, data, count, options);
					} else {
						return pagination.update(data, count);
					}
				}
				return data;
			});

		if (method === 'GET' && !search) {
			// If a GET request was made and {cache: true},
			// try to get item from cache and return a resolved promise with the cached item.
			// NOTE: Queries are not cached.<commit_msg>chore: add some TODOs<commit_after>/* tslint:disable:max-file-line-count */
import {
	decorateCtorWithPotionInstance,
	decorateCtorWithPotionURI,
	potionPromise,
	readonly
} from './metadata';
import {Item, ItemOptions} from './item';
import {Pagination, PaginationOptions} from './pagination';
import {
	entries,
	isArray,
	mapToObject,
	MemCache,
	omap,
	toCamelCase,
	toSnakeCase
} from '../utils';


/**
 * Item cache.
 * Dictates the implementation of the item cache.
 */
export interface ItemCache<T extends Item> {
	has(key: string): boolean;
	get(key: string): Promise<T>;
	put(key: string, item: Promise<T>): Promise<T>;
	remove(key: string): void;
}


/**
 * Common interfaces.
 */

export interface ParsedURI {
	uri: string;
	resource: typeof Item;
	params: string[];
}

// TODO: Start using a more standard impl. of these interfaces (either create proper classes for some or use the native Request, etc.)
export interface URLSearchParams {
	[key: string]: any;
}

export interface RequestOptions {
	method?: string;
	search?: URLSearchParams | null;
	data?: any;
	cache?: boolean;
}

export interface FetchOptions extends RequestOptions {
	paginate?: boolean;
}

export interface QueryOptions extends PaginationOptions {
	where?: any;
	sort?: any;
}

export interface PotionResponse {
	data: any;
	headers: any;
}


export interface PotionOptions {
	host?: string;
	prefix?: string;
	cache?: ItemCache<Item>;
}


/**
 * This class contains the main logic for interacting with the Flask Potion backend.
 * Note that this class does not contain the logic for making the HTTP requests,
 * it is up to the child class to implement the logic for that through the `request` method.
 * Furthermore, the child class also needs to provide the Promise class/fn as this class is set to use the native Promise only available from ES6.
 *
 * @example
 * class Potion extends PotionBase {
 *     protected request(uri, options?: RequestOptions): Promise<any> {
 *         // Here we need to implement the actual HTTP request
 *     };
 * }
 */
export abstract class PotionBase {
	readonly resources: {[key: string]: Item} = {};
	readonly cache: ItemCache<Item>;
	host: string;
	readonly prefix: string;

	private readonly Promise: typeof Promise = potionPromise(this); // NOTE: This is needed only to provide support for AngularJS.
	private pendingGETRequests: Map<string, any> = new Map();

	constructor({host = '', prefix = '', cache}: PotionOptions = {}) {
		this.cache = cache || new MemCache();
		this.host = host;
		this.prefix = prefix;
	}

	fetch(uri: string, fetchOptions?: FetchOptions, pagination?: Pagination<any>): Promise<Item | Item[] | Pagination<Item> | any> {
		const options: FetchOptions = {...fetchOptions};
		const {method, cache, paginate, data} = options;
		let {search} = options;
		const key = uri;
		const {Promise} = this;

		// Add the API prefix if not present
		const {prefix} = this;
		if (uri.indexOf(prefix) === -1) {
			uri = `${prefix}${uri}`;
		}

		if (paginate) {
			// If no page was provided set to first
			// Default to 25 items per page
			search = options.search = {page: 1, perPage: 25, ...search};
		}

		// Convert the {data, search} object props to snake case.
		// Serialize all values to Potion JSON.
		const fetch = () => this.request(`${this.host}${uri}`, {...options, ...{
				search: this.toPotionJSON(search),
				data: this.toPotionJSON(data)
			}})
			// Convert the data to Potion JSON
			.then(response => this.deserialize(response))
			.then(({headers, data}) => {
				// Return or update Pagination
				// TODO: Refactor this, looks messy (pagination logic should be handled in the Pagination class)
				if (paginate) {
					const count = headers['x-total-count'] || data.length;
					if (!pagination) {
						return new Pagination<Item>({uri, potion: this}, data, count, options);
					} else {
						return pagination.update(data, count);
					}
				}
				return data;
			});

		if (method === 'GET' && !search) {
			// If a GET request was made and {cache: true},
			// try to get item from cache and return a resolved promise with the cached item.
			// NOTE: Queries are not cached.
{'fraction_non_alphanumeric': 0.12683229813664595, 'fraction_numerical': 0.0017391304347826088, 'mean_word_length': 3.6699535962877032, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '612861', 'n_tokens_mistral': 2640, 'n_tokens_neox': 2418, 'n_words': 872}