Skip to content

Commit 8e714e7

Browse files
committed
v7.9.42
1 parent 3c0a1bb commit 8e714e7

5 files changed

Lines changed: 78 additions & 100 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nosflare",
3-
"version": "7.9.41",
3+
"version": "7.9.42",
44
"main": "worker.js",
55
"type": "module",
66
"scripts": {

src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const relayInfo: RelayInfo = {
2323
contact: "lux@fed.wtf",
2424
supported_nips: [1, 2, 4, 5, 9, 11, 12, 13, 15, 16, 17, 20, 22, 25, 28, 33, 40, 42, 57],
2525
software: "https://github.com/Spl0itable/nosflare",
26-
version: "7.9.41",
26+
version: "7.9.42",
2727
icon: "https://raw.githubusercontent.com/Spl0itable/nosflare/main/images/flare.png",
2828

2929
// Optional fields (uncomment as needed):

src/durable-object.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,9 @@ export class RelayWebSocket implements DurableObject {
701701

702702
console.log(`DO ${this.doName} received event ${event.id} from ${sourceDoId}`);
703703

704+
// Invalidate local query caches that match this event so stale results aren't served
705+
this.invalidateRelevantCaches(event);
706+
704707
// Broadcast to local sessions
705708
await this.broadcastToLocalSessions(event);
706709

src/relay-worker.ts

Lines changed: 72 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -639,18 +639,6 @@ function calculateQueryComplexity(filter: NostrFilter): number {
639639
// Event processing
640640
async function processEvent(event: NostrEvent, sessionId: string, env: Env): Promise<{ success: boolean; message: string; bookmark?: string }> {
641641
try {
642-
// Check cache for duplicate event ID using primary for consistency
643-
const session = env.RELAY_DATABASE.withSession('first-primary');
644-
const existingEvent = await session
645-
.prepare("SELECT id FROM events WHERE id = ? LIMIT 1")
646-
.bind(event.id)
647-
.first();
648-
649-
if (existingEvent) {
650-
console.log(`Duplicate event detected: ${event.id}`);
651-
return { success: false, message: "duplicate: already have this event", bookmark: session.getBookmark() ?? undefined };
652-
}
653-
654642
// NIP-05 validation if enabled (bypassed for kind 1059)
655643
if (event.kind !== 1059 && checkValidNip05 && event.kind !== 0) {
656644
const isValidNIP05 = await validateNIP05FromKind0(event.pubkey, env);
@@ -670,7 +658,7 @@ async function processEvent(event: NostrEvent, sessionId: string, env: Env): Pro
670658
return { success: true, message: "Ephemeral event broadcast" };
671659
}
672660

673-
// Save event directly to database
661+
// Save event directly to database (duplicate check happens inside saveEventToDatabase)
674662
return await saveEventToDatabase(event, env);
675663

676664
} catch (error: any) {
@@ -835,66 +823,54 @@ async function saveEventToDatabase(event: NostrEvent, env: Env): Promise<{ succe
835823
return { success: false, message: "duplicate: event already exists", bookmark: session.getBookmark() ?? undefined };
836824
}
837825

838-
// Only insert tags if the event was successfully inserted
839-
if (tagInserts.length > 0) {
840-
for (let j = 0; j < tagInserts.length; j += 50) {
841-
const tagChunk = tagInserts.slice(j, j + 50);
842-
const tagBatch = tagChunk.map(t =>
843-
session.prepare(`
844-
INSERT INTO tags (event_id, tag_name, tag_value)
845-
VALUES (?, ?, ?)
846-
`).bind(event.id, t.name, t.value)
847-
);
826+
// Consolidate all post-insert writes (tags, caches, content hash) into a single batch
827+
// to minimize D1 round-trips to the primary
828+
const postInsertBatch: ReturnType<typeof session.prepare>[] = [];
848829

849-
if (tagBatch.length > 0) {
850-
await session.batch(tagBatch);
851-
}
852-
}
830+
// Tag inserts
831+
for (const t of tagInserts) {
832+
postInsertBatch.push(
833+
session.prepare('INSERT INTO tags (event_id, tag_name, tag_value) VALUES (?, ?, ?)').bind(event.id, t.name, t.value)
834+
);
853835
}
854836

855-
// Update event tags cache for common tags (legacy single-value cache)
837+
// Event tags cache (legacy single-value cache)
856838
if (tagP || tagE || tagA) {
857-
await session.prepare(`
858-
INSERT INTO event_tags_cache (event_id, pubkey, kind, created_at, tag_p, tag_e, tag_a)
859-
VALUES (?, ?, ?, ?, ?, ?, ?)
860-
ON CONFLICT(event_id) DO UPDATE SET
861-
tag_p = excluded.tag_p,
862-
tag_e = excluded.tag_e,
863-
tag_a = excluded.tag_a
864-
`).bind(
865-
event.id,
866-
event.pubkey,
867-
event.kind,
868-
event.created_at,
869-
tagP,
870-
tagE,
871-
tagA
872-
).run();
873-
}
874-
875-
// Insert ALL p/e/a/t/d/r/L/s/u tags into multi-value cache
876-
// Chunk in batches of 50 to stay well under D1's 100 statement batch limit
839+
postInsertBatch.push(
840+
session.prepare(`
841+
INSERT INTO event_tags_cache (event_id, pubkey, kind, created_at, tag_p, tag_e, tag_a)
842+
VALUES (?, ?, ?, ?, ?, ?, ?)
843+
ON CONFLICT(event_id) DO UPDATE SET
844+
tag_p = excluded.tag_p, tag_e = excluded.tag_e, tag_a = excluded.tag_a
845+
`).bind(event.id, event.pubkey, event.kind, event.created_at, tagP, tagE, tagA)
846+
);
847+
}
848+
849+
// Multi-value tag cache for p/e/a/t/d/r/L/s/u tags
877850
const cacheableTags = tagInserts.filter(t => ['p', 'e', 'a', 't', 'd', 'r', 'L', 's', 'u'].includes(t.name));
878-
if (cacheableTags.length > 0) {
879-
for (let j = 0; j < cacheableTags.length; j += 50) {
880-
const cacheChunk = cacheableTags.slice(j, j + 50);
881-
const cacheBatch = cacheChunk.map(t =>
882-
session.prepare(`
883-
INSERT OR IGNORE INTO event_tags_cache_multi (event_id, pubkey, kind, created_at, tag_type, tag_value)
884-
VALUES (?, ?, ?, ?, ?, ?)
885-
`).bind(event.id, event.pubkey, event.kind, event.created_at, t.name, t.value)
886-
);
887-
await session.batch(cacheBatch);
888-
}
851+
for (const t of cacheableTags) {
852+
postInsertBatch.push(
853+
session.prepare(`
854+
INSERT OR IGNORE INTO event_tags_cache_multi (event_id, pubkey, kind, created_at, tag_type, tag_value)
855+
VALUES (?, ?, ?, ?, ?, ?)
856+
`).bind(event.id, event.pubkey, event.kind, event.created_at, t.name, t.value)
857+
);
889858
}
890859

891-
// Insert content hash if present
860+
// Content hash
892861
if (contentHash) {
893-
await session.prepare(`
894-
INSERT INTO content_hashes (hash, event_id, pubkey, created_at)
895-
VALUES (?, ?, ?, ?)
896-
ON CONFLICT(hash) DO NOTHING
897-
`).bind(contentHash, event.id, event.pubkey, event.created_at).run();
862+
postInsertBatch.push(
863+
session.prepare(`
864+
INSERT INTO content_hashes (hash, event_id, pubkey, created_at)
865+
VALUES (?, ?, ?, ?)
866+
ON CONFLICT(hash) DO NOTHING
867+
`).bind(contentHash, event.id, event.pubkey, event.created_at)
868+
);
869+
}
870+
871+
// Execute all post-insert writes in chunks of 50 (D1 batch limit is 100)
872+
for (let i = 0; i < postInsertBatch.length; i += 50) {
873+
await session.batch(postInsertBatch.slice(i, i + 50));
898874
}
899875

900876
// Cache the event ID in worker cache to prevent duplicates
@@ -927,58 +903,57 @@ async function processDeletionEvent(event: NostrEvent, env: Env): Promise<{ succ
927903

928904
let deletedCount = 0;
929905
const errors: string[] = [];
906+
const idsToDelete: string[] = [];
930907

908+
// First pass: verify ownership for all events (reads only)
931909
for (const eventId of deletedEventIds) {
932910
try {
933-
// Check if the event exists in D1
934911
const existing = await session.prepare(
935912
"SELECT pubkey FROM events WHERE id = ? LIMIT 1"
936913
).bind(eventId).first();
937914

938-
// If event doesn't exist in D1, skip (events in queue will be processed later)
939915
if (!existing) {
940916
console.warn(`Event ${eventId} not found in D1. Nothing to delete (may be in queue).`);
941917
continue;
942918
}
943919

944-
// Check ownership
945920
if (existing.pubkey !== event.pubkey) {
946921
console.warn(`Event ${eventId} does not belong to pubkey ${event.pubkey}. Skipping deletion.`);
947922
errors.push(`unauthorized: cannot delete event ${eventId} - wrong pubkey`);
948923
continue;
949924
}
950925

951-
// Delete from D1
952-
// Delete associated tags first (due to foreign key constraint)
953-
await session.prepare(
954-
"DELETE FROM tags WHERE event_id = ?"
955-
).bind(eventId).run();
956-
957-
// Delete from content_hashes if exists
958-
await session.prepare(
959-
"DELETE FROM content_hashes WHERE event_id = ?"
960-
).bind(eventId).run();
961-
962-
// Delete from event_tags_cache (both old and new)
963-
await session.prepare(
964-
"DELETE FROM event_tags_cache WHERE event_id = ?"
965-
).bind(eventId).run();
966-
await session.prepare(
967-
"DELETE FROM event_tags_cache_multi WHERE event_id = ?"
968-
).bind(eventId).run();
969-
970-
// Now delete the event
971-
const result = await session.prepare(
972-
"DELETE FROM events WHERE id = ?"
973-
).bind(eventId).run();
974-
975-
if (result.meta.changes > 0) {
976-
console.log(`Event ${eventId} deleted from D1.`);
977-
deletedCount++;
926+
idsToDelete.push(eventId);
927+
} catch (error) {
928+
console.error(`Error checking event ${eventId}:`, error);
929+
errors.push(`error checking ${eventId}`);
930+
}
931+
}
932+
933+
// Second pass: batch delete all verified events in one D1 call
934+
if (idsToDelete.length > 0) {
935+
try {
936+
const deleteStatements: ReturnType<typeof session.prepare>[] = [];
937+
for (const eventId of idsToDelete) {
938+
deleteStatements.push(
939+
session.prepare("DELETE FROM tags WHERE event_id = ?").bind(eventId),
940+
session.prepare("DELETE FROM content_hashes WHERE event_id = ?").bind(eventId),
941+
session.prepare("DELETE FROM event_tags_cache WHERE event_id = ?").bind(eventId),
942+
session.prepare("DELETE FROM event_tags_cache_multi WHERE event_id = ?").bind(eventId),
943+
session.prepare("DELETE FROM events WHERE id = ?").bind(eventId),
944+
);
945+
}
946+
947+
// Execute in chunks of 50 to stay under D1's 100 statement batch limit
948+
for (let i = 0; i < deleteStatements.length; i += 50) {
949+
await session.batch(deleteStatements.slice(i, i + 50));
978950
}
951+
952+
deletedCount = idsToDelete.length;
953+
console.log(`Batch deleted ${deletedCount} events from D1.`);
979954
} catch (error) {
980-
console.error(`Error deleting event ${eventId}:`, error);
981-
errors.push(`error deleting ${eventId}`);
955+
console.error('Error batch deleting events:', error);
956+
errors.push('error batch deleting events');
982957
}
983958
}
984959

worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ var relayInfo = {
8080
contact: "lux@fed.wtf",
8181
supported_nips: [1, 2, 4, 5, 9, 11, 12, 13, 15, 16, 17, 20, 22, 25, 28, 33, 40, 42, 57],
8282
software: "https://github.com/Spl0itable/nosflare",
83-
version: "7.9.41",
83+
version: "7.9.42",
8484
icon: "https://raw.githubusercontent.com/Spl0itable/nosflare/main/images/flare.png",
8585
// Optional fields (uncomment as needed):
8686
// banner: "https://example.com/banner.jpg",

0 commit comments

Comments
 (0)