lint fix wip

This commit is contained in:
Nicolas Cantu 2026-01-07 11:00:13 +01:00
parent e0c025908b
commit b69dfc96e9
7 changed files with 533 additions and 40 deletions

View File

@ -0,0 +1,175 @@
# Patterns de cache d'événements
**Date** : 2024-12-19
**Auteur** : Équipe 4NK
## Introduction
Ce document décrit les patterns à utiliser pour cacher des événements Nostr dans IndexedDB.
## Pattern recommandé : Utilisation des helpers de cache
### Cache d'événements groupés par hash
Pour les objets qui ont plusieurs versions (publications, series), utiliser `groupAndCacheEventsByHash` :
```typescript
import { groupAndCacheEventsByHash } from '@/lib/helpers/eventCacheHelper'
import { extractPublicationFromEvent } from '@/lib/metadataExtractor'
import { parseObjectId } from '@/lib/urlGenerator'
import { extractTagsFromEvent } from '@/lib/nostrTagSystem'
await groupAndCacheEventsByHash(events, {
objectType: 'publication',
extractor: extractPublicationFromEvent,
getHash: (extracted: unknown): string | null => {
const id = (extracted as { id?: string })?.id
if (!id) {
return null
}
const parsed = parseObjectId(id)
return parsed.hash ?? id
},
getIndex: (extracted: unknown): number => {
return (extracted as { index?: number })?.index ?? 0
},
getVersion: (event: Event): number => {
return extractTagsFromEvent(event).version ?? 0
},
getHidden: (event: Event): boolean => {
return extractTagsFromEvent(event).hidden ?? false
},
})
```
### Cache d'événements simples
Pour les objets sans versioning (purchases, sponsoring, review tips), utiliser `writeObjectToCache` :
```typescript
import { writeObjectToCache } from '@/lib/helpers/writeObjectHelper'
await writeObjectToCache({
objectType: 'purchase',
hash: purchase.hash,
event,
parsed: purchase,
index: purchase.index,
})
```
### Cache avec extraction automatique
Pour combiner extraction et cache en une seule opération :
```typescript
import { extractAndWriteObject } from '@/lib/helpers/writeObjectHelper'
import { extractPurchaseFromEvent } from '@/lib/metadataExtractor'
const cached = await extractAndWriteObject(
event,
'purchase',
extractPurchaseFromEvent
)
if (cached) {
console.log('Event cached successfully')
}
```
## Pattern de cache dans les boucles
### Optimisation : Cache l'import de writeService
Le helper `writeObjectToCache` cache automatiquement l'import de `writeService`, donc pas besoin d'optimiser manuellement :
```typescript
// ✅ Bon : writeObjectToCache gère le cache automatiquement
for (const event of events) {
await writeObjectToCache({
objectType: 'purchase',
hash: purchase.hash,
event,
parsed: purchase,
})
}
// ❌ Éviter : Import répété dans la boucle
for (const event of events) {
const { writeService } = await import('./writeService')
await writeService.writeObject(...)
}
```
## Gestion des versions
Pour les objets avec versioning, `groupAndCacheEventsByHash` :
1. Groupe les événements par hash
2. Sélectionne la dernière version avec `getLatestVersion`
3. Cache uniquement la dernière version
```typescript
// Les événements sont automatiquement groupés par hash
// Seule la dernière version est cachée
await groupAndCacheEventsByHash(events, {
objectType: 'publication',
extractor: extractPublicationFromEvent,
// ... config
})
```
## Bonnes pratiques
1. **Utiliser les helpers appropriés** :
- `groupAndCacheEventsByHash` pour objets avec versioning
- `writeObjectToCache` pour objets simples
- `extractAndWriteObject` pour combiner extraction et cache
2. **Ne pas dupliquer la logique de cache** : Toujours utiliser les helpers centralisés
3. **Gérer les erreurs** : Les helpers propagent les erreurs, les gérer en amont
4. **Typage strict** : Utiliser les types TypeScript pour `parsed`
5. **Performance** : Les helpers optimisent automatiquement les imports
## Exemples complets
### Cache de publications avec groupement
```typescript
import { groupAndCacheEventsByHash } from '@/lib/helpers/eventCacheHelper'
import { cachePublicationsByHash } from '@/lib/helpers/syncContentCacheHelpers'
// Utiliser le helper spécialisé
await cachePublicationsByHash(events)
```
### Cache de purchases
```typescript
import { cachePurchases } from '@/lib/helpers/syncCacheHelpers'
await cachePurchases(events)
```
### Cache personnalisé
```typescript
import { writeObjectToCache } from '@/lib/helpers/writeObjectHelper'
for (const event of events) {
const extracted = await extractMyObjectFromEvent(event)
if (extracted?.hash) {
await writeObjectToCache({
objectType: 'my_object',
hash: extracted.hash,
event,
parsed: extracted,
version: extractTagsFromEvent(event).version,
hidden: extractTagsFromEvent(event).hidden,
index: extracted.index,
})
}
}
```

