/** * @module notification.util * * Server-side utility functions for notification config resolution, exclusion management, * send state evaluation, and recipient merging. */ import { type ArrayOrValue, type Maybe } from '@dereekb/util'; import { type Notification, type NotificationBox, type NotificationBoxDocument, NotificationRecipientSendFlag, type NotificationSendFlags, NotificationSendState, type NotificationUser } from './notification'; import { type NotificationUserNotificationBoxRecipientConfig, type NotificationBoxRecipient, type NotificationUserDefaultNotificationBoxRecipientConfig } from './notification.config'; import { type AppNotificationTemplateTypeInfoRecordService } from './notification.details'; import { type FirebaseAuthUserId, type FirestoreDocumentAccessor, type FirestoreModelKey } from '../../common'; import { type NotificationBoxId, type NotificationId, type NotificationBoxSendExclusionList, type NotificationBoxSendExclusion } from './notification.id'; /** * Input for computing the effective {@link NotificationBoxRecipient} by merging the 3-level config hierarchy: * recipient entry → user's per-box config → user's global config. */ export interface EffectiveNotificationBoxRecipientConfigInput { readonly uid: FirebaseAuthUserId; readonly m?: FirestoreModelKey; readonly appNotificationTemplateTypeInfoRecordService: AppNotificationTemplateTypeInfoRecordService; readonly gc: NotificationUserDefaultNotificationBoxRecipientConfig; readonly boxConfig: NotificationUserNotificationBoxRecipientConfig; readonly recipient?: Maybe; } /** * Computes the effective {@link NotificationBoxRecipient} by merging configs from highest to lowest priority: * global config (`gc`) → per-box user config (`boxConfig`) → existing box recipient. * * Filters template configs to only include types applicable to the notification box's model. * Used during the server-side sync process to update box recipient entries from user configs. * * @param input - the merged config inputs including uid, global config, box config, and optional existing recipient * @returns the computed effective box recipient with merged config, contact info, and flags */ export declare function effectiveNotificationBoxRecipientConfig(input: EffectiveNotificationBoxRecipientConfigInput): NotificationBoxRecipient; /** * Input for adding/removing send exclusions on a {@link NotificationUser}. */ export interface UpdateNotificationUserNotificationSendExclusionsInput { readonly notificationUser: Pick; readonly addExclusions?: ArrayOrValue; readonly removeExclusions?: ArrayOrValue; } export interface UpdateNotificationUserNotificationSendExclusionsResult { readonly nextExclusions: NotificationBoxSendExclusionList; readonly update: Pick; } /** * Adds and/or removes send exclusions from a {@link NotificationUser}, validates them against associated boxes, * and propagates exclusion flags to the per-box configs. * * Exclusions not matching any associated notification box are automatically filtered out. * * @param input - the user, exclusions to add, and exclusions to remove * @returns the updated exclusion list and the partial user update to apply */ export declare function updateNotificationUserNotificationSendExclusions(input: UpdateNotificationUserNotificationSendExclusionsInput): UpdateNotificationUserNotificationSendExclusionsResult; /** * Input for applying exclusion flags to per-box configs, updating the `x` and `ns` fields on each config. */ export interface ApplyExclusionsToNotificationUserNotificationBoxRecipientConfigsParams { readonly x?: NotificationBoxSendExclusionList; readonly bc?: Maybe; readonly notificationUser?: Pick; readonly recalculateNs?: boolean; } export type ApplyExclusionsToNotificationUserNotificationBoxRecipientConfigsResult = Pick; /** * Applies the current exclusion list to per-box configs, setting/clearing the `x` flag and marking * changed configs as needing sync (`ns = true`). * * @param params - the exclusion list, per-box configs, and optional flag to recalculate the global ns flag * @returns the updated per-box configs and the recalculated needs-sync flag */ export declare function applyExclusionsToNotificationUserNotificationBoxRecipientConfigs(params: ApplyExclusionsToNotificationUserNotificationBoxRecipientConfigsParams): ApplyExclusionsToNotificationUserNotificationBoxRecipientConfigsResult; /** * Returns true if any of the per-box configs need syncing (`ns == true`). * * @param configs - array of per-box recipient configs to check * @returns true if at least one config has its needs-sync flag set */ export declare function calculateNsForNotificationUserNotificationBoxRecipientConfigs(configs: NotificationUserNotificationBoxRecipientConfig[]): boolean; /** * Predicate function that returns true if the given notification/box ID is NOT excluded from delivery. * Uses prefix matching against the exclusion list. */ export type NotificationSendExclusionCanSendFunction = ((notification: NotificationId | NotificationBoxId) => boolean) & { readonly _exclusions: NotificationBoxSendExclusionList; }; /** * Creates a {@link NotificationSendExclusionCanSendFunction} from the given exclusion list. * Returns true for IDs that don't match any exclusion prefix. * * @param exclusions - the list of box IDs or prefixes that should be excluded from delivery * @returns a predicate function that returns true if the given notification/box ID is not excluded */ export declare const notificationSendExclusionCanSendFunction: (exclusions: NotificationBoxSendExclusionList) => NotificationSendExclusionCanSendFunction; /** * Returns true if all channels on the notification have reached a terminal state * (NONE, NO_TRY, SENT, or SKIPPED). Used to determine if the notification can be marked done. * * @param input - the per-channel send flags to evaluate * @returns true if all channels are in a terminal send state */ export declare function notificationSendFlagsImplyIsComplete(input: NotificationSendFlags): boolean; /** * Returns true if the given send state is terminal — no further send attempts will be made. * Terminal states: NONE, NO_TRY, SENT, SKIPPED. * * @param input - the send state to evaluate * @returns true if the state is terminal and no further delivery will be attempted */ export declare function isCompleteNotificationSendState(input: NotificationSendState): boolean; /** * Resolved recipient group flags based on a {@link NotificationRecipientSendFlag}. */ export interface AllowedNotificationRecipients { readonly canSendToGlobalRecipients: boolean; readonly canSendToBoxRecipients: boolean; readonly canSendToExplicitRecipients: boolean; } /** * Resolves which recipient groups (global, box, explicit) are allowed based on the {@link NotificationRecipientSendFlag}. * * @param flag - the recipient send flag controlling which groups are included * @returns an object indicating which recipient groups are permitted for this notification */ export declare function allowedNotificationRecipients(flag?: Maybe): AllowedNotificationRecipients; /** * Returns true if the notification should be archived to a {@link NotificationWeek} after delivery. * * Only notifications that can be sent to box recipients are archived (notifications restricted * to only explicit or only global recipients are not saved to the weekly archive). * * @param notification - the notification to check * @returns true if the notification should be saved to the weekly archive after delivery */ export declare function shouldSaveNotificationToNotificationWeek(notification: Notification): boolean; /** * Merges a partial update into a {@link NotificationUserNotificationBoxRecipientConfig}, * preserving user-controlled fields (`nb`, `rm`, `ns`, `lk`, `bk`) and respecting OPT_OUT state. * * @param a - base user box recipient config to merge into * @param b - partial update to apply on top of the base * @returns the merged config with protected fields retained from `a` */ export declare function mergeNotificationUserNotificationBoxRecipientConfigs(a: NotificationUserNotificationBoxRecipientConfig, b: Partial): NotificationUserNotificationBoxRecipientConfig; /** * Merges a partial update into a {@link NotificationBoxRecipient}, deeply merging the `c` (config record) field. * * @param a - base recipient to merge into * @param b - partial recipient update to apply on top of the base * @returns the merged recipient with deeply merged template config records */ export declare function mergeNotificationBoxRecipients(a: T, b: Partial): T; /** * Input for resolving a {@link NotificationBoxDocument}, either directly or by computing its ID from a model key. */ export interface NotificationBoxDocumentReferencePair { /** * NotificationBoxDocument to update. * * If not provided, please provide the notificationBoxRelatedModelKey. If neither value is provided, an error will be thrown. */ readonly notificationBoxDocument?: Maybe; /** * Key of the model the notification box is expected to be associated with. Used if NotificationBoxDocument is not provided already. */ readonly notificationBoxRelatedModelKey?: Maybe; } /** * Resolves a {@link NotificationBoxDocument} from a reference pair, loading by model key if no document is provided directly. * * @param input - reference pair containing either a direct document or a model key to load from * @param accessor - Firestore document accessor used to load the document by ID when needed * @returns the resolved NotificationBoxDocument * @throws {Error} When neither a document nor a model key is provided. */ export declare function loadNotificationBoxDocumentForReferencePair(input: NotificationBoxDocumentReferencePair, accessor: FirestoreDocumentAccessor): NotificationBoxDocument;