src / searchCriteria.ts
export interface EmailSearchFilters {
query?: string;
from?: string;
to?: string;
subject?: string;
since?: Date;
before?: Date;
unseen?: boolean;
}
export type ImapSearchCriteria = Record<string, unknown>;
export function buildSearchCriteria(filters: EmailSearchFilters): ImapSearchCriteria {
const criteria: ImapSearchCriteria = {};
// ImapFlow's `text` criterion searches headers and body. All populated
// properties in this object are combined with AND semantics.
if (filters.query) criteria.text = filters.query;
if (filters.from) criteria.from = filters.from;
if (filters.to) criteria.to = filters.to;
if (filters.subject) criteria.subject = filters.subject;
if (filters.since) criteria.since = filters.since;
if (filters.before) criteria.before = filters.before;
if (filters.unseen) criteria.seen = false;
if (Object.keys(criteria).length === 0) criteria.all = true;
return criteria;
}
interface ESearchLike {
all?: unknown;
}
export function normalizeSearchUids(result: unknown): number[] {
if (result === false || result === null || result === undefined) return [];
const raw = Array.isArray(result)
? result
: typeof result === "object" && Array.isArray((result as ESearchLike).all)
? (result as ESearchLike).all
: [];
const valid = (raw as unknown[]).filter(
(uid): uid is number => typeof uid === "number" && Number.isSafeInteger(uid) && uid > 0,
);
return [...new Set(valid)];
}
export interface PaginationResult {
page: number[];
total: number;
offset: number;
hasMore: boolean;
}
export function paginateNewestFirst(
uids: number[],
offset: number,
limit: number,
): PaginationResult {
const safeOffset = Math.max(0, offset);
const safeLimit = Math.max(1, limit);
const newestFirst = [...uids].reverse();
const page = newestFirst.slice(safeOffset, safeOffset + safeLimit);
return {
page,
total: newestFirst.length,
offset: safeOffset,
hasMore: safeOffset + safeLimit < newestFirst.length,
};
}
src / searchCriteria.ts
export interface EmailSearchFilters {
query?: string;
from?: string;
to?: string;
subject?: string;
since?: Date;
before?: Date;
unseen?: boolean;
}
export type ImapSearchCriteria = Record<string, unknown>;
export function buildSearchCriteria(filters: EmailSearchFilters): ImapSearchCriteria {
const criteria: ImapSearchCriteria = {};
// ImapFlow's `text` criterion searches headers and body. All populated
// properties in this object are combined with AND semantics.
if (filters.query) criteria.text = filters.query;
if (filters.from) criteria.from = filters.from;
if (filters.to) criteria.to = filters.to;
if (filters.subject) criteria.subject = filters.subject;
if (filters.since) criteria.since = filters.since;
if (filters.before) criteria.before = filters.before;
if (filters.unseen) criteria.seen = false;
if (Object.keys(criteria).length === 0) criteria.all = true;
return criteria;
}
interface ESearchLike {
all?: unknown;
}
export function normalizeSearchUids(result: unknown): number[] {
if (result === false || result === null || result === undefined) return [];
const raw = Array.isArray(result)
? result
: typeof result === "object" && Array.isArray((result as ESearchLike).all)
? (result as ESearchLike).all
: [];
const valid = (raw as unknown[]).filter(
(uid): uid is number => typeof uid === "number" && Number.isSafeInteger(uid) && uid > 0,
);
return [...new Set(valid)];
}
export interface PaginationResult {
page: number[];
total: number;
offset: number;
hasMore: boolean;
}
export function paginateNewestFirst(
uids: number[],
offset: number,
limit: number,
): PaginationResult {
const safeOffset = Math.max(0, offset);
const safeLimit = Math.max(1, limit);
const newestFirst = [...uids].reverse();
const page = newestFirst.slice(safeOffset, safeOffset + safeLimit);
return {
page,
total: newestFirst.length,
offset: safeOffset,
hasMore: safeOffset + safeLimit < newestFirst.length,
};
}