nostr-idb 0.1.0
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/README.md +3 -0
- package/dist/common.d.ts +1 -0
- package/dist/common.js +2 -0
- package/dist/database.d.ts +6 -0
- package/dist/database.js +26 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/ingest.d.ts +8 -0
- package/dist/ingest.js +39 -0
- package/dist/query-filter.d.ts +10 -0
- package/dist/query-filter.js +128 -0
- package/dist/query-misc.d.ts +4 -0
- package/dist/query-misc.js +26 -0
- package/dist/schema.d.ts +43 -0
- package/dist/schema.js +1 -0
- package/package.json +32 -0
package/README.md
ADDED
package/dist/common.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const GENERIC_TAGS: string[];
|
package/dist/common.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DeleteDBCallbacks, OpenDBCallbacks } from "idb";
|
|
2
|
+
import { Schema } from "./schema.js";
|
|
3
|
+
export declare const NOSTR_IDB_NAME = "nostr-idb";
|
|
4
|
+
export declare const NOSTR_IDB_VERSION = 1;
|
|
5
|
+
export declare function openDatabase(name?: string, callbacks?: OpenDBCallbacks<Schema>): Promise<import("idb").IDBPDatabase<Schema>>;
|
|
6
|
+
export declare function deleteDatabase(name?: string, callbacks?: DeleteDBCallbacks): Promise<void>;
|
package/dist/database.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { deleteDB, openDB } from "idb";
|
|
2
|
+
export const NOSTR_IDB_NAME = "nostr-idb";
|
|
3
|
+
export const NOSTR_IDB_VERSION = 1;
|
|
4
|
+
export async function openDatabase(name = NOSTR_IDB_NAME, callbacks) {
|
|
5
|
+
return await openDB(name, NOSTR_IDB_VERSION, {
|
|
6
|
+
...callbacks,
|
|
7
|
+
upgrade(db, oldVersion, newVersion, transaction, event) {
|
|
8
|
+
const events = db.createObjectStore("events", { keyPath: "event.id" });
|
|
9
|
+
events.createIndex("id", "event.id", { unique: true });
|
|
10
|
+
events.createIndex("pubkey", "event.pubkey");
|
|
11
|
+
events.createIndex("kind", "event.kind");
|
|
12
|
+
events.createIndex("create_at", "event.created_at");
|
|
13
|
+
events.createIndex("tags", "tags", { multiEntry: true });
|
|
14
|
+
const seen = db.createObjectStore("seen", { keyPath: "id" });
|
|
15
|
+
seen.createIndex("date", "date");
|
|
16
|
+
seen.createIndex("relay", "relays", { multiEntry: true });
|
|
17
|
+
const used = db.createObjectStore("used", { keyPath: "id" });
|
|
18
|
+
used.createIndex("date", "date");
|
|
19
|
+
if (callbacks?.upgrade)
|
|
20
|
+
callbacks.upgrade(db, oldVersion, newVersion, transaction, event);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export async function deleteDatabase(name = NOSTR_IDB_NAME, callbacks) {
|
|
25
|
+
return await deleteDB(name, callbacks);
|
|
26
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/ingest.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IDBPTransaction } from "idb";
|
|
2
|
+
import { Event } from "nostr-tools";
|
|
3
|
+
import type { NostrIDB, Schema } from "./schema.js";
|
|
4
|
+
export declare function createWriteTransaction(db: NostrIDB): IDBPTransaction<Schema, ["events"], "readwrite">;
|
|
5
|
+
export declare function getEventTags(event: Event): string[];
|
|
6
|
+
export declare function addEvent(db: NostrIDB, event: Event, transaction?: IDBPTransaction<Schema, ["events"], "readwrite">): Promise<void>;
|
|
7
|
+
export declare function addEvents(db: NostrIDB, events: Event[]): Promise<void>;
|
|
8
|
+
export declare function updateUsed(db: NostrIDB, ids: string[]): Promise<void>;
|
package/dist/ingest.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { validateEvent } from "nostr-tools";
|
|
2
|
+
import { GENERIC_TAGS } from "./common.js";
|
|
3
|
+
export function createWriteTransaction(db) {
|
|
4
|
+
return db.transaction("events", "readwrite");
|
|
5
|
+
}
|
|
6
|
+
export function getEventTags(event) {
|
|
7
|
+
return event.tags
|
|
8
|
+
.filter((t) => t.length >= 2 && t[0].length === 1 && GENERIC_TAGS.includes(t[0]))
|
|
9
|
+
.map((t) => t[0] + t[1]);
|
|
10
|
+
}
|
|
11
|
+
export async function addEvent(db, event, transaction) {
|
|
12
|
+
if (!validateEvent(event))
|
|
13
|
+
throw new Error("Invalid Event");
|
|
14
|
+
const trans = transaction || createWriteTransaction(db);
|
|
15
|
+
trans.objectStore("events").put({
|
|
16
|
+
event,
|
|
17
|
+
tags: getEventTags(event),
|
|
18
|
+
});
|
|
19
|
+
if (!transaction)
|
|
20
|
+
await trans.commit();
|
|
21
|
+
}
|
|
22
|
+
export async function addEvents(db, events) {
|
|
23
|
+
const trans = db.transaction("events", "readwrite");
|
|
24
|
+
for (const event of events) {
|
|
25
|
+
await addEvent(db, event, trans);
|
|
26
|
+
}
|
|
27
|
+
await trans.commit();
|
|
28
|
+
}
|
|
29
|
+
export async function updateUsed(db, ids) {
|
|
30
|
+
const trans = db.transaction("used", "readwrite");
|
|
31
|
+
const nowUnix = Math.floor(new Date().valueOf() / 1000);
|
|
32
|
+
for (const id of ids) {
|
|
33
|
+
trans.objectStore("used").put({
|
|
34
|
+
id,
|
|
35
|
+
date: nowUnix,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
await trans.commit();
|
|
39
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Event, Filter } from "nostr-tools";
|
|
2
|
+
import type { NostrIDB } from "./schema.js";
|
|
3
|
+
export declare function queryForPubkeys(db: NostrIDB, authors?: Filter["authors"]): Promise<Set<string>>;
|
|
4
|
+
export declare function queryForTag(db: NostrIDB, tag: string, values: string[]): Promise<Set<string>>;
|
|
5
|
+
export declare function queryForKinds(db: NostrIDB, kinds?: Filter["kinds"]): Promise<Set<string>>;
|
|
6
|
+
export declare function queryForTime(db: NostrIDB, since: number | undefined, until: number | undefined): Promise<Set<string>>;
|
|
7
|
+
export declare function getIdsForFilter(db: NostrIDB, filter: Filter): Promise<Set<string>>;
|
|
8
|
+
export declare function getIdsForFilters(db: NostrIDB, filters: Filter[]): Promise<Set<string>>;
|
|
9
|
+
export declare function getEventsForFilters(db: NostrIDB, filters: Filter[]): Promise<Event<number>[]>;
|
|
10
|
+
export declare function countEventsForFilters(db: NostrIDB, filters: Filter[]): Promise<void>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { GENERIC_TAGS } from "./common.js";
|
|
2
|
+
export function queryForPubkeys(db, authors = []) {
|
|
3
|
+
const ids = new Set();
|
|
4
|
+
const trans = db.transaction("events", "readonly");
|
|
5
|
+
const objectStore = trans.objectStore("events");
|
|
6
|
+
const index = objectStore.index("pubkey");
|
|
7
|
+
const handleEvents = (result) => {
|
|
8
|
+
for (const id of result)
|
|
9
|
+
ids.add(id);
|
|
10
|
+
};
|
|
11
|
+
const promises = authors.map((pubkey) => index.getAllKeys(pubkey).then(handleEvents));
|
|
12
|
+
const result = Promise.all(promises).then(() => ids);
|
|
13
|
+
trans.commit();
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
export function queryForTag(db, tag, values) {
|
|
17
|
+
const ids = new Set();
|
|
18
|
+
const trans = db.transaction("events", "readonly");
|
|
19
|
+
const objectStore = trans.objectStore("events");
|
|
20
|
+
const index = objectStore.index("tags");
|
|
21
|
+
const handleEvents = (result) => {
|
|
22
|
+
for (const id of result)
|
|
23
|
+
ids.add(id);
|
|
24
|
+
};
|
|
25
|
+
const promises = values.map((v) => index.getAllKeys(tag + v).then(handleEvents));
|
|
26
|
+
const result = Promise.all(promises).then(() => ids);
|
|
27
|
+
trans.commit();
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
export function queryForKinds(db, kinds = []) {
|
|
31
|
+
const ids = new Set();
|
|
32
|
+
const trans = db.transaction("events", "readonly");
|
|
33
|
+
const objectStore = trans.objectStore("events");
|
|
34
|
+
const index = objectStore.index("kind");
|
|
35
|
+
const handleEvents = (result) => {
|
|
36
|
+
for (const id of result)
|
|
37
|
+
ids.add(id);
|
|
38
|
+
};
|
|
39
|
+
const promises = kinds.map((kind) => index.getAllKeys(kind).then(handleEvents));
|
|
40
|
+
const result = Promise.all(promises).then(() => ids);
|
|
41
|
+
trans.commit();
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
export async function queryForTime(db, since, until) {
|
|
45
|
+
let range;
|
|
46
|
+
if (since !== undefined && until !== undefined)
|
|
47
|
+
range = IDBKeyRange.bound(since, until);
|
|
48
|
+
else if (since !== undefined)
|
|
49
|
+
range = IDBKeyRange.lowerBound(since);
|
|
50
|
+
else if (until !== undefined)
|
|
51
|
+
range = IDBKeyRange.upperBound(until);
|
|
52
|
+
else
|
|
53
|
+
throw new Error("Missing since or until");
|
|
54
|
+
const arr = await db.getAllKeysFromIndex("events", "create_at", range);
|
|
55
|
+
const ids = new Set(arr);
|
|
56
|
+
return ids;
|
|
57
|
+
}
|
|
58
|
+
export async function getIdsForFilter(db, filter) {
|
|
59
|
+
if (filter.ids)
|
|
60
|
+
return new Set(filter.ids);
|
|
61
|
+
let ids = null;
|
|
62
|
+
const and = (set) => {
|
|
63
|
+
if (!ids)
|
|
64
|
+
ids = set;
|
|
65
|
+
for (const id of ids) {
|
|
66
|
+
if (!set.has(id))
|
|
67
|
+
ids.delete(id);
|
|
68
|
+
}
|
|
69
|
+
return ids;
|
|
70
|
+
};
|
|
71
|
+
// query for time first if both are set
|
|
72
|
+
if (filter.since && filter.until)
|
|
73
|
+
and(await queryForTime(db, filter.since, filter.until));
|
|
74
|
+
for (const t of GENERIC_TAGS) {
|
|
75
|
+
const key = `#${t}`;
|
|
76
|
+
const values = filter[key];
|
|
77
|
+
if (values?.length)
|
|
78
|
+
and(await queryForTag(db, t, values));
|
|
79
|
+
}
|
|
80
|
+
if (filter.authors)
|
|
81
|
+
and(await queryForPubkeys(db, filter.authors));
|
|
82
|
+
if (filter.kinds)
|
|
83
|
+
and(await queryForKinds(db, filter.kinds));
|
|
84
|
+
// query for time last if only one is set
|
|
85
|
+
if ((filter.since === undefined && filter.until) ||
|
|
86
|
+
(filter.since && filter.until === undefined))
|
|
87
|
+
and(await queryForTime(db, filter.since, filter.until));
|
|
88
|
+
if (ids === null)
|
|
89
|
+
throw new Error("Empty filter");
|
|
90
|
+
return ids;
|
|
91
|
+
}
|
|
92
|
+
export async function getIdsForFilters(db, filters) {
|
|
93
|
+
if (filters.length === 0)
|
|
94
|
+
throw new Error("No Filters");
|
|
95
|
+
let ids = null;
|
|
96
|
+
for (const filter of filters) {
|
|
97
|
+
const filterIds = await getIdsForFilter(db, filter);
|
|
98
|
+
if (!ids)
|
|
99
|
+
ids = filterIds;
|
|
100
|
+
else
|
|
101
|
+
for (const id of ids)
|
|
102
|
+
if (!filterIds.has(id))
|
|
103
|
+
ids.delete(id);
|
|
104
|
+
}
|
|
105
|
+
if (ids === null)
|
|
106
|
+
throw new Error("Empty filters");
|
|
107
|
+
return ids;
|
|
108
|
+
}
|
|
109
|
+
export async function getEventsForFilters(db, filters) {
|
|
110
|
+
const ids = await getIdsForFilters(db, filters);
|
|
111
|
+
const events = [];
|
|
112
|
+
const trans = db.transaction("events", "readonly");
|
|
113
|
+
const objectStore = trans.objectStore("events");
|
|
114
|
+
const index = objectStore.index("id");
|
|
115
|
+
const handleEntry = (e) => e && events.push(e.event);
|
|
116
|
+
const promises = Array.from(ids).map((id) => index.get(id).then(handleEntry));
|
|
117
|
+
const sorted = await Promise.all(promises).then(() => events.sort((a, b) => b.created_at - a.created_at));
|
|
118
|
+
trans.commit();
|
|
119
|
+
let minLimit = Infinity;
|
|
120
|
+
for (const filter of filters) {
|
|
121
|
+
if (filter.limit && filter.limit < minLimit)
|
|
122
|
+
minLimit = filter.limit;
|
|
123
|
+
}
|
|
124
|
+
if (sorted.length > minLimit)
|
|
125
|
+
sorted.length = minLimit;
|
|
126
|
+
return sorted;
|
|
127
|
+
}
|
|
128
|
+
export async function countEventsForFilters(db, filters) { }
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { NostrIDB } from "./schema.js";
|
|
2
|
+
export declare function countEventsByAllPubkeys(db: NostrIDB): Promise<Record<string, number>>;
|
|
3
|
+
export declare function countEventsByPubkey(db: NostrIDB, pubkey: string): Promise<number>;
|
|
4
|
+
export declare function countEvents(db: NostrIDB): Promise<number>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export async function countEventsByAllPubkeys(db) {
|
|
2
|
+
let cursor = await db
|
|
3
|
+
.transaction("events", "readonly")
|
|
4
|
+
.objectStore("events")
|
|
5
|
+
.index("pubkey")
|
|
6
|
+
.openKeyCursor();
|
|
7
|
+
if (!cursor)
|
|
8
|
+
return {};
|
|
9
|
+
const counts = {};
|
|
10
|
+
while (cursor) {
|
|
11
|
+
const pubkey = cursor.key;
|
|
12
|
+
counts[pubkey] = (counts[pubkey] || 0) + 1;
|
|
13
|
+
cursor = await cursor.continue();
|
|
14
|
+
}
|
|
15
|
+
return counts;
|
|
16
|
+
}
|
|
17
|
+
export async function countEventsByPubkey(db, pubkey) {
|
|
18
|
+
return await db
|
|
19
|
+
.transaction("events", "readonly")
|
|
20
|
+
.objectStore("events")
|
|
21
|
+
.index("pubkey")
|
|
22
|
+
.count(pubkey);
|
|
23
|
+
}
|
|
24
|
+
export function countEvents(db) {
|
|
25
|
+
return db.transaction("events", "readonly").store.count();
|
|
26
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DBSchema, IDBPDatabase } from "idb";
|
|
2
|
+
import type { Event } from "nostr-tools";
|
|
3
|
+
export type NostrIDB = IDBPDatabase<Schema>;
|
|
4
|
+
export interface Schema extends DBSchema {
|
|
5
|
+
events: {
|
|
6
|
+
key: "id";
|
|
7
|
+
value: {
|
|
8
|
+
event: Event;
|
|
9
|
+
tags: string[];
|
|
10
|
+
};
|
|
11
|
+
indexes: {
|
|
12
|
+
id: string;
|
|
13
|
+
pubkey: string;
|
|
14
|
+
kind: number;
|
|
15
|
+
create_at: number;
|
|
16
|
+
tags: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
seen: {
|
|
20
|
+
key: "id";
|
|
21
|
+
value: {
|
|
22
|
+
id: string;
|
|
23
|
+
date: number;
|
|
24
|
+
relays: string[];
|
|
25
|
+
};
|
|
26
|
+
indexes: {
|
|
27
|
+
id: string;
|
|
28
|
+
date: string;
|
|
29
|
+
relay: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
used: {
|
|
33
|
+
key: "id";
|
|
34
|
+
value: {
|
|
35
|
+
id: string;
|
|
36
|
+
date: number;
|
|
37
|
+
};
|
|
38
|
+
indexes: {
|
|
39
|
+
id: string;
|
|
40
|
+
date: string;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nostr-idb",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A collection of helper methods for storing nostr events in IndexedDB",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "vite",
|
|
14
|
+
"build": "tsc --project tsconfig.json"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"idb": "^8.0.0",
|
|
18
|
+
"nostr-tools": "^1.17.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/debug": "^4.1.12",
|
|
22
|
+
"@types/react": "^18.2.37",
|
|
23
|
+
"@types/react-dom": "^18.2.15",
|
|
24
|
+
"@vitejs/plugin-react-swc": "^3.5.0",
|
|
25
|
+
"dayjs": "^1.11.10",
|
|
26
|
+
"prettier": "^3.1.1",
|
|
27
|
+
"react": "^18.2.0",
|
|
28
|
+
"react-dom": "^18.2.0",
|
|
29
|
+
"typescript": "^5.3.3",
|
|
30
|
+
"vite": "^5.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|