@massalabs/gossip-sdk 0.0.2-dev.20260220143015 → 0.0.2-dev.20260223101931
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 +33 -5
- package/dist/core/SdkEventEmitter.d.ts +2 -0
- package/dist/core/SdkEventEmitter.js +2 -0
- package/dist/{db.d.ts → db/db.d.ts} +14 -3
- package/dist/{db.js → db/db.js} +17 -8
- package/dist/db/exec-utils.d.ts +19 -0
- package/dist/db/exec-utils.js +48 -0
- package/dist/db/generated-ddl.d.ts +1 -0
- package/dist/db/generated-ddl.js +31 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.js +3 -0
- package/dist/db/queries/activeSeekers.d.ts +1 -0
- package/dist/{queries → db/queries}/activeSeekers.js +0 -5
- package/dist/{queries → db/queries}/messages.d.ts +2 -2
- package/dist/{queries → db/queries}/messages.js +3 -3
- package/dist/{queries → db/queries}/userProfile.d.ts +1 -1
- package/dist/{queries → db/queries}/userProfile.js +1 -1
- package/dist/db/schema/_helpers.d.ts +26 -0
- package/dist/db/schema/_helpers.js +15 -0
- package/dist/db/schema/activeSeekers.d.ts +43 -0
- package/dist/db/schema/activeSeekers.js +6 -0
- package/dist/db/schema/announcementCursors.d.ts +45 -0
- package/dist/db/schema/announcementCursors.js +5 -0
- package/dist/db/schema/contacts.d.ts +170 -0
- package/dist/db/schema/contacts.js +16 -0
- package/dist/db/schema/discussions.d.ts +336 -0
- package/dist/db/schema/discussions.js +31 -0
- package/dist/db/schema/index.d.ts +8 -0
- package/dist/db/schema/index.js +8 -0
- package/dist/db/schema/messages.d.ts +309 -0
- package/dist/db/schema/messages.js +29 -0
- package/dist/db/schema/pendingAnnouncements.d.ts +79 -0
- package/dist/db/schema/pendingAnnouncements.js +11 -0
- package/dist/db/schema/pendingEncryptedMessages.d.ts +79 -0
- package/dist/db/schema/pendingEncryptedMessages.js +11 -0
- package/dist/db/schema/userProfile.d.ts +208 -0
- package/dist/db/schema/userProfile.js +20 -0
- package/dist/{sqlite-worker.js → db/sqlite-worker.js} +2 -30
- package/dist/db/sqlite.d.ts +77 -0
- package/dist/db/sqlite.js +254 -0
- package/dist/gossip.d.ts +4 -55
- package/dist/gossip.js +35 -51
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/services/announcement.js +1 -1
- package/dist/services/discussion.js +1 -1
- package/dist/services/message.d.ts +1 -1
- package/dist/services/message.js +2 -3
- package/dist/services/refresh.js +1 -1
- package/dist/utils/contacts.d.ts +11 -0
- package/dist/utils/contacts.js +30 -2
- package/dist/utils/discussions.d.ts +1 -1
- package/dist/utils/discussions.js +1 -1
- package/dist/utils/validation.js +1 -1
- package/package.json +2 -1
- package/dist/contacts.d.ts +0 -120
- package/dist/contacts.js +0 -160
- package/dist/queries/activeSeekers.d.ts +0 -2
- package/dist/schema.d.ts +0 -1280
- package/dist/schema.js +0 -164
- package/dist/sqlite.d.ts +0 -79
- package/dist/sqlite.js +0 -448
- /package/dist/{queries → db/queries}/announcementCursors.d.ts +0 -0
- /package/dist/{queries → db/queries}/announcementCursors.js +0 -0
- /package/dist/{queries → db/queries}/contacts.d.ts +0 -0
- /package/dist/{queries → db/queries}/contacts.js +0 -0
- /package/dist/{queries → db/queries}/discussions.d.ts +0 -0
- /package/dist/{queries → db/queries}/discussions.js +0 -0
- /package/dist/{queries → db/queries}/index.d.ts +0 -0
- /package/dist/{queries → db/queries}/index.js +0 -0
- /package/dist/{queries → db/queries}/pendingAnnouncements.d.ts +0 -0
- /package/dist/{queries → db/queries}/pendingAnnouncements.js +0 -0
- /package/dist/{sqlite-worker.d.ts → db/sqlite-worker.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -33,6 +33,11 @@ await sdk.init();
|
|
|
33
33
|
// 2. Open session (login)
|
|
34
34
|
await sdk.openSession({
|
|
35
35
|
mnemonic: 'word1 word2 word3 ... word12',
|
|
36
|
+
// Optional: for existing session
|
|
37
|
+
// encryptedSession: savedBlob,
|
|
38
|
+
// encryptionKey, // optional - derived from mnemonic if not provided
|
|
39
|
+
// Optional: for persistence
|
|
40
|
+
// onPersist: async (blob, key) => { await saveToStorage(blob, key); },
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
// 3. Use the SDK
|
|
@@ -308,12 +313,12 @@ await sdk.init({
|
|
|
308
313
|
|
|
309
314
|
## Session Persistence
|
|
310
315
|
|
|
311
|
-
For restoring sessions across app restarts:
|
|
316
|
+
For restoring sessions across app restarts, pass `encryptionKey` (optional — derived from mnemonic if omitted) and `onPersist` when opening the session:
|
|
312
317
|
|
|
313
318
|
```typescript
|
|
314
319
|
await sdk.openSession({
|
|
315
320
|
mnemonic,
|
|
316
|
-
encryptionKey,
|
|
321
|
+
encryptionKey, // optional
|
|
317
322
|
onPersist: async (blob, key) => {
|
|
318
323
|
await storage.save({ session: blob });
|
|
319
324
|
},
|
|
@@ -345,6 +350,26 @@ import type {
|
|
|
345
350
|
import { SessionStatus, SdkEventType } from '@massalabs/gossip-sdk';
|
|
346
351
|
```
|
|
347
352
|
|
|
353
|
+
## Database
|
|
354
|
+
|
|
355
|
+
SQLite via [wa-sqlite](https://github.com/nicolo-ribaudo/wa-sqlite) (WASM) with [Drizzle ORM](https://orm.drizzle.team). Data is persisted to IndexedDB using `IDBBatchAtomicVFS`.
|
|
356
|
+
|
|
357
|
+
### Schema
|
|
358
|
+
|
|
359
|
+
Schema is defined in `src/db/schema/` with one file per table. Drizzle-kit generates SQL migrations from the schema.
|
|
360
|
+
|
|
361
|
+
### Migrations
|
|
362
|
+
|
|
363
|
+
Migrations live in `drizzle/` and are applied automatically on `initDb()` via Drizzle's built-in migrator.
|
|
364
|
+
|
|
365
|
+
When you change the schema, regenerate migrations:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
npm run db:generate
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
This runs `drizzle-kit generate`, which diffs the schema against existing migrations and outputs a new `.sql` file in `drizzle/`. Commit the generated migration alongside your schema change.
|
|
372
|
+
|
|
348
373
|
## Testing
|
|
349
374
|
|
|
350
375
|
```bash
|
|
@@ -358,10 +383,14 @@ Tests use wa-sqlite with in-memory databases for fast, isolated execution.
|
|
|
358
383
|
|
|
359
384
|
```
|
|
360
385
|
gossip-sdk/
|
|
386
|
+
├── drizzle/ # Generated SQL migrations
|
|
361
387
|
├── src/
|
|
362
388
|
│ ├── gossipSdk.ts # SDK class & factory
|
|
363
|
-
│ ├── db
|
|
364
|
-
│ ├──
|
|
389
|
+
│ ├── db/
|
|
390
|
+
│ │ ├── index.ts # Barrel export (all DB access goes through here)
|
|
391
|
+
│ │ ├── schema/ # Drizzle table definitions (one file per table)
|
|
392
|
+
│ │ ├── queries/ # Query functions
|
|
393
|
+
│ │ └── sqlite.ts # SQLite init, migration, connection
|
|
365
394
|
│ ├── api/
|
|
366
395
|
│ │ └── messageProtocol/ # REST protocol implementation
|
|
367
396
|
│ ├── config/
|
|
@@ -376,7 +405,6 @@ gossip-sdk/
|
|
|
376
405
|
│ │ ├── discussion.ts # Discussion service
|
|
377
406
|
│ │ ├── announcement.ts # Announcement service
|
|
378
407
|
│ │ └── refresh.ts # Session refresh service
|
|
379
|
-
│ ├── types/ # Type definitions
|
|
380
408
|
│ ├── utils/ # Utility modules
|
|
381
409
|
│ └── wasm/ # WASM module wrappers
|
|
382
410
|
└── test/ # Test files
|
|
@@ -13,6 +13,7 @@ export declare enum SdkEventType {
|
|
|
13
13
|
SESSION_CREATED = "sessionCreated",
|
|
14
14
|
SESSION_RENEWED = "sessionRenewed",
|
|
15
15
|
SESSION_ACCEPTED = "sessionAccepted",
|
|
16
|
+
SEEKERS_UPDATED = "seekersUpdated",
|
|
16
17
|
ERROR = "error"
|
|
17
18
|
}
|
|
18
19
|
export interface SdkEventHandlers {
|
|
@@ -24,6 +25,7 @@ export interface SdkEventHandlers {
|
|
|
24
25
|
[SdkEventType.SESSION_CREATED]: (discussion: Discussion) => void;
|
|
25
26
|
[SdkEventType.SESSION_RENEWED]: (discussion: Discussion) => void;
|
|
26
27
|
[SdkEventType.SESSION_ACCEPTED]: (contactUserId: string) => void;
|
|
28
|
+
[SdkEventType.SEEKERS_UPDATED]: (seekers: Uint8Array[]) => void;
|
|
27
29
|
[SdkEventType.ERROR]: (error: Error, context: string) => void;
|
|
28
30
|
}
|
|
29
31
|
export declare class SdkEventEmitter {
|
|
@@ -16,6 +16,7 @@ export var SdkEventType;
|
|
|
16
16
|
SdkEventType["SESSION_CREATED"] = "sessionCreated";
|
|
17
17
|
SdkEventType["SESSION_RENEWED"] = "sessionRenewed";
|
|
18
18
|
SdkEventType["SESSION_ACCEPTED"] = "sessionAccepted";
|
|
19
|
+
SdkEventType["SEEKERS_UPDATED"] = "seekersUpdated";
|
|
19
20
|
SdkEventType["ERROR"] = "error";
|
|
20
21
|
})(SdkEventType || (SdkEventType = {}));
|
|
21
22
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -36,6 +37,7 @@ export class SdkEventEmitter {
|
|
|
36
37
|
[SdkEventType.SESSION_CREATED]: new Set(),
|
|
37
38
|
[SdkEventType.SESSION_RENEWED]: new Set(),
|
|
38
39
|
[SdkEventType.SESSION_ACCEPTED]: new Set(),
|
|
40
|
+
[SdkEventType.SEEKERS_UPDATED]: new Set(),
|
|
39
41
|
[SdkEventType.ERROR]: new Set(),
|
|
40
42
|
}
|
|
41
43
|
});
|
|
@@ -106,11 +106,21 @@ export interface ReadyAnnouncement {
|
|
|
106
106
|
export type SendAnnouncement = null | ReadyAnnouncement;
|
|
107
107
|
/** Serialize a SendAnnouncement to a JSON string for SQLite text column */
|
|
108
108
|
export declare function serializeSendAnnouncement(announcement: ReadyAnnouncement): string;
|
|
109
|
-
/** Deserialize a SendAnnouncement JSON string from SQLite back to an object
|
|
109
|
+
/** Deserialize a SendAnnouncement JSON string from SQLite back to an object.
|
|
110
|
+
* Throws a descriptive error if the stored JSON is malformed. */
|
|
110
111
|
export declare function deserializeSendAnnouncement(json: string): ReadyAnnouncement;
|
|
111
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* Shape required by rowToDiscussion — matches Drizzle's inferred DiscussionRow
|
|
114
|
+
* without importing from schema (avoids circular dependency).
|
|
115
|
+
*/
|
|
116
|
+
interface DiscussionRowLike {
|
|
117
|
+
sendAnnouncement: string | null;
|
|
118
|
+
announcementMessage: string | null;
|
|
119
|
+
[key: string]: unknown;
|
|
120
|
+
}
|
|
121
|
+
/** Convert a Drizzle discussion row to a domain Discussion object.
|
|
112
122
|
* Deserializes sendAnnouncement from JSON text to SendAnnouncement. */
|
|
113
|
-
export declare function rowToDiscussion(row:
|
|
123
|
+
export declare function rowToDiscussion(row: DiscussionRowLike): Discussion;
|
|
114
124
|
export interface Discussion {
|
|
115
125
|
id?: number;
|
|
116
126
|
ownerUserId: string;
|
|
@@ -148,3 +158,4 @@ export interface ActiveSeeker {
|
|
|
148
158
|
id?: number;
|
|
149
159
|
seeker: Uint8Array;
|
|
150
160
|
}
|
|
161
|
+
export {};
|
package/dist/{db.js → db/db.js}
RENAMED
|
@@ -53,20 +53,29 @@ export function serializeSendAnnouncement(announcement) {
|
|
|
53
53
|
when_to_send: announcement.when_to_send.toISOString(),
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
-
/** Deserialize a SendAnnouncement JSON string from SQLite back to an object
|
|
56
|
+
/** Deserialize a SendAnnouncement JSON string from SQLite back to an object.
|
|
57
|
+
* Throws a descriptive error if the stored JSON is malformed. */
|
|
57
58
|
export function deserializeSendAnnouncement(json) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
announcement_bytes
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(json);
|
|
61
|
+
if (!parsed || !parsed.announcement_bytes || !parsed.when_to_send) {
|
|
62
|
+
throw new Error('missing required fields');
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
announcement_bytes: new Uint8Array(parsed.announcement_bytes),
|
|
66
|
+
when_to_send: new Date(parsed.when_to_send),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
throw new Error(`Failed to deserialize SendAnnouncement: ${e instanceof Error ? e.message : e}`);
|
|
71
|
+
}
|
|
63
72
|
}
|
|
64
|
-
/** Convert a
|
|
73
|
+
/** Convert a Drizzle discussion row to a domain Discussion object.
|
|
65
74
|
* Deserializes sendAnnouncement from JSON text to SendAnnouncement. */
|
|
66
75
|
export function rowToDiscussion(row) {
|
|
67
76
|
return {
|
|
68
77
|
...row,
|
|
69
|
-
sendAnnouncement:
|
|
78
|
+
sendAnnouncement: row.sendAnnouncement !== null
|
|
70
79
|
? deserializeSendAnnouncement(row.sendAnnouncement)
|
|
71
80
|
: null,
|
|
72
81
|
lastAnnouncementMessage: row.announcementMessage ?? undefined,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SQLite execution utilities.
|
|
3
|
+
*
|
|
4
|
+
* Used by both sqlite.ts (in-process path) and sqlite-worker.ts (Worker path)
|
|
5
|
+
* to avoid duplicating the wa-sqlite prepare/bind/step/finalize logic.
|
|
6
|
+
*/
|
|
7
|
+
import * as SQLite from 'wa-sqlite';
|
|
8
|
+
export type SqliteAPI = ReturnType<typeof SQLite.Factory>;
|
|
9
|
+
/**
|
|
10
|
+
* Copy blob values out of WASM linear memory.
|
|
11
|
+
* wa-sqlite's column_blob() returns a Uint8Array VIEW into Module.HEAPU8.
|
|
12
|
+
* These views become stale after finalize() or memory growth.
|
|
13
|
+
*/
|
|
14
|
+
export declare function copyRow(row: unknown[]): unknown[];
|
|
15
|
+
/**
|
|
16
|
+
* Execute a SQL statement against a wa-sqlite database handle.
|
|
17
|
+
* Handles both parameterless (exec) and parameterized (prepare_v2) paths.
|
|
18
|
+
*/
|
|
19
|
+
export declare function execStatements(sqlite3: SqliteAPI, dbHandle: number, sql: string, params?: unknown[]): Promise<unknown[][]>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SQLite execution utilities.
|
|
3
|
+
*
|
|
4
|
+
* Used by both sqlite.ts (in-process path) and sqlite-worker.ts (Worker path)
|
|
5
|
+
* to avoid duplicating the wa-sqlite prepare/bind/step/finalize logic.
|
|
6
|
+
*/
|
|
7
|
+
import * as SQLite from 'wa-sqlite';
|
|
8
|
+
/**
|
|
9
|
+
* Copy blob values out of WASM linear memory.
|
|
10
|
+
* wa-sqlite's column_blob() returns a Uint8Array VIEW into Module.HEAPU8.
|
|
11
|
+
* These views become stale after finalize() or memory growth.
|
|
12
|
+
*/
|
|
13
|
+
export function copyRow(row) {
|
|
14
|
+
return row.map(v => (v instanceof Uint8Array ? new Uint8Array(v) : v));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Execute a SQL statement against a wa-sqlite database handle.
|
|
18
|
+
* Handles both parameterless (exec) and parameterized (prepare_v2) paths.
|
|
19
|
+
*/
|
|
20
|
+
export async function execStatements(sqlite3, dbHandle, sql, params = []) {
|
|
21
|
+
if (params.length === 0) {
|
|
22
|
+
const rows = [];
|
|
23
|
+
await sqlite3.exec(dbHandle, sql, (row) => {
|
|
24
|
+
rows.push(copyRow(row));
|
|
25
|
+
});
|
|
26
|
+
return rows;
|
|
27
|
+
}
|
|
28
|
+
const str = sqlite3.str_new(dbHandle, sql);
|
|
29
|
+
try {
|
|
30
|
+
const prepared = await sqlite3.prepare_v2(dbHandle, sqlite3.str_value(str));
|
|
31
|
+
if (!prepared)
|
|
32
|
+
return [];
|
|
33
|
+
try {
|
|
34
|
+
sqlite3.bind_collection(prepared.stmt, params);
|
|
35
|
+
const rows = [];
|
|
36
|
+
while ((await sqlite3.step(prepared.stmt)) === SQLite.SQLITE_ROW) {
|
|
37
|
+
rows.push(copyRow(sqlite3.row(prepared.stmt)));
|
|
38
|
+
}
|
|
39
|
+
return rows;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await sqlite3.finalize(prepared.stmt);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
sqlite3.str_finish(str);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DDL: string[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Auto-generated from drizzle migrations — do not edit manually.
|
|
2
|
+
// Regenerate with: npm run db:generate
|
|
3
|
+
export const DDL = [
|
|
4
|
+
'CREATE TABLE IF NOT EXISTS `activeSeekers` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`seeker` blob NOT NULL\n);',
|
|
5
|
+
'CREATE INDEX IF NOT EXISTS `active_seekers_seeker_idx` ON `activeSeekers` (`seeker`);',
|
|
6
|
+
'CREATE TABLE IF NOT EXISTS `announcementCursors` (\n\t`userId` text PRIMARY KEY NOT NULL,\n\t`counter` text NOT NULL\n);',
|
|
7
|
+
'CREATE TABLE IF NOT EXISTS `contacts` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`ownerUserId` text NOT NULL,\n\t`userId` text NOT NULL,\n\t`name` text NOT NULL,\n\t`avatar` text,\n\t`publicKeys` blob NOT NULL,\n\t`isOnline` integer NOT NULL,\n\t`lastSeen` integer NOT NULL,\n\t`createdAt` integer NOT NULL\n);',
|
|
8
|
+
'CREATE INDEX IF NOT EXISTS `contacts_owner_user_idx` ON `contacts` (`ownerUserId`,`userId`);',
|
|
9
|
+
'CREATE INDEX IF NOT EXISTS `contacts_owner_name_idx` ON `contacts` (`ownerUserId`,`name`);',
|
|
10
|
+
'CREATE TABLE IF NOT EXISTS `discussions` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`ownerUserId` text NOT NULL,\n\t`contactUserId` text NOT NULL,\n\t`weAccepted` integer DEFAULT false NOT NULL,\n\t`sendAnnouncement` text,\n\t`direction` text NOT NULL,\n\t`status` text NOT NULL,\n\t`nextSeeker` blob,\n\t`initiationAnnouncement` blob,\n\t`announcementMessage` text,\n\t`lastSyncTimestamp` integer,\n\t`customName` text,\n\t`lastMessageId` integer,\n\t`lastMessageContent` text,\n\t`lastMessageTimestamp` integer,\n\t`unreadCount` integer DEFAULT 0 NOT NULL,\n\t`createdAt` integer NOT NULL,\n\t`updatedAt` integer NOT NULL\n);',
|
|
11
|
+
'CREATE UNIQUE INDEX IF NOT EXISTS `discussions_owner_contact_idx` ON `discussions` (`ownerUserId`,`contactUserId`);',
|
|
12
|
+
'CREATE INDEX IF NOT EXISTS `discussions_owner_status_idx` ON `discussions` (`ownerUserId`,`status`);',
|
|
13
|
+
'CREATE TABLE IF NOT EXISTS `messages` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`ownerUserId` text NOT NULL,\n\t`contactUserId` text NOT NULL,\n\t`messageId` blob,\n\t`content` text NOT NULL,\n\t`serializedContent` blob,\n\t`type` text NOT NULL,\n\t`direction` text NOT NULL,\n\t`status` text NOT NULL,\n\t`timestamp` integer NOT NULL,\n\t`metadata` text,\n\t`seeker` blob,\n\t`replyTo` text,\n\t`forwardOf` text,\n\t`encryptedMessage` blob,\n\t`whenToSend` integer\n);',
|
|
14
|
+
'CREATE INDEX IF NOT EXISTS `messages_owner_contact_idx` ON `messages` (`ownerUserId`,`contactUserId`);',
|
|
15
|
+
'CREATE INDEX IF NOT EXISTS `messages_owner_status_idx` ON `messages` (`ownerUserId`,`status`);',
|
|
16
|
+
'CREATE INDEX IF NOT EXISTS `messages_owner_contact_status_idx` ON `messages` (`ownerUserId`,`contactUserId`,`status`);',
|
|
17
|
+
'CREATE INDEX IF NOT EXISTS `messages_owner_seeker_idx` ON `messages` (`ownerUserId`,`seeker`);',
|
|
18
|
+
'CREATE INDEX IF NOT EXISTS `messages_owner_contact_dir_idx` ON `messages` (`ownerUserId`,`contactUserId`,`direction`);',
|
|
19
|
+
'CREATE INDEX IF NOT EXISTS `messages_owner_dir_status_idx` ON `messages` (`ownerUserId`,`direction`,`status`);',
|
|
20
|
+
'CREATE INDEX IF NOT EXISTS `messages_timestamp_idx` ON `messages` (`timestamp`);',
|
|
21
|
+
'CREATE TABLE IF NOT EXISTS `pendingAnnouncements` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`announcement` blob NOT NULL,\n\t`fetchedAt` integer NOT NULL,\n\t`counter` text\n);',
|
|
22
|
+
'CREATE UNIQUE INDEX IF NOT EXISTS `pending_announcements_announcement_idx` ON `pendingAnnouncements` (`announcement`);',
|
|
23
|
+
'CREATE INDEX IF NOT EXISTS `pending_announcements_fetchedAt_idx` ON `pendingAnnouncements` (`fetchedAt`);',
|
|
24
|
+
'CREATE TABLE IF NOT EXISTS `pendingEncryptedMessages` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`seeker` blob NOT NULL,\n\t`ciphertext` blob NOT NULL,\n\t`fetchedAt` integer NOT NULL\n);',
|
|
25
|
+
'CREATE INDEX IF NOT EXISTS `pending_encrypted_seeker_idx` ON `pendingEncryptedMessages` (`seeker`);',
|
|
26
|
+
'CREATE INDEX IF NOT EXISTS `pending_encrypted_fetchedAt_idx` ON `pendingEncryptedMessages` (`fetchedAt`);',
|
|
27
|
+
'CREATE TABLE IF NOT EXISTS `userProfile` (\n\t`userId` text PRIMARY KEY NOT NULL,\n\t`username` text NOT NULL,\n\t`avatar` text,\n\t`bio` text,\n\t`status` text NOT NULL,\n\t`lastSeen` integer NOT NULL,\n\t`createdAt` integer NOT NULL,\n\t`updatedAt` integer NOT NULL,\n\t`lastPublicKeyPush` integer,\n\t`security` text NOT NULL,\n\t`session` blob NOT NULL\n);',
|
|
28
|
+
'CREATE INDEX IF NOT EXISTS `userProfile_username_idx` ON `userProfile` (`username`);',
|
|
29
|
+
'CREATE INDEX IF NOT EXISTS `userProfile_status_idx` ON `userProfile` (`status`);',
|
|
30
|
+
'CREATE INDEX IF NOT EXISTS `messages_owner_contact_msgid_idx` ON `messages` (`ownerUserId`,`contactUserId`,`messageId`);',
|
|
31
|
+
];
|
package/dist/db/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function replaceActiveSeekers(seekers: Uint8Array[]): Promise<void>;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import * as schema from '../schema';
|
|
2
2
|
import { getSqliteDb, withTransaction } from '../sqlite';
|
|
3
|
-
let onSeekersUpdated = null;
|
|
4
|
-
export function setOnSeekersUpdated(cb) {
|
|
5
|
-
onSeekersUpdated = cb;
|
|
6
|
-
}
|
|
7
3
|
export async function replaceActiveSeekers(seekers) {
|
|
8
4
|
await withTransaction(async () => {
|
|
9
5
|
const db = getSqliteDb();
|
|
@@ -14,5 +10,4 @@ export async function replaceActiveSeekers(seekers) {
|
|
|
14
10
|
.values(seekers.map(seeker => ({ seeker })));
|
|
15
11
|
}
|
|
16
12
|
});
|
|
17
|
-
onSeekersUpdated?.(seekers);
|
|
18
13
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as schema from '../schema';
|
|
2
|
-
import { MessageStatus } from '
|
|
2
|
+
import { MessageStatus } from '../../db/db';
|
|
3
3
|
export type MessageRow = typeof schema.messages.$inferSelect;
|
|
4
4
|
export type MessageInsert = typeof schema.messages.$inferInsert;
|
|
5
5
|
export declare function getMessageById(id: number): Promise<MessageRow | undefined>;
|
|
@@ -14,7 +14,7 @@ export declare function deleteDeliveredKeepAliveMessages(ownerUserId: string): P
|
|
|
14
14
|
export declare function getOutgoingSentMessagesByOwner(ownerUserId: string): Promise<MessageRow[]>;
|
|
15
15
|
export declare function getWaitingMessageCount(ownerUserId: string, contactUserId: string): Promise<number>;
|
|
16
16
|
export declare function getSendQueueMessages(ownerUserId: string, contactUserId: string): Promise<MessageRow[]>;
|
|
17
|
-
export declare function getMessagesByStatus(status: MessageStatus): Promise<MessageRow[]>;
|
|
17
|
+
export declare function getMessagesByStatus(ownerUserId: string, status: MessageStatus): Promise<MessageRow[]>;
|
|
18
18
|
/**
|
|
19
19
|
* Reset outgoing messages to WAITING_SESSION, clearing encryption data.
|
|
20
20
|
* @param statuses - If provided, only reset messages with these statuses.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { eq, and, sql, inArray, asc } from 'drizzle-orm';
|
|
2
2
|
import * as schema from '../schema';
|
|
3
3
|
import { getSqliteDb, getLastInsertRowId } from '../sqlite';
|
|
4
|
-
import { MessageDirection, MessageStatus, MessageType } from '
|
|
4
|
+
import { MessageDirection, MessageStatus, MessageType } from '../../db/db';
|
|
5
5
|
export async function getMessageById(id) {
|
|
6
6
|
return getSqliteDb()
|
|
7
7
|
.select()
|
|
@@ -82,11 +82,11 @@ export async function getSendQueueMessages(ownerUserId, contactUserId) {
|
|
|
82
82
|
.orderBy(asc(schema.messages.timestamp), asc(schema.messages.id))
|
|
83
83
|
.all();
|
|
84
84
|
}
|
|
85
|
-
export async function getMessagesByStatus(status) {
|
|
85
|
+
export async function getMessagesByStatus(ownerUserId, status) {
|
|
86
86
|
return getSqliteDb()
|
|
87
87
|
.select()
|
|
88
88
|
.from(schema.messages)
|
|
89
|
-
.where(eq(schema.messages.status, status))
|
|
89
|
+
.where(and(eq(schema.messages.ownerUserId, ownerUserId), eq(schema.messages.status, status)))
|
|
90
90
|
.all();
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
@@ -6,7 +6,7 @@ export type UserProfileInsert = typeof schema.userProfile.$inferInsert;
|
|
|
6
6
|
export declare function rowToUserProfile(row: UserProfileRow): UserProfile;
|
|
7
7
|
/** Convert a domain UserProfile to a DB-ready insert row (security as JSON string). */
|
|
8
8
|
export declare function userProfileToRow(profile: UserProfile): UserProfileInsert;
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function getUserProfileById(userId: string): Promise<UserProfileRow | undefined>;
|
|
10
10
|
export declare function updateUserProfileById(userId: string, data: Partial<UserProfileInsert>): Promise<void>;
|
|
11
11
|
export declare function getUserProfileByUsernameLower(username: string): Promise<{
|
|
12
12
|
userId: string;
|
|
@@ -51,7 +51,7 @@ export function userProfileToRow(profile) {
|
|
|
51
51
|
lastPublicKeyPush: profile.lastPublicKeyPush ?? null,
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
-
export async function
|
|
54
|
+
export async function getUserProfileById(userId) {
|
|
55
55
|
return getSqliteDb()
|
|
56
56
|
.select()
|
|
57
57
|
.from(schema.userProfile)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const bytes: {
|
|
2
|
+
(): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
|
|
3
|
+
name: "";
|
|
4
|
+
dataType: "custom";
|
|
5
|
+
columnType: "SQLiteCustomColumn";
|
|
6
|
+
data: Uint8Array<ArrayBufferLike>;
|
|
7
|
+
driverParam: Uint8Array<ArrayBufferLike>;
|
|
8
|
+
enumValues: undefined;
|
|
9
|
+
}>;
|
|
10
|
+
<TConfig extends Record<string, any>>(fieldConfig?: TConfig | undefined): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
|
|
11
|
+
name: "";
|
|
12
|
+
dataType: "custom";
|
|
13
|
+
columnType: "SQLiteCustomColumn";
|
|
14
|
+
data: Uint8Array<ArrayBufferLike>;
|
|
15
|
+
driverParam: Uint8Array<ArrayBufferLike>;
|
|
16
|
+
enumValues: undefined;
|
|
17
|
+
}>;
|
|
18
|
+
<TName extends string>(dbName: TName, fieldConfig?: unknown): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
|
|
19
|
+
name: TName;
|
|
20
|
+
dataType: "custom";
|
|
21
|
+
columnType: "SQLiteCustomColumn";
|
|
22
|
+
data: Uint8Array<ArrayBufferLike>;
|
|
23
|
+
driverParam: Uint8Array<ArrayBufferLike>;
|
|
24
|
+
enumValues: undefined;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { customType } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
// Custom blob type — wa-sqlite returns Uint8Array natively for BLOB columns.
|
|
3
|
+
// Drizzle's built-in blob mode converts to/from hex strings, which breaks
|
|
4
|
+
// with wa-sqlite's direct Uint8Array handling. This custom type passes through.
|
|
5
|
+
export const bytes = customType({
|
|
6
|
+
dataType() {
|
|
7
|
+
return 'blob';
|
|
8
|
+
},
|
|
9
|
+
fromDriver(value) {
|
|
10
|
+
return value;
|
|
11
|
+
},
|
|
12
|
+
toDriver(value) {
|
|
13
|
+
return value;
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const activeSeekers: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
2
|
+
name: "activeSeekers";
|
|
3
|
+
schema: undefined;
|
|
4
|
+
columns: {
|
|
5
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
6
|
+
name: "id";
|
|
7
|
+
tableName: "activeSeekers";
|
|
8
|
+
dataType: "number";
|
|
9
|
+
columnType: "SQLiteInteger";
|
|
10
|
+
data: number;
|
|
11
|
+
driverParam: number;
|
|
12
|
+
notNull: true;
|
|
13
|
+
hasDefault: true;
|
|
14
|
+
isPrimaryKey: true;
|
|
15
|
+
isAutoincrement: false;
|
|
16
|
+
hasRuntimeDefault: false;
|
|
17
|
+
enumValues: undefined;
|
|
18
|
+
baseColumn: never;
|
|
19
|
+
identity: undefined;
|
|
20
|
+
generated: undefined;
|
|
21
|
+
}, {}, {}>;
|
|
22
|
+
seeker: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
23
|
+
name: "seeker";
|
|
24
|
+
tableName: "activeSeekers";
|
|
25
|
+
dataType: "custom";
|
|
26
|
+
columnType: "SQLiteCustomColumn";
|
|
27
|
+
data: Uint8Array<ArrayBufferLike>;
|
|
28
|
+
driverParam: Uint8Array<ArrayBufferLike>;
|
|
29
|
+
notNull: true;
|
|
30
|
+
hasDefault: false;
|
|
31
|
+
isPrimaryKey: false;
|
|
32
|
+
isAutoincrement: false;
|
|
33
|
+
hasRuntimeDefault: false;
|
|
34
|
+
enumValues: undefined;
|
|
35
|
+
baseColumn: never;
|
|
36
|
+
identity: undefined;
|
|
37
|
+
generated: undefined;
|
|
38
|
+
}, {}, {
|
|
39
|
+
sqliteColumnBuilderBrand: "SQLiteCustomColumnBuilderBrand";
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
dialect: "sqlite";
|
|
43
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { sqliteTable, integer, index } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { bytes } from './_helpers';
|
|
3
|
+
export const activeSeekers = sqliteTable('activeSeekers', {
|
|
4
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
5
|
+
seeker: bytes('seeker').notNull(),
|
|
6
|
+
}, table => [index('active_seekers_seeker_idx').on(table.seeker)]);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const announcementCursors: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
2
|
+
name: "announcementCursors";
|
|
3
|
+
schema: undefined;
|
|
4
|
+
columns: {
|
|
5
|
+
userId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
6
|
+
name: "userId";
|
|
7
|
+
tableName: "announcementCursors";
|
|
8
|
+
dataType: "string";
|
|
9
|
+
columnType: "SQLiteText";
|
|
10
|
+
data: string;
|
|
11
|
+
driverParam: string;
|
|
12
|
+
notNull: true;
|
|
13
|
+
hasDefault: false;
|
|
14
|
+
isPrimaryKey: true;
|
|
15
|
+
isAutoincrement: false;
|
|
16
|
+
hasRuntimeDefault: false;
|
|
17
|
+
enumValues: [string, ...string[]];
|
|
18
|
+
baseColumn: never;
|
|
19
|
+
identity: undefined;
|
|
20
|
+
generated: undefined;
|
|
21
|
+
}, {}, {
|
|
22
|
+
length: number | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
counter: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
25
|
+
name: "counter";
|
|
26
|
+
tableName: "announcementCursors";
|
|
27
|
+
dataType: "string";
|
|
28
|
+
columnType: "SQLiteText";
|
|
29
|
+
data: string;
|
|
30
|
+
driverParam: string;
|
|
31
|
+
notNull: true;
|
|
32
|
+
hasDefault: false;
|
|
33
|
+
isPrimaryKey: false;
|
|
34
|
+
isAutoincrement: false;
|
|
35
|
+
hasRuntimeDefault: false;
|
|
36
|
+
enumValues: [string, ...string[]];
|
|
37
|
+
baseColumn: never;
|
|
38
|
+
identity: undefined;
|
|
39
|
+
generated: undefined;
|
|
40
|
+
}, {}, {
|
|
41
|
+
length: number | undefined;
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
dialect: "sqlite";
|
|
45
|
+
}>;
|