UNPKG

@baqhub/sdk

Version:

The official JavaScript SDK for the BAQ federated app platform.

274 lines (273 loc) 9.31 kB
import { JSONPath } from "jsonpath-plus"; import flow from "lodash/flow.js"; import isEqual from "lodash/isEqual.js"; import isString from "lodash/isString.js"; import orderBy from "lodash/orderBy.js"; import uniqBy from "lodash/uniqBy.js"; import { Constants } from "../../constants.js"; import { Array } from "../../helpers/array.js"; import { Str } from "../../helpers/string.js"; import { isDefined } from "../../helpers/type.js"; import { RecordSource, } from "../records/record.js"; import { RecordKey } from "../records/recordKey.js"; import { normalizePath } from "./pathHelpers.js"; import { QueryDate } from "./queryDate.js"; import { Q, QueryFilter } from "./queryFilter.js"; import { QuerySort, QuerySortDirection, QuerySortProperty } from "./querySort.js"; const includeLinkSpecialValues = [ "entity", "standing", "existential", ]; const defaultIncludeLinks = [ "entity", "existential", ]; const defaultSources = [ RecordSource.SELF, RecordSource.NOTIFICATION, RecordSource.SUBSCRIPTION, ]; const defaultSortBy = [ QuerySortProperty.VERSION_RECEIVED_AT, QuerySortDirection.DESCENDING, ]; // // I/O. // function queryNew(query) { return query; } function queryOfKey(key, baseQuery = {}) { const { entity, recordId } = RecordKey.toComponents(key); return { ...baseQuery, pageSize: 1, filter: Q.and(Q.author(entity), Q.id(recordId)), }; } function querySingleToQueryString(query) { if (!query) { return ""; } return paramsToString([ ["include_links", includeLinksToString(query.includeLinks)], ["include_deleted", query.includeDeleted ? "true" : undefined], ["proxy_to", query.proxyTo], ]); } function queryToQueryString(query) { const filterStrings = query.filter && QueryFilter.toListString(query.filter); return paramsToString([ ["sort", query.sort && QuerySort.toString(query.sort)], ["min", query.min && QueryDate.toString(query.min)], ["max", query.max && QueryDate.toString(query.max)], ["page_start", query.pageStart && QueryDate.toString(query.pageStart)], ["page_size", (query.pageSize || Constants.defaultPageSize).toString()], ["distinct", query.distinct && normalizePath(query.distinct)], ["sources", query.sources?.join(",")], ...(filterStrings || []).map(f => ["filter", f]), ["include_links", includeLinksToString(query.includeLinks)], ["include_deleted", query.includeDeleted ? "true" : undefined], ["proxy_to", query.proxyTo], ]); } function queryToSync(query, boundary) { return { max: undefined, min: boundary, sort: QuerySort.syncDefault, pageStart: undefined, pageSize: 100, distinct: undefined, sources: query.sources, filter: query.filter, mode: query.mode, includeLinks: query.includeLinks, includeDeleted: true, proxyTo: query.proxyTo, }; } function queryFindBoundary(query, record) { const sort = query.sort || QuerySort.default; return [QuerySort.findDateInRecord(record, sort), record.id]; } function includeLinksToString(includeLinks) { function includeLinkToString(link) { if (includeLinkSpecialValues.includes(link)) { return link; } return normalizePath(link); } return includeLinks?.map(includeLinkToString).join(","); } function includeLinksIsSuperset(links1, links2) { const l1 = links1 || defaultIncludeLinks; const l2 = links2 || defaultIncludeLinks; return Array.isSuperset(l1, l2); } function sourcesIsSuperset(sources1, sources2) { const s1 = sources1 || defaultSources; const s2 = sources2 || defaultSources; return Array.isSuperset(s1, s2); } function paramsToString(params) { const filteredParams = params .map(p => (isDefined(p[1]) ? [p[0], p[1]] : undefined)) .filter(isDefined); return Str.buildQuery(filteredParams); } function findQueryMaxDate(query, sortDirection) { if (query.pageStart && sortDirection === QuerySortDirection.DESCENDING) { return query.pageStart; } return query.max; } function findQueryMinDate(query, sortDirection) { if (query.pageStart && sortDirection === QuerySortDirection.ASCENDING) { return query.pageStart; } return query.min; } function queryFilter(query, records, { ignorePageSize, boundary } = {}) { const sortBy = query.sort || defaultSortBy; const sortDir = QuerySort.toDirection(sortBy); const isAscending = sortDir === QuerySortDirection.ASCENDING; const maxDate = findQueryMaxDate(query, sortDir); const minDate = findQueryMinDate(query, sortDir); function sort() { const order = isAscending ? "asc" : "desc"; return (list) => { return orderBy(list, r => QuerySort.findDateInRecord(r, sortBy), order); }; } function distinct() { const { distinct } = query; if (!distinct) { return (list) => list; } const distinctValue = (record) => { const recordValues = JSONPath({ path: distinct, json: record, }).filter(isDefined); if (recordValues.length === 0) { return `null+${record.author.entity}+${record.id}`; } // TODO: Implement link detection for correct logic. const valueToString = (value) => { if (isString(value)) { return `"${value}"`; } if ("versionHash" in value) { return `${value.entity}+${value.recordId}+${value.versionHash}`; } if ("recordId" in value) { return `${value.entity}+${value.recordId}`; } if ("entity" in value) { return value.entity; } return String(value); }; return recordValues.map(valueToString).join(":"); }; return (list) => uniqBy(list, distinctValue); } function filter() { function isMatch(record) { // Exclude deleted. if ("noContent" in record) { return false; } // Date boundaries. const recordDate = QuerySort.findDateInRecord(record, sortBy); const recordId = record.id; if (QueryDate.compare(recordDate, recordId, maxDate) > 0) { return false; } if (QueryDate.compare(recordDate, recordId, minDate) < 0) { return false; } if (boundary) { const result = QueryDate.compare(recordDate, recordId, boundary); if (isAscending ? result > 0 : result < 0) { return false; } } if (query.mode && record.mode !== query.mode) { return false; } // Sources. const sources = query.sources || defaultSources; if (!sources.includes(record.source) && record.source !== RecordSource.PROXY) { return false; } // Filter. return !query.filter || QueryFilter.isMatch(record, query.filter); } return (list) => { return list.filter(isMatch); }; } function pageSize() { if (ignorePageSize) { return list => list; } return list => { return list.slice(0, query.pageSize || Constants.defaultPageSize); }; } return flow(sort(), distinct(), filter(), pageSize())(records); } function queryIsMatch(query1, query2) { const { ["filter"]: filter1, ...q1 } = query1; const { ["filter"]: filter2, ...q2 } = query2; if (!isEqual(q1, q2)) { return false; } if (filter1 && filter2) { return (QueryFilter.isSuperset(filter1, filter2) && QueryFilter.isSuperset(filter2, filter1)); } return !filter1 && !filter2; } function queryIsSuperset(query1, query2) { if (query1.distinct !== query2.distinct) { return false; } return queryIsSyncSuperset(query1, query2); } function queryIsSyncSuperset(query1, query2) { if (query1.mode !== query2.mode || query1.proxyTo !== query2.proxyTo) { return false; } if (!includeLinksIsSuperset(query1.includeLinks, query2.includeLinks)) { return false; } if (!sourcesIsSuperset(query1.sources, query2.sources)) { return false; } if (query2.includeDeleted && !query1.includeDeleted) { return false; } if (query1.filter && query2.filter) { return QueryFilter.isSuperset(query1.filter, query2.filter); } return !query1.filter && !query2.filter; } export const Query = { new: queryNew, ofKey: queryOfKey, singleToQueryString: querySingleToQueryString, toQueryString: queryToQueryString, toSync: queryToSync, findBoundary: queryFindBoundary, filter: queryFilter, isMatch: queryIsMatch, isSuperset: queryIsSuperset, isSyncSuperset: queryIsSyncSuperset, defaultIncludeLinks, defaultSources, };