mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 06:21:09 +02:00
Upload hooks: - StorageHooks class with fire-and-forget event emitter pattern - Events: upload, upload:error, delete, delete:error, download - All StorageClient operations now emit appropriate events - Unsubscribe functions for cleanup Metrics: - StorageMetricsCollector interface (decoupled from prom-client) - InMemoryMetrics for testing and local dev - attachMetrics() wires hooks to any collector automatically - Backends can create a Prometheus collector via MetricsService Presigned multipart upload (browser direct-upload): - createMultipartUpload() initiates and returns uploadId - getMultipartUploadUrls() generates presigned PUT URLs per part - completeMultipartUpload() finalizes with part ETags - abortMultipartUpload() for cleanup on abandoned uploads 90 tests passing across 5 test files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
91 lines
1.9 KiB
TypeScript
91 lines
1.9 KiB
TypeScript
import type { UploadResult } from './types';
|
|
|
|
/**
|
|
* Storage event types
|
|
*/
|
|
export type StorageEventType = 'upload' | 'upload:error' | 'delete' | 'delete:error' | 'download';
|
|
|
|
/**
|
|
* Payload for upload events
|
|
*/
|
|
export interface UploadEventPayload {
|
|
bucket: string;
|
|
key: string;
|
|
sizeBytes?: number;
|
|
contentType?: string;
|
|
result?: UploadResult;
|
|
}
|
|
|
|
/**
|
|
* Payload for delete events
|
|
*/
|
|
export interface DeleteEventPayload {
|
|
bucket: string;
|
|
keys: string[];
|
|
}
|
|
|
|
/**
|
|
* Payload for download events
|
|
*/
|
|
export interface DownloadEventPayload {
|
|
bucket: string;
|
|
key: string;
|
|
}
|
|
|
|
/**
|
|
* Payload for error events
|
|
*/
|
|
export interface ErrorEventPayload {
|
|
bucket: string;
|
|
key?: string;
|
|
error: Error;
|
|
}
|
|
|
|
/**
|
|
* Event payload map
|
|
*/
|
|
export interface StorageEventMap {
|
|
upload: UploadEventPayload;
|
|
'upload:error': ErrorEventPayload;
|
|
delete: DeleteEventPayload;
|
|
'delete:error': ErrorEventPayload;
|
|
download: DownloadEventPayload;
|
|
}
|
|
|
|
export type StorageHook<T extends StorageEventType> = (payload: StorageEventMap[T]) => void;
|
|
|
|
/**
|
|
* Simple event emitter for storage lifecycle hooks.
|
|
* Hooks are fire-and-forget — errors in hooks do not affect storage operations.
|
|
*/
|
|
export class StorageHooks {
|
|
private listeners = new Map<StorageEventType, Set<StorageHook<StorageEventType>>>();
|
|
|
|
on<T extends StorageEventType>(event: T, hook: StorageHook<T>): () => void {
|
|
if (!this.listeners.has(event)) {
|
|
this.listeners.set(event, new Set());
|
|
}
|
|
const set = this.listeners.get(event) as Set<StorageHook<T>>;
|
|
set.add(hook);
|
|
|
|
// Return unsubscribe function
|
|
return () => set.delete(hook);
|
|
}
|
|
|
|
emit<T extends StorageEventType>(event: T, payload: StorageEventMap[T]): void {
|
|
const hooks = this.listeners.get(event);
|
|
if (!hooks) return;
|
|
|
|
for (const hook of hooks) {
|
|
try {
|
|
hook(payload);
|
|
} catch {
|
|
// Hooks are fire-and-forget — swallow errors
|
|
}
|
|
}
|
|
}
|
|
|
|
removeAll(): void {
|
|
this.listeners.clear();
|
|
}
|
|
}
|