applesauce-sqlite 0.0.0-next-20250916134023 → 0.0.0-next-20250919114711

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-sqlite",
3
- "version": "0.0.0-next-20250916134023",
3
+ "version": "0.0.0-next-20250919114711",
4
4
  "description": "sqlite event databases for applesauce",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -82,18 +82,18 @@
82
82
  }
83
83
  },
84
84
  "dependencies": {
85
- "applesauce-core": "0.0.0-next-20250916134023"
85
+ "applesauce-core": "0.0.0-next-20250919114711"
86
86
  },
87
87
  "optionalDependencies": {
88
- "better-sqlite3": "^12.2.0",
89
- "@libsql/client": "^0.15.15"
88
+ "@libsql/client": "^0.15.15",
89
+ "better-sqlite3": "^12.2.0"
90
90
  },
91
91
  "devDependencies": {
92
92
  "@hirez_io/observer-spy": "^2.2.0",
93
93
  "@types/better-sqlite3": "^7.6.13",
94
- "@types/bun": "^1.2.22",
95
94
  "@types/ws": "^8.5.13",
96
- "applesauce-signers": "0.0.0-next-20250916134023",
95
+ "applesauce-signers": "0.0.0-next-20250919114711",
96
+ "rimraf": "^6.0.1",
97
97
  "typescript": "^5.7.3",
98
98
  "vitest": "^3.2.4",
99
99
  "vitest-websocket-mock": "^0.5.0",
@@ -104,6 +104,7 @@
104
104
  "url": "lightning:nostrudel@geyser.fund"
105
105
  },