View File

@ -0,0 +1,181 @@
# Patterns d'utilisation IndexedDB
**Date** : 2024-12-19
**Auteur** : Équipe 4NK
## Introduction
Ce document décrit les patterns à utiliser pour interagir avec IndexedDB dans le projet. Tous les services IndexedDB doivent utiliser `IndexedDBHelper` pour garantir la cohérence et réduire la duplication.
## Pattern recommandé : Utilisation de IndexedDBHelper
### Création d'un service IndexedDB
```typescript
import { createIndexedDBHelper } from '@/lib/helpers/indexedDBHelper'
const dbHelper = createIndexedDBHelper({
dbName: 'my_database',
version: 1,
storeName: 'my_store',
keyPath: 'id',
indexes: [
{ name: 'timestamp', keyPath: 'timestamp', unique: false },
{ name: 'type', keyPath: 'type', unique: false },
],
onUpgrade: (db, event) => {
// Migration logic if needed
},
})
// Utilisation
await dbHelper.init()
const store = await dbHelper.getStore('readwrite')
await dbHelper.add({ id: '1', data: 'value' })
```
### Opérations courantes
#### Ajouter un objet
```typescript
await dbHelper.add(object)
```
#### Récupérer un objet par clé
```typescript
const obj = await dbHelper.get<MyType>(key)
```
#### Récupérer par index
```typescript
const obj = await dbHelper.getByIndex<MyType>('indexName', value)
```
#### Récupérer tous les objets
```typescript
const all = await dbHelper.getAll<MyType>()
```
#### Compter par index
```typescript
const count = await dbHelper.countByIndex('indexName', IDBKeyRange.only(value))
```
#### Mettre à jour un objet
```typescript
await dbHelper.update(object)
```
#### Supprimer un objet
```typescript
await dbHelper.delete(key)
```
#### Supprimer tous les objets
```typescript
await dbHelper.clear()
```
## Gestion des erreurs
Toutes les erreurs IndexedDB sont automatiquement wrappées dans `IndexedDBError` avec :
- Message d'erreur
- Nom de l'opération
- Nom du store (si applicable)
- Cause originale
```typescript
try {
await dbHelper.add(object)
} catch (error) {
if (error instanceof IndexedDBError) {
console.error(`Operation: ${error.operation}, Store: ${error.storeName}`)
}
throw error
}
```
## Transactions
Les transactions sont gérées automatiquement par `IndexedDBHelper`. Chaque opération utilise une transaction appropriée.
Pour des opérations multiples dans une seule transaction :
```typescript
const store = await dbHelper.getStore('readwrite')
// Toutes les opérations sur store sont dans la même transaction
await store.add(object1)
await store.add(object2)
// Transaction se ferme automatiquement à la fin de la fonction
```
## Migrations
Les migrations sont gérées dans `onUpgrade` :
```typescript
const dbHelper = createIndexedDBHelper({
// ...
onUpgrade: (db, event) => {
const transaction = event.target.transaction
const store = transaction.objectStore('my_store')
// Ajouter un index si nécessaire
if (!store.indexNames.contains('newIndex')) {
store.createIndex('newIndex', 'newField', { unique: false })
}
},
})
```
## Bonnes pratiques
1. **Toujours utiliser IndexedDBHelper** : Ne pas créer de code d'initialisation IndexedDB personnalisé
2. **Gérer les erreurs** : Toujours utiliser try/catch avec IndexedDBError
3. **Typage strict** : Utiliser les génériques TypeScript pour le typage
4. **Transactions** : Regrouper les opérations liées dans une seule transaction
5. **Migrations** : Tester les migrations sur des données réelles avant déploiement
## Exemples complets
### Service de cache simple
```typescript
import { createIndexedDBHelper } from '@/lib/helpers/indexedDBHelper'
class MyCacheService {
private dbHelper = createIndexedDBHelper({
dbName: 'my_cache',
version: 1,
storeName: 'items',
keyPath: 'id',
indexes: [
{ name: 'timestamp', keyPath: 'timestamp', unique: false },
],
})
async init(): Promise<void> {
await this.dbHelper.init()
}
async addItem(item: MyItem): Promise<void> {
await this.dbHelper.add(item)
}
async getItem(id: string): Promise<MyItem | null> {
return await this.dbHelper.get<MyItem>(id)
}
async getAllItems(): Promise<MyItem[]> {
return await this.dbHelper.getAll<MyItem>()
}
}
```

View File

