import * as plugins from '../plugins.js'; import type { IStorageManager, IStorageListPageOptions, IStorageListPageResult, } from '@push.rocks/smartmta'; import { getSmartMtaTextNamespace, hashSmartMtaStorageKey, normalizeSmartMtaStorageKey, validateSmartMtaTextValue, } from './smartmta-storage-policy.js'; interface ISmartMtaStorageDocument { _id: string; id: string; key: string; value: string; createdAt: string; updatedAt: string; } export class SmartMtaStorageManager implements IStorageManager { private readonly collection: any; constructor(smartdataDb: plugins.smartdata.SmartdataDb) { this.collection = smartdataDb.mongoDb.collection('SmartMtaStorageDoc'); } private validateDocument( document: ISmartMtaStorageDocument, expectedKey?: string, ): ISmartMtaStorageDocument { const normalizedKey = normalizeSmartMtaStorageKey(document.key); const expectedId = hashSmartMtaStorageKey(normalizedKey); if ( document._id !== expectedId || document.id !== expectedId || document.key !== normalizedKey || (expectedKey && document.key !== expectedKey) || typeof document.value !== 'string' ) { throw new Error(`Corrupt SmartMTA storage document: ${document._id}`); } validateSmartMtaTextValue(document.key, document.value); return document; } public async get(key: string): Promise { const normalizedKey = normalizeSmartMtaStorageKey(key); getSmartMtaTextNamespace(normalizedKey); const id = hashSmartMtaStorageKey(normalizedKey); const document = await this.collection.findOne({ _id: id }); return document ? this.validateDocument(document, normalizedKey).value : null; } public async set(key: string, value: string): Promise { const normalizedKey = normalizeSmartMtaStorageKey(key); validateSmartMtaTextValue(normalizedKey, value); const id = hashSmartMtaStorageKey(normalizedKey); const existing = await this.collection.findOne({ _id: id }); if (existing) { this.validateDocument(existing, normalizedKey); } const now = new Date().toISOString(); await this.collection.updateOne( { _id: id }, { $set: { id, key: normalizedKey, value, updatedAt: now, }, $setOnInsert: { createdAt: now, }, }, { upsert: true }, ); const stored = await this.collection.findOne({ _id: id }); if (!stored || this.validateDocument(stored, normalizedKey).value !== value) { throw new Error(`SmartMTA storage write verification failed: ${normalizedKey}`); } } public async list(prefix: string): Promise { const normalizedPrefix = normalizeSmartMtaStorageKey(prefix); getSmartMtaTextNamespace(normalizedPrefix); const documents = await this.collection.find({ key: { $gte: normalizedPrefix, $lt: `${normalizedPrefix}\uffff`, }, }).toArray(); return documents .map((document: ISmartMtaStorageDocument) => this.validateDocument(document).key) .filter((key: string) => key.startsWith(normalizedPrefix)) .sort(); } /** * Bounded, deletion-stable page listing: seeks strictly beyond the last * returned key, so tokens stay valid while pages are deleted and an * otherwise stable prefix is always exhausted. */ public async listPage( prefix: string, options: IStorageListPageOptions, ): Promise { const normalizedPrefix = normalizeSmartMtaStorageKey(prefix); getSmartMtaTextNamespace(normalizedPrefix); const requestedLimit = Number.isSafeInteger(options?.limit) && options.limit > 0 ? options.limit : 100; const limit = Math.min(requestedLimit, 1000); let seekAfter: string | undefined; if (options?.continuationToken) { const decoded = Buffer.from(options.continuationToken, 'base64url').toString('utf8'); if (!decoded.startsWith(normalizedPrefix)) { throw new Error('SmartMTA storage continuation token does not match the requested prefix'); } seekAfter = decoded; } const documents = await this.collection .find({ key: { ...(seekAfter ? { $gt: seekAfter } : { $gte: normalizedPrefix }), $lt: `${normalizedPrefix}￿`, }, }) .sort({ key: 1 }) .limit(limit + 1) .toArray(); const hasMore = documents.length > limit; const pageDocuments = documents.slice(0, limit); const keys = pageDocuments .map((document: ISmartMtaStorageDocument) => this.validateDocument(document).key) .filter((key: string) => key.startsWith(normalizedPrefix)); const lastKey = pageDocuments.length > 0 ? (pageDocuments[pageDocuments.length - 1] as ISmartMtaStorageDocument).key : undefined; return { keys, ...(hasMore && lastKey ? { continuationToken: Buffer.from(lastKey, 'utf8').toString('base64url') } : {}), }; } public async delete(key: string): Promise { const normalizedKey = normalizeSmartMtaStorageKey(key); getSmartMtaTextNamespace(normalizedKey); const id = hashSmartMtaStorageKey(normalizedKey); const existing = await this.collection.findOne({ _id: id }); if (existing) { this.validateDocument(existing, normalizedKey); await this.collection.deleteOne({ _id: id }); } } }