Aneurin Barker Snook 2 lat temu
rodzic
commit
69039945d6
2 zmienionych plików z 422 dodań i 0 usunięć
  1. 160 0
      dist/lib/index.d.ts
  2. 262 0
      dist/lib/index.js

+ 160 - 0
dist/lib/index.d.ts

@@ -0,0 +1,160 @@
+import { Document } from 'arangojs/documents';
+import { DocumentCollection } from 'arangojs/collection';
+import { GeneratedAqlQuery } from 'arangojs/aql';
+import { Database } from 'arangojs';
+/**
+ * A `CountFn` function returns the number of documents in a single collection matching search `terms` given.
+ *
+ * `count()` provides the standard implementation.
+ */
+export declare type CountFn<T extends Searchable, S extends Searchable = T> = (terms?: Terms<Document<DeepNonNullable<S>>>) => Promise<number>;
+/**
+ * Recursively renders all of a complex object's properties required, non-null, and non-undefined.
+ *
+ * Note: this type recurses through objects only, not arrays.
+ */
+export declare type DeepNonNullable<T> = NonNullable<T> extends object ? {
+    [P in keyof T]-?: DeepNonNullable<T[P]>;
+} : T;
+/** Query sort direction. */
+export declare type Direction = 'ASC' | 'DESC';
+/**
+ * Simple search filter type in which any number of conditions can be specified for a presumed parameter.
+ *
+ * Multiple operators can be used in combination.
+ * By default they will be combined into an `AND` filter.
+ * Specify `mode: "OR"` to switch to an `OR` filter.
+ *
+ * Note: `LIKE`, `NOT LIKE`, and regular expression operators are only available if `T` extends `string`.
+ */
+export declare type Filter<T> = {
+    mode?: 'AND' | 'OR';
+    eq?: T | null;
+    gt?: T;
+    gte?: T;
+    in?: T[];
+    like?: T & string;
+    lt?: T;
+    lte?: T;
+    neq?: T | null;
+    nin?: T[];
+    nlike?: T & string;
+    nreg?: T & string;
+    reg?: T & string;
+};
+/**
+ * A `FindFn` function returns the first document in a single collection matching search `terms` given.
+ * A `sort` can be specified to control the result.
+ *
+ * `find()` provides the standard implementation.
+ */
+export declare type FindFn<T extends Searchable, S extends Searchable = T> = (terms?: Terms<Document<DeepNonNullable<S>>>, sort?: Sort<Document<T>>[] | Sort<Document<T>>) => Promise<Document<T> | undefined>;
+/**
+ * Query limit.
+ * Always a tuple, but the second value can be omitted.
+ */
+export declare type Limit = [number, number?];
+/**
+ * Searchable data type.
+ * Essentially, any object is valid.
+ */
+export declare type Searchable = Record<string, unknown>;
+/**
+ * Formulate search terms based on a complex data type.
+ * Nested object types are preserved, while scalar properties are converted to `Filter` representations.
+ */
+export declare type Terms<T> = {
+    [P in keyof T]?: NonNullable<NonNullable<T[P]> extends object ? Terms<T[P]> : Filter<T[P]>>;
+};
+/**
+ * A `SearchFn` function matches documents in a single collection and returns a `SearchResult` based on the given
+ * `terms`, `limit`, and `sort`.
+ */
+export declare type SearchFn<T extends Searchable, S extends Searchable = T> = (terms?: Terms<Document<DeepNonNullable<S>>>, limit?: Limit, sort?: Sort<Document<T>>[] | Sort<Document<T>>) => Promise<SearchResult<T>>;
+/**
+ * Search results are a tuple of three values:
+ *   1. The **total** number of matching documents in the searched collection, ignoring limit
+ *   2. The documents matched within the searched collection, respecting limit
+ *   3. The AQL query object for the latter (for debugging purposes)
+ */
+export declare type SearchResult<T extends Searchable> = [number, Document<T>[], GeneratedAqlQuery];
+/** Query sort order. */
+export declare type Sort<T> = [keyof T, Direction];
+/** Format scalar or scalar array data for use in AQL. */
+export declare const formatData: <T>(data: T | T[]) => string;
+/** Format scalar data for use in AQL. */
+export declare const formatValue: <T>(data: T) => string;
+/** Map of search operator properties to AQL equivalents. */
+export declare const operatorMap: Record<keyof Omit<Filter<unknown>, 'mode'>, string>;
+/** Search operators. */
+export declare const operators: string[];
+/**
+ * Parse a search limit to a string AQL limit.
+ *
+ * Note: `LIMIT` is not prepended.
+ */
+export declare const parseLimit: (l: Limit) => string | number;
+/**
+ * Parse a search filter to a string of AQL filters.
+ *
+ * Note: `FILTER` is not prepended.
+ */
+export declare const parseFilter: <T>(param: string, search: Filter<T>) => string;
+/**
+ * Parse query sort(s) to an array of string AQL sorts.
+ *
+ * Note: `SORT` is not prepended.
+ */
+export declare const parseSort: <T>(s: Sort<T> | Sort<T>[], parent: string) => string[];
+/**
+ * Parse search terms to a flat array of search filters.
+ * The `parent` argument refers to the current document, and is prefixed to each filter.
+ */
+export declare const parseTerms: <T>(s: Terms<T>, parent: string) => string[];
+/**
+ * Build and execute a count query that matches documents in a single collection.
+ * Returns the total number of matches.
+ *
+ * This example resembles the generated AQL query:
+ *
+ * ```aql
+ * FOR {i} IN {c} {FILTER ...} COLLECT WITH COUNT INTO {n} RETURN {n}
+ * ```
+ */
+export declare const count: <T extends Searchable, S extends Searchable = T>(db: Database, c: DocumentCollection<T>, i?: string, n?: string) => CountFn<T, S>;
+/**
+ * Build and execute a find query that returns the first matching document in a single collection.
+ *
+ * This example resembles the generated AQL query:
+ *
+ * ```aql
+ * FOR {i} IN {collection} {FILTER ...} {SORT ...} LIMIT 1 RETURN {i}
+ * ```
+ */
+export declare const find: <T extends Searchable, S extends Searchable = T>(db: Database, c: DocumentCollection<T>, i?: string) => FindFn<T, S>;
+/**
+ * Build and execute a search query across a single collection.
+ * Returns a `SearchResult` tuple containing the total number of matches (ignoring limit), all matching documents
+ * (respecting limit), and the AQL query.
+ *
+ * This example resembles the generated AQL query:
+ *
+ * ```aql
+ * FOR {i} IN {collection} {FILTER ...} {SORT ...} {LIMIT ...} RETURN {i}
+ * ```
+ */
+export declare const search: <T extends Searchable, S extends Searchable = T>(db: Database, c: DocumentCollection<T>, i?: string, n?: string) => SearchFn<T, S>;
+declare const _default: {
+    count: <T extends Searchable, S extends Searchable = T>(db: Database, c: DocumentCollection<T>, i?: string, n?: string) => CountFn<T, S>;
+    find: <T_1 extends Searchable, S_1 extends Searchable = T_1>(db: Database, c: DocumentCollection<T_1>, i?: string) => FindFn<T_1, S_1>;
+    formatData: <T_2>(data: T_2 | T_2[]) => string;
+    formatValue: <T_3>(data: T_3) => string;
+    operatorMap: Record<"eq" | "gt" | "gte" | "in" | "like" | "lt" | "lte" | "neq" | "nin" | "nlike" | "nreg" | "reg", string>;
+    operators: string[];
+    parseFilter: <T_4>(param: string, search: Filter<T_4>) => string;
+    parseLimit: (l: Limit) => string | number;
+    parseSort: <T_5>(s: Sort<T_5> | Sort<T_5>[], parent: string) => string[];
+    parseTerms: <T_6>(s: Terms<T_6>, parent: string) => string[];
+    search: <T_7 extends Searchable, S_2 extends Searchable = T_7>(db: Database, c: DocumentCollection<T_7>, i?: string, n?: string) => SearchFn<T_7, S_2>;
+};
+export default _default;

