Skip to content

Commit 8ed6a89

Browse files
feat: add streaming export of events to JSON Lines
1 parent 1d9b9a8 commit 8ed6a89

File tree

2 files changed

+53
-33
lines changed

2 files changed

+53
-33
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,17 @@ To see the integration test coverage report open `.coverage/integration/lcov-rep
543543
open .coverage/integration/lcov-report/index.html
544544
```
545545
546+
## Export Events
547+
548+
Export all stored events to a [JSON Lines](https://jsonlines.org/) (`.jsonl`) file. Each line is a valid NIP-01 Nostr event JSON object. The export streams rows from the database using cursors, so it works safely on relays with millions of events without loading them into memory.
549+
550+
```
551+
npm run export # writes to events.jsonl
552+
npm run export -- backup-2024-01-01.jsonl # custom filename
553+
```
554+
555+
The script reads the same `DB_*` environment variables used by the relay (see [CONFIGURATION.md](CONFIGURATION.md)).
556+
546557
## Configuration
547558
548559
You can change the default folder by setting the `NOSTR_CONFIG_DIR` environment variable to a different path.

src/scripts/export-events.ts

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,43 +40,52 @@ async function exportEvents(): Promise<void> {
4040
const output = fs.createWriteStream(outputPath)
4141
let exported = 0
4242

43-
const dbStream = db('events')
44-
.select(
45-
'event_id',
46-
'event_pubkey',
47-
'event_kind',
48-
'event_created_at',
49-
'event_content',
50-
'event_tags',
51-
'event_signature',
52-
)
53-
.whereNull('deleted_at')
54-
.orderBy('event_created_at', 'asc')
55-
.stream()
43+
const trx = await db.transaction(null, { isolationLevel: 'repeatable read' })
44+
try {
45+
await trx.raw('SET TRANSACTION READ ONLY')
46+
47+
const dbStream = trx('events')
48+
.select(
49+
'event_id',
50+
'event_pubkey',
51+
'event_kind',
52+
'event_created_at',
53+
'event_content',
54+
'event_tags',
55+
'event_signature',
56+
)
57+
.whereNull('deleted_at')
58+
.orderBy('event_created_at', 'asc')
59+
.stream()
5660

57-
const toJsonLine = new Transform({
58-
objectMode: true,
59-
transform(row: any, _encoding, callback) {
60-
const event = {
61-
id: row.event_id.toString('hex'),
62-
pubkey: row.event_pubkey.toString('hex'),
63-
created_at: row.event_created_at,
64-
kind: row.event_kind,
65-
tags: row.event_tags || [],
66-
content: row.event_content,
67-
sig: row.event_signature.toString('hex'),
68-
}
61+
const toJsonLine = new Transform({
62+
objectMode: true,
63+
transform(row: any, _encoding, callback) {
64+
const event = {
65+
id: row.event_id.toString('hex'),
66+
pubkey: row.event_pubkey.toString('hex'),
67+
created_at: row.event_created_at,
68+
kind: row.event_kind,
69+
tags: row.event_tags || [],
70+
content: row.event_content,
71+
sig: row.event_signature.toString('hex'),
72+
}
6973

70-
exported++
71-
if (exported % 10000 === 0) {
72-
console.log(`Exported ${exported}/${total} events...`)
73-
}
74+
exported++
75+
if (exported % 10000 === 0) {
76+
console.log(`Exported ${exported}/${total} events...`)
77+
}
7478

75-
callback(null, JSON.stringify(event) + '\n')
76-
},
77-
})
79+
callback(null, JSON.stringify(event) + '\n')
80+
},
81+
})
7882

79-
await pipeline(dbStream, toJsonLine, output)
83+
await pipeline(dbStream, toJsonLine, output)
84+
await trx.commit()
85+
} catch (err) {
86+
await trx.rollback()
87+
throw err
88+
}
8089

8190
console.log(`Export complete: ${exported} events written to ${outputPath}`)
8291
} finally {

0 commit comments

Comments
 (0)