106
106
  "scripts": {
107
+ "prebuild": "rimraf dist",
107
108
  "build": "tsc",
108
109
  "watch:build": "tsc --watch > /dev/null",
109
110
  "test": "vitest run --passWithNoTests",
@@ -1,66 +0,0 @@
1
- import { Filter, NostrEvent } from "applesauce-core/helpers";
2
- import { Database } from "better-sqlite3";
3
- export declare const CREATE_EVENTS_TABLE = "\nCREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n kind INTEGER NOT NULL,\n pubkey TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n content TEXT NOT NULL,\n tags TEXT,\n sig TEXT NOT NULL,\n identifier TEXT NOT NULL DEFAULT ''\n);\n";
4
- export declare const CREATE_EVENT_TAGS_TABLE = "\nCREATE TABLE IF NOT EXISTS event_tags (\n event_id TEXT NOT NULL,\n tag_name TEXT NOT NULL,\n tag_value TEXT NOT NULL,\n FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE,\n PRIMARY KEY (event_id, tag_name, tag_value)\n);\n";
5
- export declare const CREATE_SEARCH_TABLE = "\nCREATE VIRTUAL TABLE IF NOT EXISTS events_search USING fts5(\n event_id UNINDEXED,\n content,\n kind UNINDEXED,\n pubkey UNINDEXED,\n created_at UNINDEXED\n);\n";
6
- export declare const CREATE_INDEXES: string[];
7
- export type EventRow = {
8
- id: string;
9
- kind: number;
10
- pubkey: string;
11
- created_at: number;
12
- content: string;
13
- tags: string;
14
- sig: string;
15
- };
16
- /** Filter with search field */
17
- export type FilterWithSearch = Filter & {
18
- search?: string;
19
- order?: "created_at" | "rank";
20
- };
21
- /** Content formatter function type for search indexing */
22
- export type SearchContentFormatter = (event: NostrEvent) => string;
23
- /** Default search content formatter - returns the raw content */
24
- export declare const defaultSearchContentFormatter: SearchContentFormatter;
25
- /** Enhanced search content formatter that includes tags and special handling for kind 0 events */
26
- export declare const enhancedSearchContentFormatter: SearchContentFormatter;
27
- /** Create and migrate the `events`, `event_tags`, and search tables */
28
- export declare function createTables(db: Database, search?: boolean): void;
29
- /** Inserts search content for an event */
30
- export declare function insertSearchContent(db: Database, event: NostrEvent, contentFormatter: SearchContentFormatter): void;
31
- /** Removes search content for an event */
32
- export declare function deleteSearchContent(db: Database, eventId: string): void;
33
- /** Inserts an event into the `events`, `event_tags`, and search tables of a database */
34
- export declare function insertEvent(db: Database, event: NostrEvent, contentFormatter?: SearchContentFormatter): boolean;
35
- /** Insert indexable tags for an event into the event_tags table */
36
- export declare function insertEventTags(db: Database, event: NostrEvent): void;
37
- /** Removes an event by id from the `events`, `event_tags`, and search tables of a database */
38
- export declare function deleteEvent(db: Database, id: string): boolean;
39
- /** Checks if an event exists */
40
- export declare function hasEvent(db: Database, id: string): boolean;
41
- /** Gets a single event from a database */
42
- export declare function getEvent(db: Database, id: string): NostrEvent | undefined;
43
- /** Gets the latest replaceable event from a database */
44
- export declare function getReplaceable(db: Database, kind: number, pubkey: string, identifier: string): NostrEvent | undefined;
45
- /** Gets the history of a replaceable event from a database */
46
- export declare function getReplaceableHistory(db: Database, kind: number, pubkey: string, identifier: string): NostrEvent[];
47
- /** Checks if a replaceable event exists in a database */
48
- export declare function hasReplaceable(db: Database, kind: number, pubkey: string, identifier?: string): boolean;
49
- /** Convert database row to NostrEvent */
50
- export declare function rowToEvent(row: EventRow): NostrEvent;
51
- /** Builds conditions for a single filter */
52
- export declare function buildFilterConditions(filter: FilterWithSearch): {
53
- conditions: string[];
54
- params: any[];
55
- search: boolean;
56
- };
57
- export declare function buildFiltersQuery(filters: FilterWithSearch | FilterWithSearch[]): {
58
- sql: string;
59
- params: any[];
60
- } | null;
61
- /** Get all events that match the filters (includes NIP-50 search support) */
62
- export declare function getEventsByFilters(db: Database, filters: FilterWithSearch | FilterWithSearch[]): Set<NostrEvent>;
63
- /** Search events using FTS5 full-text search (convenience wrapper around getEventsByFilters) */
64
- export declare function searchEvents(db: Database, search: string, options?: Filter): NostrEvent[];
65
- /** Rebuild the FTS5 search index for all events */
66
- export declare function rebuildSearchIndex(db: Database, contentFormatter: SearchContentFormatter): void;
@@ -1,367 +0,0 @@
1
- import { logger } from "applesauce-core";
2
- import { getIndexableTags, getReplaceableIdentifier } from "applesauce-core/helpers";
3
- const log = logger.extend("sqlite:tables");
4
- // SQL schema for Nostr events
5
- export const CREATE_EVENTS_TABLE = `
6
- CREATE TABLE IF NOT EXISTS events (
7
- id TEXT PRIMARY KEY,
8
- kind INTEGER NOT NULL,
9
- pubkey TEXT NOT NULL,
10
- created_at INTEGER NOT NULL,
11
- content TEXT NOT NULL,
12
- tags TEXT,
13
- sig TEXT NOT NULL,
14
- identifier TEXT NOT NULL DEFAULT ''
15
- );
16
- `;
17
- // SQL schema for event tags (for efficient tag filtering)
18
- export const CREATE_EVENT_TAGS_TABLE = `
19
- CREATE TABLE IF NOT EXISTS event_tags (
20
- event_id TEXT NOT NULL,
21
- tag_name TEXT NOT NULL,
22
- tag_value TEXT NOT NULL,
23
- FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE,
24
- PRIMARY KEY (event_id, tag_name, tag_value)
25
- );
26
- `;
27
- // SQL schema for FTS5 search table - stores formatted searchable content directly
28
- export const CREATE_SEARCH_TABLE = `
29
- CREATE VIRTUAL TABLE IF NOT EXISTS events_search USING fts5(
30
- event_id UNINDEXED,
31
- content,
32
- kind UNINDEXED,
33
- pubkey UNINDEXED,
34
- created_at UNINDEXED
35
- );
36
- `;
37
- export const CREATE_INDEXES = [
38
- // Events table indexes
39
- `CREATE INDEX IF NOT EXISTS kind_idx ON events(kind);`,
40
- `CREATE INDEX IF NOT EXISTS pubkey_idx ON events(pubkey);`,
41
- `CREATE INDEX IF NOT EXISTS created_at_idx ON events(created_at);`,
42
- `CREATE INDEX IF NOT EXISTS identifier_idx ON events(identifier);`,
43
- // Event tags table indexes for efficient tag filtering
44
- `CREATE INDEX IF NOT EXISTS event_tags_event_id_idx ON event_tags(event_id);`,
45
- `CREATE INDEX IF NOT EXISTS event_tags_name_value_idx ON event_tags(tag_name, tag_value);`,
46
- ];
47
- /** Default search content formatter - returns the raw content */
48
- export const defaultSearchContentFormatter = (event) => {
49
- return event.content;
50
- };
51
- /** Enhanced search content formatter that includes tags and special handling for kind 0 events */
52
- export const enhancedSearchContentFormatter = (event) => {
53
- let searchableContent = event.content;
54
- // Special handling for kind 0 (profile metadata) events
55
- if (event.kind === 0) {
56
- try {
57
- const profile = JSON.parse(event.content);
58
- const profileFields = [];
59
- // Include common profile fields in search
60
- if (profile.name)
61
- profileFields.push(profile.name);
62
- if (profile.display_name)
63
- profileFields.push(profile.display_name);
64
- if (profile.about)
65
- profileFields.push(profile.about);
66
- if (profile.nip05)
67
- profileFields.push(profile.nip05);
68
- if (profile.lud16)
69
- profileFields.push(profile.lud16);
70
- searchableContent = profileFields.join(" ");
71
- }
72
- catch (e) {
73
- // If JSON parsing fails, use the raw content
74
- searchableContent = event.content;
75
- }
76
- }
77
- // Include relevant tags in the searchable content
78
- const relevantTags = ["t", "subject", "title", "summary", "d"]; // hashtags, subject, title, summary, identifier
79
- const tagContent = [];
80
- for (const tag of event.tags) {
81
- if (tag.length >= 2 && relevantTags.includes(tag[0])) {
82
- tagContent.push(tag[1]);
83
- }
84
- }
85
- // Combine content with tag content
86
- if (tagContent.length > 0) {
87
- searchableContent += " " + tagContent.join(" ");
88
- }
89
- return searchableContent;
90
- };
91
- /** Create and migrate the `events`, `event_tags`, and search tables */
92
- export function createTables(db, search = true) {
93
- // Create the events table
94
- log("Creating events table");
95
- db.exec(CREATE_EVENTS_TABLE);
96
- // Create the event_tags table
97
- log("Creating event_tags table");
98
- db.exec(CREATE_EVENT_TAGS_TABLE);
99
- // Create the FTS5 search table
100
- if (search) {
101
- log("Creating events_search FTS5 table");
102
- db.exec(CREATE_SEARCH_TABLE);
103
- }
104
- // Create indexes
105
- log("Creating indexes");
106
- CREATE_INDEXES.forEach((indexSql) => {
107
- db.exec(indexSql);
108
- });
109
- }
110
- /** Inserts search content for an event */
111
- export function insertSearchContent(db, event, contentFormatter) {
112
- const searchableContent = contentFormatter(event);
113
- // Insert/update directly into the FTS5 table
114
- const stmt = db.prepare(`
115
- INSERT OR REPLACE INTO events_search (event_id, content, kind, pubkey, created_at)
116
- VALUES (?, ?, ?, ?, ?)
117
- `);
118
- stmt.run(event.id, searchableContent, event.kind, event.pubkey, event.created_at);
119
- }
120
- /** Removes search content for an event */
121
- export function deleteSearchContent(db, eventId) {
122
- const stmt = db.prepare(`DELETE FROM events_search WHERE event_id = ?`);
123
- stmt.run(eventId);
124
- }
125
- /** Inserts an event into the `events`, `event_tags`, and search tables of a database */
126
- export function insertEvent(db, event, contentFormatter) {
127
- const identifier = getReplaceableIdentifier(event);
128
- return db.transaction(() => {
129
- // Insert/update the main event
130
- const stmt = db.prepare(`
131
- INSERT OR REPLACE INTO events (id, kind, pubkey, created_at, content, tags, sig, identifier)
132
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
133
- `);
134
- const result = stmt.run(event.id, event.kind, event.pubkey, event.created_at, event.content, JSON.stringify(event.tags), event.sig, identifier);
135
- // Insert indexable tags into the event_tags table
136
- insertEventTags(db, event);
137
- // Insert searchable content into the search tables
138
- if (contentFormatter)
139
- insertSearchContent(db, event, contentFormatter);
140
- return result.changes > 0;
141
- })();
142
- }
143
- /** Insert indexable tags for an event into the event_tags table */
144
- export function insertEventTags(db, event) {
145
- // Clear existing tags for this event first
146
- const deleteStmt = db.prepare(`DELETE FROM event_tags WHERE event_id = ?`);
147
- deleteStmt.run(event.id);
148
- // Get only the indexable tags using applesauce-core helper
149
- const indexableTags = getIndexableTags(event);
150
- if (indexableTags && indexableTags.size > 0) {
151
- const insertStmt = db.prepare(`INSERT OR IGNORE INTO event_tags (event_id, tag_name, tag_value) VALUES (?, ?, ?)`);
152
- for (const tagString of indexableTags) {
153
- // Parse the "tagName:tagValue" format
154
- const [name, value] = tagString.split(":");
155
- if (name && value)
156
- insertStmt.run(event.id, name, value);
157
- }
158
- }
159
- }
160
- /** Removes an event by id from the `events`, `event_tags`, and search tables of a database */
161
- export function deleteEvent(db, id) {
162
- return db.transaction(() => {
163
- // Delete from event_tags first (foreign key constraint)
164
- const deleteTagsStmt = db.prepare(`DELETE FROM event_tags WHERE event_id = ?`);
165
- deleteTagsStmt.run(id);
166
- // Delete from search tables
167
- deleteSearchContent(db, id);
168
- // Delete from events table
169
- const deleteEventStmt = db.prepare(`DELETE FROM events WHERE id = ?`);
170
- const result = deleteEventStmt.run(id);
171
- return result.changes > 0;
172
- })();
173
- }
174
- /** Checks if an event exists */
175
- export function hasEvent(db, id) {
176
- const stmt = db.prepare(`SELECT COUNT(*) as count FROM events WHERE id = ?`);
177
- const result = stmt.get(id);
178
- if (!result)
179
- return false;
180
- return result.count > 0;
181
- }
182
- /** Gets a single event from a database */
183
- export function getEvent(db, id) {
184
- const stmt = db.prepare(`SELECT * FROM events WHERE id = ?`);
185
- const row = stmt.get(id);
186
- return row && rowToEvent(row);
187
- }
188
- /** Gets the latest replaceable event from a database */
189
- export function getReplaceable(db, kind, pubkey, identifier) {
190
- const stmt = db.prepare(`SELECT * FROM events WHERE kind = ? AND pubkey = ? AND identifier = ? ORDER BY created_at DESC LIMIT 1`);
191
- const row = stmt.get(kind, pubkey, identifier);
192
- return row && rowToEvent(row);
193
- }
194
- /** Gets the history of a replaceable event from a database */
195
- export function getReplaceableHistory(db, kind, pubkey, identifier) {
196
- const stmt = db.prepare(`SELECT * FROM events WHERE kind = ? AND pubkey = ? AND identifier = ? ORDER BY created_at DESC`);
197
- return stmt.all(kind, pubkey, identifier).map(rowToEvent);
198
- }
199
- /** Checks if a replaceable event exists in a database */
200
- export function hasReplaceable(db, kind, pubkey, identifier = "") {
201
- const stmt = db.prepare(`SELECT COUNT(*) as count FROM events WHERE kind = ? AND pubkey = ? AND identifier = ?`);
202
- const result = stmt.get(kind, pubkey, identifier);
203
- if (!result)
204
- return false;
205
- return result.count > 0;
206
- }
207
- /** Convert database row to NostrEvent */
208
- export function rowToEvent(row) {
209
- return {
210
- id: row.id,
211
- kind: row.kind,
212
- pubkey: row.pubkey,
213
- created_at: row.created_at,
214
- content: row.content,
215
- tags: JSON.parse(row.tags || "[]"),
216
- sig: row.sig,
217
- };
218
- }
219
- /** Builds conditions for a single filter */
220
- export function buildFilterConditions(filter) {
221
- const conditions = [];
222
- const params = [];
223
- let search = false;
224
- // Handle NIP-50 search filter
225
- if (filter.search && filter.search.trim()) {
226
- conditions.push(`events_search MATCH ?`);
227
- params.push(filter.search.trim());
228
- search = true;
229
- }
230
- // Handle IDs filter
231
- if (filter.ids && filter.ids.length > 0) {
232
- const placeholders = filter.ids.map(() => `?`).join(", ");
233
- conditions.push(`events.id IN (${placeholders})`);
234
- params.push(...filter.ids);
235
- }
236
- // Handle kinds filter
237
- if (filter.kinds && filter.kinds.length > 0) {
238
- const placeholders = filter.kinds.map(() => `?`).join(", ");
239
- conditions.push(`events.kind IN (${placeholders})`);
240
- params.push(...filter.kinds);
241
- }
242
- // Handle authors filter (pubkeys)
243
- if (filter.authors && filter.authors.length > 0) {
244
- const placeholders = filter.authors.map(() => `?`).join(", ");
245
- conditions.push(`events.pubkey IN (${placeholders})`);
246
- params.push(...filter.authors);
247
- }
248
- // Handle since filter (timestamp >= since)
249
- if (filter.since !== undefined) {
250
- conditions.push(`events.created_at >= ?`);
251
- params.push(filter.since);
252
- }
253
- // Handle until filter (timestamp <= until)
254
- if (filter.until !== undefined) {
255
- conditions.push(`events.created_at <= ?`);
256
- params.push(filter.until);
257
- }
258
- // Handle tag filters (e.g., #e, #p, #t, #d, etc.)
259
- for (const [key, values] of Object.entries(filter)) {
260
- if (key.startsWith("#") && values && Array.isArray(values) && values.length > 0) {
261
- const tagName = key.slice(1); // Remove the '#' prefix
262
- // Use the event_tags table for efficient tag filtering
263
- const placeholders = values.map(() => "?").join(", ");
264
- conditions.push(`events.id IN (
265
- SELECT DISTINCT event_id
266
- FROM event_tags
267
- WHERE tag_name = ? AND tag_value IN (${placeholders})
268
- )`);
269
- // Add parameters: tagName first, then all the tag values
270
- params.push(tagName, ...values);
271
- }
272
- }
273
- return { conditions, params, search };
274
- }
275
- export function buildFiltersQuery(filters) {
276
- const filterArray = Array.isArray(filters) ? filters : [filters];
277
- if (filterArray.length === 0)
278
- return null;
279
- // Build queries for each filter (OR logic between filters)
280
- const filterQueries = [];
281
- const allParams = [];
282
- let globalLimit;
283
- // Build the final query with proper ordering and limit
284
- let fromClause = "events";
285
- let orderBy = "events.created_at DESC, events.id ASC";
286
- for (const filter of filterArray) {
287
- const { conditions, params, search } = buildFilterConditions(filter);
288
- if (search) {
289
- // Override the from clause to join the events_search table
290
- fromClause = "events INNER JOIN events_search ON events.id = events_search.event_id";
291
- // Set the order by clause based on the filter order
292
- switch (filter.order) {
293
- case "created_at":
294
- orderBy = "events.created_at DESC, events.id ASC";
295
- break;
296
- case "rank":
297
- orderBy = "events_search.rank, events.created_at DESC";
298
- break;
299
- }
300
- }
301
- if (conditions.length === 0) {
302
- // If no conditions, this filter matches all events
303
- filterQueries.push("1=1");
304
- }
305
- else {
306
- // AND logic within a single filter
307
- filterQueries.push(`(${conditions.join(" AND ")})`);
308
- }
309
- allParams.push(...params);
310
- // Track the most restrictive limit across all filters
311
- if (filter.limit !== undefined) {
312
- globalLimit = globalLimit === undefined ? filter.limit : Math.min(globalLimit, filter.limit);
313
- }
314
- }
315
- // Combine all filter conditions with OR logic
316
- const whereClause = filterQueries.length > 0 ? `WHERE ${filterQueries.join(" OR ")}` : "";
317
- let query = `
318
- SELECT DISTINCT events.* FROM ${fromClause}
319
- ${whereClause}
320
- ORDER BY ${orderBy}
321
- `;
322
- // Apply global limit if specified
323
- if (globalLimit !== undefined && globalLimit > 0) {
324
- query += ` LIMIT ?`;
325
- allParams.push(globalLimit);
326
- }
327
- return { sql: query, params: allParams };
328
- }
329
- /** Get all events that match the filters (includes NIP-50 search support) */
330
- export function getEventsByFilters(db, filters) {
331
- const query = buildFiltersQuery(filters);
332
- if (!query)
333
- return new Set();
334
- const eventSet = new Set();
335
- const stmt = db.prepare(query.sql);
336
- const rows = stmt.all(...query.params);
337
- // Convert rows to events and add to set
338
- for (const row of rows)
339
- eventSet.add(rowToEvent(row));
340
- return eventSet;
341
- }
342
- /** Search events using FTS5 full-text search (convenience wrapper around getEventsByFilters) */
343
- export function searchEvents(db, search, options) {
344
- if (!search.trim())
345
- return [];
346
- // Build filter with search and other options
347
- const filter = {
348
- search: search.trim(),
349
- ...options,
350
- };
351
- // Use the main filter system which now supports search
352
- const results = getEventsByFilters(db, filter);
353
- return Array.from(results);
354
- }
355
- /** Rebuild the FTS5 search index for all events */
356
- export function rebuildSearchIndex(db, contentFormatter) {
357
- db.transaction(() => {
358
- // Clear existing search data
359
- db.exec(`DELETE FROM events_search;`);
360
- // Rebuild from all events
361
- const stmt = db.prepare(`SELECT * FROM events`);
362
- const events = stmt.all().map(rowToEvent);
363
- for (const event of events) {
364
- insertSearchContent(db, event, contentFormatter);
365
- }
366
- })();
367
- }
@@ -1,53 +0,0 @@
1
- import { IEventDatabase } from "applesauce-core";
2
- import { Filter, NostrEvent } from "applesauce-core/helpers";
3
- import { type Database as TDatabase } from "better-sqlite3";
4
- import { defaultSearchContentFormatter, enhancedSearchContentFormatter, type SearchContentFormatter } from "./helpers/sqlite.js";
5
- export { defaultSearchContentFormatter, enhancedSearchContentFormatter, type SearchContentFormatter };
6
- /** Options for the SqliteEventDatabase */
7
- export type SqliteEventDatabaseOptions = {
8
- search?: boolean;
9
- searchContentFormatter?: SearchContentFormatter;
10
- };
11
- export declare class SqliteEventDatabase implements IEventDatabase {
12
- db: TDatabase;
13
- /** If search is enabled */
14
- private search;
15
- /** The search content formatter */
16
- private searchContentFormatter;
17
- constructor(database?: string | TDatabase, options?: SqliteEventDatabaseOptions);
18
- /** Store a Nostr event in the database */
19
- add(event: NostrEvent): NostrEvent;
20
- /** Delete an event by ID */
21
- remove(id: string): boolean;
22
- /** Checks if an event exists */
23
- hasEvent(id: string): boolean;
24
- /** Get an event by its ID */
25
- getEvent(id: string): NostrEvent | undefined;
26
- /** Get the latest replaceable event For replaceable events (10000-19999), returns the most recent event */
27
- getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
28
- /** Checks if a replaceable event exists */
29
- hasReplaceable(kind: number, pubkey: string, identifier?: string): boolean;
30
- /** Returns all the versions of a replaceable event */
31
- getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[];
32
- /** Get all events that match the filters (supports NIP-50 search field) */
33
- getByFilters(filters: (Filter & {
34
- search?: string;
35
- }) | (Filter & {
36
- search?: string;
37
- })[]): Set<NostrEvent>;
38
- /** Get a timeline of events that match the filters (returns array in chronological order, supports NIP-50 search) */
39
- getTimeline(filters: (Filter & {
40
- search?: string;
41
- }) | (Filter & {
42
- search?: string;
43
- })[]): NostrEvent[];
44
- /** Set the search content formatter */
45
- setSearchContentFormatter(formatter: SearchContentFormatter): void;
46
- /** Get the current search content formatter */
47
- getSearchContentFormatter(): SearchContentFormatter;
48
- /** Rebuild the search index for all events */
49
- rebuildSearchIndex(): void;
50
- /** Close the database connection */
51
- close(): void;
52
- [Symbol.dispose](): void;
53
- }
@@ -1,105 +0,0 @@
1
- import { logger } from "applesauce-core";
2
- import { insertEventIntoDescendingList } from "applesauce-core/helpers";
3
- import Database from "better-sqlite3";
4
- import { createTables, defaultSearchContentFormatter, deleteEvent, enhancedSearchContentFormatter, getEvent, getEventsByFilters, getReplaceable, getReplaceableHistory, hasEvent, hasReplaceable, insertEvent, rebuildSearchIndex, } from "./helpers/sqlite.js";
5
- const log = logger.extend("SqliteEventDatabase");
6
- // Export the search content formatters and types for external use
7
- export { defaultSearchContentFormatter, enhancedSearchContentFormatter };
8
- export class SqliteEventDatabase {
9
- db;
10
- /** If search is enabled */
11
- search;
12
- /** The search content formatter */
13
- searchContentFormatter;
14
- constructor(database = ":memory:", options) {
15
- this.db = typeof database === "string" ? new Database(database) : database;
16
- this.search = options?.search ?? true;
17
- this.searchContentFormatter = options?.searchContentFormatter ?? enhancedSearchContentFormatter;
18
- // Setup the database tables and indexes
19
- createTables(this.db, this.search);
20
- }
21
- /** Store a Nostr event in the database */
22
- add(event) {
23
- try {
24
- insertEvent(this.db, event, this.search ? this.searchContentFormatter : undefined);
25
- return event;
26
- }
27
- catch (error) {
28
- log("Error inserting event:", error);
29
- throw error;
30
- }
31
- }
32
- /** Delete an event by ID */
33
- remove(id) {
34
- try {
35
- // Remove event from database
36
- return deleteEvent(this.db, id);
37
- }
38
- catch (error) {
39
- return false;
40
- }
41
- }
42
- /** Checks if an event exists */
43
- hasEvent(id) {
44
- return hasEvent(this.db, id);
45
- }
46
- /** Get an event by its ID */
47
- getEvent(id) {
48
- return getEvent(this.db, id);
49
- }
50
- /** Get the latest replaceable event For replaceable events (10000-19999), returns the most recent event */
51
- getReplaceable(kind, pubkey, identifier = "") {
52
- return getReplaceable(this.db, kind, pubkey, identifier);
53
- }
54
- /** Checks if a replaceable event exists */
55
- hasReplaceable(kind, pubkey, identifier = "") {
56
- return hasReplaceable(this.db, kind, pubkey, identifier);
57
- }
58
- /** Returns all the versions of a replaceable event */
59
- getReplaceableHistory(kind, pubkey, identifier = "") {
60
- return getReplaceableHistory(this.db, kind, pubkey, identifier);
61
- }
62
- /** Get all events that match the filters (supports NIP-50 search field) */
63
- getByFilters(filters) {
64
- try {
65
- // If search is disabled, remove the search field from the filters
66
- if (!this.search && (Array.isArray(filters) ? filters.some((f) => "search" in f) : "search" in filters))
67
- throw new Error("Search is disabled");
68
- return getEventsByFilters(this.db, filters);
69
- }
70
- catch (error) {
71
- return new Set();
72
- }
73
- }
74
- /** Get a timeline of events that match the filters (returns array in chronological order, supports NIP-50 search) */
75
- getTimeline(filters) {
76
- const events = this.getByFilters(filters);
77
- const timeline = [];
78
- for (const event of events)
79
- insertEventIntoDescendingList(timeline, event);
80
- return timeline;
81
- }
82
- /** Set the search content formatter */
83
- setSearchContentFormatter(formatter) {
84
- this.searchContentFormatter = formatter;
85
- }
86
- /** Get the current search content formatter */
87
- getSearchContentFormatter() {
88
- return this.searchContentFormatter;
89
- }
90
- /** Rebuild the search index for all events */
91
- rebuildSearchIndex() {
92
- if (!this.search)
93
- throw new Error("Search is disabled");
94
- rebuildSearchIndex(this.db, this.searchContentFormatter);
95
- log("Search index rebuilt successfully");
96
- }
97
- /** Close the database connection */
98
- close() {
99
- log("Closing database connection");
100
- this.db.close();
101
- }
102
- [Symbol.dispose]() {
103
- this.close();
104
- }
105
- }