+ 262 - 0
dist/lib/index.js

@@ -0,0 +1,262 @@
+"use strict";
+var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
+    if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
+    return cooked;
+};
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+exports.__esModule = true;
+exports.search = exports.find = exports.count = exports.parseTerms = exports.parseSort = exports.parseFilter = exports.parseLimit = exports.operators = exports.operatorMap = exports.formatValue = exports.formatData = void 0;
+var arangojs_1 = require("arangojs");
+/** Format scalar or scalar array data for use in AQL. */
+var formatData = function (data) {
+    return data instanceof Array ? "[".concat(data.map(exports.formatValue).join(','), "]") : (0, exports.formatValue)(data);
+};
+exports.formatData = formatData;
+/** Format scalar data for use in AQL. */
+var formatValue = function (data) {
+    if (typeof data === 'string')
+        return "\"".concat(data, "\"");
+    if (data === null)
+        return 'null';
+    return "".concat(data);
+};
+exports.formatValue = formatValue;
+/** Map of search operator properties to AQL equivalents. */
+exports.operatorMap = {
+    eq: '==',
+    gt: '>',
+    gte: '>=',
+    "in": 'IN',
+    like: 'LIKE',
+    lt: '<',
+    lte: '<=',
+    neq: '!=',
+    nin: 'NOT IN',
+    nlike: 'NOT LIKE',
+    nreg: '!~',
+    reg: '=~'
+};
+/** Search operators. */
+exports.operators = Object.keys(exports.operatorMap);
+/**
+ * Parse a search limit to a string AQL limit.
+ *
+ * Note: `LIMIT` is not prepended.
+ */
+var parseLimit = function (l) { return l.length > 1 ? "".concat(l[0], ", ").concat(l[1]) : l[0]; };
+exports.parseLimit = parseLimit;
+/**
+ * Parse a search filter to a string of AQL filters.
+ *
+ * Note: `FILTER` is not prepended.
+ */
+var parseFilter = function (param, search) { return parseFilterOps(search)
+    .map(function (_a) {
+    var op = _a[0], data = _a[1];
+    return "".concat(param, " ").concat(op, " ").concat((0, exports.formatData)(data));
+})
+    .join(" ".concat(search.mode || 'AND', " ")); };
+exports.parseFilter = parseFilter;
+/** Parse search parameter object to FILTER statement(s). */
+var parseFilterOps = function (search) {
+    return Object.keys(search).map(function (key) {
+        if (key === 'mode' || search[key] === undefined)
+            return undefined;
+        if (exports.operatorMap[key] === undefined)
+            throw new Error('unrecognised search operator');
+        return [exports.operatorMap[key], search[key]];
+    }).filter(Boolean);
+};
+/**
+ * Parse query sort(s) to an array of string AQL sorts.
+ *
+ * Note: `SORT` is not prepended.
+ */
+var parseSort = function (s, parent) {
+    if (s[0] instanceof Array)
+        return s.map(function (ss) { return "".concat(parent, ".").concat(String(ss[0]), " ").concat(ss[1]); });
+    return ["".concat(parent, ".").concat(String(s[0]), " ").concat(s[1])];
+};
+exports.parseSort = parseSort;
+/**
+ * Parse search terms to a flat array of search filters.
+ * The `parent` argument refers to the current document, and is prefixed to each filter.
+ */
+var parseTerms = function (s, parent) { return Object.keys(s)
+    .reduce(function (filters, param) {
+    var f = s[param];
+    if (!f)
+        return filters;
+    if (Object.keys(f).find(function (k) { return k !== 'mode' && !exports.operators.includes(k); })) {
+        // object is nested
+        filters.push.apply(filters, (0, exports.parseTerms)(f, "".concat(parent, ".").concat(String(param))));
+    }
+    else {
+        // object resembles a search parameter
+        filters.push((0, exports.parseFilter)("".concat(parent, ".").concat(String(param)), f));
+    }
+    return filters;
+}, []); };
+exports.parseTerms = parseTerms;
+/**
+ * Build and execute a count query that matches documents in a single collection.
+ * Returns the total number of matches.
+ *
+ * This example resembles the generated AQL query:
+ *
+ * ```aql
+ * FOR {i} IN {c} {FILTER ...} COLLECT WITH COUNT INTO {n} RETURN {n}
+ * ```
+ */
+var count = function (db, c, i, n) {
+    if (i === void 0) { i = 'i'; }
+    if (n === void 0) { n = 'n'; }
+    return function (terms) { return __awaiter(void 0, void 0, void 0, function () {
+        var filters, filterStr, l, countQuery;
+        return __generator(this, function (_a) {
+            switch (_a.label) {
+                case 0:
+                    filters = terms && (0, exports.parseTerms)(terms, i);
+                    filterStr = arangojs_1.aql.literal(filters ? filters.map(function (f) { return "FILTER ".concat(f); }).join(' ') : '');
+                    l = { i: arangojs_1.aql.literal(i), n: arangojs_1.aql.literal(n) };
+                    countQuery = (0, arangojs_1.aql)(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n      FOR ", " IN ", "\n        ", "\n        COLLECT WITH COUNT INTO ", "\n        RETURN ", "\n    "], ["\n      FOR ", " IN ", "\n        ", "\n        COLLECT WITH COUNT INTO ", "\n        RETURN ", "\n    "])), l.i, c, filterStr, l.n, l.n);
+                    return [4 /*yield*/, db.query(countQuery)];
+                case 1: return [4 /*yield*/, (_a.sent()).next()];
+                case 2: return [2 /*return*/, _a.sent()];
+            }
+        });
+    }); };
+};
+exports.count = count;
+/**
+ * Build and execute a find query that returns the first matching document in a single collection.
+ *
+ * This example resembles the generated AQL query:
+ *
+ * ```aql
+ * FOR {i} IN {collection} {FILTER ...} {SORT ...} LIMIT 1 RETURN {i}
+ * ```
+ */
+var find = function (db, c, i) {
+    if (i === void 0) { i = 'i'; }
+    return function (terms, sort) {
+        if (sort === void 0) { sort = ['_key', 'ASC']; }
+        return __awaiter(void 0, void 0, void 0, function () {
+            var filters, filterStr, sortStr, l, query, data;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        filters = terms && (0, exports.parseTerms)(terms, 'i');
+                        filterStr = arangojs_1.aql.literal(filters ? filters.map(function (f) { return "FILTER ".concat(f); }).join(' ') : '');
+                        sortStr = arangojs_1.aql.literal(sort ? "SORT ".concat((0, exports.parseSort)(sort, 'i').join(', ')) : '');
+                        l = { i: arangojs_1.aql.literal(i) };
+                        query = (0, arangojs_1.aql)(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n      FOR ", " IN ", "\n        ", "\n        ", "\n        LIMIT 1\n        RETURN ", "\n    "], ["\n      FOR ", " IN ", "\n        ", "\n        ", "\n        LIMIT 1\n        RETURN ", "\n    "])), l.i, c, filterStr, sortStr, l.i);
+                        return [4 /*yield*/, db.query(query)];
+                    case 1: return [4 /*yield*/, (_a.sent()).next()];
+                    case 2:
+                        data = _a.sent();
+                        return [2 /*return*/, data];
+                }
+            });
+        });
+    };
+};
+exports.find = find;
+/**
+ * Build and execute a search query across a single collection.
+ * Returns a `SearchResult` tuple containing the total number of matches (ignoring limit), all matching documents
+ * (respecting limit), and the AQL query.
+ *
+ * This example resembles the generated AQL query:
+ *
+ * ```aql
+ * FOR {i} IN {collection} {FILTER ...} {SORT ...} {LIMIT ...} RETURN {i}
+ * ```
+ */
+var search = function (db, c, i, n) {
+    if (i === void 0) { i = 'i'; }
+    if (n === void 0) { n = 'n'; }
+    return function (terms, limit, sort) {
+        if (sort === void 0) { sort = ['_rev', 'ASC']; }
+        return __awaiter(void 0, void 0, void 0, function () {
+            var filters, filterStr, limitStr, sortStr, l, count, countQuery, query, data;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        filters = terms && (0, exports.parseTerms)(terms, 'i');
+                        filterStr = arangojs_1.aql.literal(filters ? filters.map(function (f) { return "FILTER ".concat(f); }).join(' ') : '');
+                        limitStr = arangojs_1.aql.literal(limit ? "LIMIT ".concat((0, exports.parseLimit)(limit)) : '');
+                        sortStr = arangojs_1.aql.literal(sort ? "SORT ".concat((0, exports.parseSort)(sort, 'i').join(', ')) : '');
+                        l = { i: arangojs_1.aql.literal(i), n: arangojs_1.aql.literal(n) };
+                        count = 0;
+                        if (!limit) return [3 /*break*/, 3];
+                        countQuery = (0, arangojs_1.aql)(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n        FOR ", " IN ", "\n          ", "\n          COLLECT WITH COUNT INTO ", "\n          RETURN ", "\n      "], ["\n        FOR ", " IN ", "\n          ", "\n          COLLECT WITH COUNT INTO ", "\n          RETURN ", "\n      "])), l.i, c, filterStr, l.n, l.n);
+                        return [4 /*yield*/, db.query(countQuery)];
+                    case 1: return [4 /*yield*/, (_a.sent()).next()];
+                    case 2:
+                        count = _a.sent();
+                        _a.label = 3;
+                    case 3:
+                        query = (0, arangojs_1.aql)(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n      FOR ", " IN ", "\n        ", "\n        ", "\n        ", "\n        RETURN ", "\n    "], ["\n      FOR ", " IN ", "\n        ", "\n        ", "\n        ", "\n        RETURN ", "\n    "])), l.i, c, filterStr, sortStr, limitStr, l.i);
+                        return [4 /*yield*/, db.query(query)];
+                    case 4: return [4 /*yield*/, (_a.sent()).all()];
+                    case 5:
+                        data = _a.sent();
+                        if (data.length > count)
+                            count = data.length;
+                        return [2 /*return*/, [count, data, query]];
+                }
+            });
+        });
+    };
+};
+exports.search = search;
+exports["default"] = {
+    count: exports.count,
+    find: exports.find,
+    formatData: exports.formatData,
+    formatValue: exports.formatValue,
+    operatorMap: exports.operatorMap,
+    operators: exports.operators,
+    parseFilter: exports.parseFilter,
+    parseLimit: exports.parseLimit,
+    parseSort: exports.parseSort,
+    parseTerms: exports.parseTerms,
+    search: exports.search
+};
+var templateObject_1, templateObject_2, templateObject_3, templateObject_4;