import { DocBase } from "../util/doc-types.ts";import { DEFAULT_QUERY, Query, QueryFilter } from "./query-types.ts";
import { Logger } from "../util/log.ts";import { shallowEqualObjects } from "../../deps.ts";const logger = new Logger("query", "green");
export type WillMatch = "all" | "all-latest" | "some" | "nothing";export interface CleanUpQueryResult { query: Query<string[]>; isValid: boolean; willMatch: WillMatch;}export function cleanUpQuery(inputQuery: Query<string[]>): CleanUpQueryResult {
const query = { ...DEFAULT_QUERY, ...inputQuery };
const invalidResponse: CleanUpQueryResult = { query: { limit: 0 }, isValid: false, willMatch: "nothing", };
if (query.limit !== undefined && query.limit < 0) { logger.debug( "cleanUpQuery: unreasonable limit - returning empty invalid query", invalidResponse, ); return invalidResponse; }
if ( query.orderBy?.startsWith("path") && query.startAfter?.localIndex !== undefined ) { logger.debug( 'cleanUpQuery: orderBy is "path" but startAfter is not compatible - returning empty invalid query', invalidResponse, ); return invalidResponse; } if ( query.orderBy?.startsWith("localIndex") && query.startAfter?.path !== undefined ) { logger.debug( 'cleanUpQuery: orderBy is "localIndex" but startAfter is not compatible - returning empty invalid query', invalidResponse, ); return invalidResponse; }
if ( query.historyMode !== undefined && query.historyMode !== "all" && query.historyMode !== "latest" ) { logger.debug( `cleanUpQuery: unknown historyMode ${ JSON.stringify(query.historyMode) } - returning empty invalid query`, invalidResponse, ); return invalidResponse; } if (query.orderBy !== undefined) { if ( ["path ASC", "path DESC", "localIndex ASC", "localIndex DESC"] .indexOf( query.orderBy, ) === -1 ) { logger.debug( `cleanUpQuery: unrecognized orderBy value ${ JSON.stringify(query.orderBy) } - returning empty invalid query`, invalidResponse, ); return invalidResponse; } }
let willMatch: WillMatch = query.historyMode === "all" ? "all" : "all-latest";
if (query.filter !== undefined && !shallowEqualObjects(query.filter, {})) { willMatch = "some"; }
if ( query.startAfter !== undefined && !shallowEqualObjects(query.startAfter, {}) ) { willMatch = "some"; }
if (query.limit !== undefined) { if (query.limit > 0) willMatch = "some"; if (query.limit === 0) willMatch = "nothing"; }
if (query.filter !== undefined) { const filter = query.filter; if ( filter.path && filter.pathStartsWith && !filter.path.startsWith(filter.pathStartsWith) ) { willMatch = "nothing"; } if ( filter.path && filter.pathEndsWith && !filter.path.endsWith(filter.pathEndsWith) ) { willMatch = "nothing"; } if ( filter.timestamp && filter.timestampGt && !(filter.timestamp > filter.timestampGt) ) { willMatch = "nothing"; } if ( filter.timestamp && filter.timestampLt && !(filter.timestamp < filter.timestampLt) ) { willMatch = "nothing"; } if ( filter.timestampGt && filter.timestampLt && !(filter.timestampLt + 1 < filter.timestampGt) ) { willMatch = "nothing"; }
}
if (willMatch === "nothing") { let nopQuery: CleanUpQueryResult = { query: { limit: 0 }, isValid: true, willMatch: "nothing", }; logger.debug( `cleanUpQuery - this query will match nothing, so returning a simpler query that also matches nothing`, nopQuery, ); return nopQuery; }
logger.debug(`cleanUpQuery - query is ok! willMatch = ${willMatch}`); return { query, isValid: true, willMatch, };}
export function docMatchesFilter< FormatType extends string, DocType extends DocBase<FormatType>,>(doc: DocType, filter: QueryFilter): boolean { if (filter.path !== undefined && doc.path !== filter.path) return false; if ( filter.pathStartsWith !== undefined && !doc.path.startsWith(filter.pathStartsWith) ) { return false; } if ( filter.pathEndsWith !== undefined && !doc.path.endsWith(filter.pathEndsWith) ) { return false; } if (filter.author !== undefined && doc.author !== filter.author) { return false; } if (filter.timestamp !== undefined && doc.timestamp !== filter.timestamp) { return false; } if ( filter.timestampGt !== undefined && !(doc.timestamp > filter.timestampGt) ) { return false; } if ( filter.timestampLt !== undefined && !(doc.timestamp < filter.timestampLt) ) { return false; }
return true;}
export function docIsExpired< FormatType extends string, DocType extends DocBase<FormatType>,>(doc: DocType, now?: number) { if (doc.deleteAfter === null || doc.deleteAfter === undefined) { return false; }
const nowToUse = now || Date.now() * 1000;
return nowToUse > doc.deleteAfter;}