@ -0,0 +1,152 @@
# Patterns de subscription Nostr
**Date** : 2024-12-19
**Auteur** : Équipe 4NK
## Introduction
Ce document décrit les patterns à utiliser pour créer des subscriptions Nostr avec rotation de relais et gestion des événements.
## Pattern recommandé : Utilisation de createSyncSubscription
### Subscription simple avec relay rotation
```typescript
import { createSyncSubscription } from '@/lib/helpers/syncSubscriptionHelper'
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
import type { Filter } from 'nostr-tools'
async function fetchEvents(
pool: SimplePoolWithSub,
filters: Filter[]
): Promise<Event[]> {
const result = await createSyncSubscription({
pool,
filters,
timeout: 10000,
})
return result.events
}
```
### Subscription avec filtrage d'événements
```typescript
const result = await createSyncSubscription({
pool,
filters,
eventFilter: (event: Event): boolean => {
const tags = extractTagsFromEvent(event)
return tags.type === 'publication' && !tags.hidden
},
timeout: 10000,
})
```
### Subscription avec callbacks
```typescript
const result = await createSyncSubscription({
pool,
filters,
onEvent: (event: Event): void => {
console.log('Received event:', event.id)
// Traitement immédiat si nécessaire
},
onComplete: async (events: Event[]): Promise<void> => {
console.log(`Received ${events.length} events`)
// Traitement final de tous les événements
},
timeout: 10000,
})
```
### Subscription avec mise à jour du progrès
```typescript
const result = await createSyncSubscription({
pool,
filters,
updateProgress: (relayUrl: string): void => {
// Mise à jour personnalisée du progrès
console.log('Using relay:', relayUrl)
},
timeout: 10000,
})
```
## Gestion des erreurs
Les erreurs de subscription sont gérées automatiquement :
- Rotation automatique vers le relais suivant en cas d'échec
- Fallback vers le relais principal si tous les relais échouent
- Timeout automatique après le délai spécifié
```typescript
try {
const result = await createSyncSubscription({
pool,
filters,
timeout: 10000,
})
// Traiter les événements
} catch (error) {
console.error('Subscription failed:', error)
// Gérer l'erreur
}
```
## Bonnes pratiques
1. **Toujours utiliser createSyncSubscription** : Ne pas créer de code de subscription personnalisé
2. **Définir un timeout approprié** : Par défaut 10 secondes, ajuster selon le contexte
3. **Filtrer les événements** : Utiliser `eventFilter` pour éviter de traiter des événements non pertinents
4. **Gérer les callbacks** : Utiliser `onEvent` pour traitement immédiat, `onComplete` pour traitement final
5. **Mise à jour du progrès** : Utiliser `updateProgress` pour informer l'utilisateur
## Exemples complets
### Synchronisation de publications
```typescript
import { createSyncSubscription } from '@/lib/helpers/syncSubscriptionHelper'
import { buildTagFilter } from '@/lib/nostrTagSystemFilter'
import { PLATFORM_SERVICE } from '@/lib/platformConfig'
import { extractTagsFromEvent } from '@/lib/nostrTagSystem'
async function syncPublications(
pool: SimplePoolWithSub,
authorPubkey: string
): Promise<void> {
const { getLastSyncDate } = await import('./syncStorage')
const lastSyncDate = await getLastSyncDate()
const filters = [
{
...buildTagFilter({
type: 'publication',
authorPubkey,
service: PLATFORM_SERVICE,
}),
since: lastSyncDate,
limit: 1000,
},
]
const result = await createSyncSubscription({
pool,
filters,
eventFilter: (event: Event): boolean => {
const tags = extractTagsFromEvent(event)
return tags.type === 'publication' && !tags.hidden
},
timeout: 10000,
})
// Traiter les événements
for (const event of result.events) {
// ...
}
}
```

View File

@ -79,14 +79,16 @@ export async function extractAndWriteObject<T extends { hash?: string; index?: n
return false
}
await writeObjectToCache({
const params: WriteObjectParams = {
objectType,
hash,
event,
parsed: extracted,
index: extracted.index,
})
}
if (extracted.index !== undefined) {
params.index = extracted.index
}
await writeObjectToCache(params)
return true
}

View File

@ -14,7 +14,7 @@ import { getPrimaryRelay, getPrimaryRelaySync } from './config'
import { buildTagFilter } from './nostrTagSystem'
import { PLATFORM_SERVICE, MIN_EVENT_DATE } from './platformConfig'
import type { PublishResult, RelayPublishStatus } from './publishResult'
import { objectCache, type ObjectType } from './objectCache'
import { objectCache, type CachedObject } from './objectCache'
class NostrService {
private pool: SimplePool | null = null
@ -486,33 +486,15 @@ class NostrService {
// If not found in unpublished, search all objects
for (const objectType of objectTypes) {
try {
// Use private method via type assertion for direct database access
const db = await (objectCache as unknown as { initDB: (objectType: ObjectType) => Promise<IDBDatabase> }).initDB(objectType)
const transaction = db.transaction(['objects'], 'readonly')
const store = transaction.objectStore('objects')
const request = store.openCursor()
const found = await new Promise<string | null>((resolve, reject) => {
request.onsuccess = (event: globalThis.Event): void => {
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result
if (cursor) {
const obj = cursor.value as { id: string; event: { id: string } }
if (obj.event.id === eventId) {
resolve(obj.id)
return
}
cursor.continue()
} else {
resolve(null)
}
}
request.onerror = (): void => {
reject(request.error)
}
// Use getAll to search all objects
const allObjects = await objectCache.getAll(objectType)
const matching = allObjects.find((obj) => {
const cachedObj = obj as CachedObject
return cachedObj.event?.id === eventId
})
if (found) {
await writeService.updatePublished(objectType, found, published)
if (matching) {
const cachedObj = matching as CachedObject
await writeService.updatePublished(objectType, cachedObj.id, published)
return
}
} catch (error) {

View File

@ -4,6 +4,7 @@
*/
import { nostrService } from './nostr'
import type { SimplePool } from 'nostr-tools'
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
import type { SyncProgress } from './helpers/syncProgressHelper'
import { initializeSyncProgress, finalizeSync } from './helpers/syncProgressHelper'
@ -30,7 +31,7 @@ export async function syncUserContentToCache(
onProgress?: (progress: SyncProgress) => void
): Promise<void> {
try {
const pool = nostrService.getPool()
const pool: SimplePool | null = (nostrService as { getPool: () => SimplePool | null }).getPool()
if (!pool) {
const errorMsg = 'Pool not initialized, cannot sync user content'
console.warn(errorMsg)

View File

@ -72,10 +72,10 @@ async function executeWriteTask(task) {
await handleCreateNotification(data, id)
break
case 'LOG_PUBLICATION':
await handleLogPublication(data, id)
await handleLogPublication(data)
break
case 'WRITE_MULTI_TABLE':
await handleWriteMultiTable(data, id)
await handleWriteMultiTable(data)
break
default:
throw new Error(`Unknown message type: ${type}`)
@ -121,7 +121,7 @@ async function handleWriteObject(data, taskId) {
const store = transaction.objectStore('objects')
// Vérifier si l'objet existe déjà pour préserver published
const existing = await executeTransactionOperation(store, (s) => s.get(finalId)).catch((_e) => null)
const existing = await executeTransactionOperation(store, (s) => s.get(finalId)).catch(() => null)
// Préserver published si existant et non fourni
const finalPublished = existing && published === false ? existing.published : (published ?? false)
@ -218,7 +218,7 @@ async function handleUpdatePublished(data, taskId) {
* Handle write multi-table request
* Transactions multi-tables : plusieurs transactions, logique de découpage côté worker
*/
async function handleWriteMultiTable(data, _taskId) {
async function handleWriteMultiTable(data) {
const { writes } = data // Array of { objectType, hash, event, parsed, version, hidden, index, published }
try {
@ -251,7 +251,7 @@ async function handleWriteMultiTable(data, _taskId) {
finalId = `${hash}:${count}:${version}`
}
const existing = await executeTransactionOperation(store, (s) => s.get(finalId)).catch((_e) => null)
const existing = await executeTransactionOperation(store, (s) => s.get(finalId)).catch(() => null)
const finalPublished = existing && published === false ? existing.published : (published ?? false)
@ -302,7 +302,7 @@ async function handleCreateNotification(data, taskId) {
// Vérifier si la notification existe déjà
const index = store.index('eventId')
const existing = await executeTransactionOperation(index, (idx) => idx.get(eventId)).catch((_e) => null)
const existing = await executeTransactionOperation(index, (idx) => idx.get(eventId)).catch(() => null)
if (existing) {
// Notification déjà existante
@ -344,7 +344,7 @@ async function handleCreateNotification(data, taskId) {
/**
* Handle log publication request
*/
async function handleLogPublication(data, _taskId) {
async function handleLogPublication(data) {
const { eventId, relayUrl, success, error, objectType, objectId } = data
try {
@ -418,7 +418,7 @@ function openDB(objectType) {
* Open IndexedDB for notifications
*/
function openNotificationDB() {
return openIndexedDB('nostr_notifications', 1, (db, _event) => {
return openIndexedDB('nostr_notifications', 1, (db) => {
if (!db.objectStoreNames.contains('notifications')) {
const store = db.createObjectStore('notifications', { keyPath: 'id' })
store.createIndex('type', 'type', { unique: false })