@massalabs/gossip-sdk 0.0.2-dev.20260220142001 → 0.0.2-dev.20260223065033
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 +26 -3
- 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 +1 -41
- package/dist/gossip.js +8 -10
- 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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
export declare const userProfile: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
2
|
+
name: "userProfile";
|
|
3
|
+
schema: undefined;
|
|
4
|
+
columns: {
|
|
5
|
+
userId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
6
|
+
name: "userId";
|
|
7
|
+
tableName: "userProfile";
|
|
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
|
+
username: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
25
|
+
name: "username";
|
|
26
|
+
tableName: "userProfile";
|
|
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
|
+
avatar: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
44
|
+
name: "avatar";
|
|
45
|
+
tableName: "userProfile";
|
|
46
|
+
dataType: "string";
|
|
47
|
+
columnType: "SQLiteText";
|
|
48
|
+
data: string;
|
|
49
|
+
driverParam: string;
|
|
50
|
+
notNull: false;
|
|
51
|
+
hasDefault: false;
|
|
52
|
+
isPrimaryKey: false;
|
|
53
|
+
isAutoincrement: false;
|
|
54
|
+
hasRuntimeDefault: false;
|
|
55
|
+
enumValues: [string, ...string[]];
|
|
56
|
+
baseColumn: never;
|
|
57
|
+
identity: undefined;
|
|
58
|
+
generated: undefined;
|
|
59
|
+
}, {}, {
|
|
60
|
+
length: number | undefined;
|
|
61
|
+
}>;
|
|
62
|
+
bio: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
63
|
+
name: "bio";
|
|
64
|
+
tableName: "userProfile";
|
|
65
|
+
dataType: "string";
|
|
66
|
+
columnType: "SQLiteText";
|
|
67
|
+
data: string;
|
|
68
|
+
driverParam: string;
|
|
69
|
+
notNull: false;
|
|
70
|
+
hasDefault: false;
|
|
71
|
+
isPrimaryKey: false;
|
|
72
|
+
isAutoincrement: false;
|
|
73
|
+
hasRuntimeDefault: false;
|
|
74
|
+
enumValues: [string, ...string[]];
|
|
75
|
+
baseColumn: never;
|
|
76
|
+
identity: undefined;
|
|
77
|
+
generated: undefined;
|
|
78
|
+
}, {}, {
|
|
79
|
+
length: number | undefined;
|
|
80
|
+
}>;
|
|
81
|
+
status: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
82
|
+
name: "status";
|
|
83
|
+
tableName: "userProfile";
|
|
84
|
+
dataType: "string";
|
|
85
|
+
columnType: "SQLiteText";
|
|
86
|
+
data: string;
|
|
87
|
+
driverParam: string;
|
|
88
|
+
notNull: true;
|
|
89
|
+
hasDefault: false;
|
|
90
|
+
isPrimaryKey: false;
|
|
91
|
+
isAutoincrement: false;
|
|
92
|
+
hasRuntimeDefault: false;
|
|
93
|
+
enumValues: [string, ...string[]];
|
|
94
|
+
baseColumn: never;
|
|
95
|
+
identity: undefined;
|
|
96
|
+
generated: undefined;
|
|
97
|
+
}, {}, {
|
|
98
|
+
length: number | undefined;
|
|
99
|
+
}>;
|
|
100
|
+
lastSeen: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
101
|
+
name: "lastSeen";
|
|
102
|
+
tableName: "userProfile";
|
|
103
|
+
dataType: "date";
|
|
104
|
+
columnType: "SQLiteTimestamp";
|
|
105
|
+
data: Date;
|
|
106
|
+
driverParam: number;
|
|
107
|
+
notNull: true;
|
|
108
|
+
hasDefault: false;
|
|
109
|
+
isPrimaryKey: false;
|
|
110
|
+
isAutoincrement: false;
|
|
111
|
+
hasRuntimeDefault: false;
|
|
112
|
+
enumValues: undefined;
|
|
113
|
+
baseColumn: never;
|
|
114
|
+
identity: undefined;
|
|
115
|
+
generated: undefined;
|
|
116
|
+
}, {}, {}>;
|
|
117
|
+
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
118
|
+
name: "createdAt";
|
|
119
|
+
tableName: "userProfile";
|
|
120
|
+
dataType: "date";
|
|
121
|
+
columnType: "SQLiteTimestamp";
|
|
122
|
+
data: Date;
|
|
123
|
+
driverParam: number;
|
|
124
|
+
notNull: true;
|
|
125
|
+
hasDefault: false;
|
|
126
|
+
isPrimaryKey: false;
|
|
127
|
+
isAutoincrement: false;
|
|
128
|
+
hasRuntimeDefault: false;
|
|
129
|
+
enumValues: undefined;
|
|
130
|
+
baseColumn: never;
|
|
131
|
+
identity: undefined;
|
|
132
|
+
generated: undefined;
|
|
133
|
+
}, {}, {}>;
|
|
134
|
+
updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
135
|
+
name: "updatedAt";
|
|
136
|
+
tableName: "userProfile";
|
|
137
|
+
dataType: "date";
|
|
138
|
+
columnType: "SQLiteTimestamp";
|
|
139
|
+
data: Date;
|
|
140
|
+
driverParam: number;
|
|
141
|
+
notNull: true;
|
|
142
|
+
hasDefault: false;
|
|
143
|
+
isPrimaryKey: false;
|
|
144
|
+
isAutoincrement: false;
|
|
145
|
+
hasRuntimeDefault: false;
|
|
146
|
+
enumValues: undefined;
|
|
147
|
+
baseColumn: never;
|
|
148
|
+
identity: undefined;
|
|
149
|
+
generated: undefined;
|
|
150
|
+
}, {}, {}>;
|
|
151
|
+
lastPublicKeyPush: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
152
|
+
name: "lastPublicKeyPush";
|
|
153
|
+
tableName: "userProfile";
|
|
154
|
+
dataType: "date";
|
|
155
|
+
columnType: "SQLiteTimestamp";
|
|
156
|
+
data: Date;
|
|
157
|
+
driverParam: number;
|
|
158
|
+
notNull: false;
|
|
159
|
+
hasDefault: false;
|
|
160
|
+
isPrimaryKey: false;
|
|
161
|
+
isAutoincrement: false;
|
|
162
|
+
hasRuntimeDefault: false;
|
|
163
|
+
enumValues: undefined;
|
|
164
|
+
baseColumn: never;
|
|
165
|
+
identity: undefined;
|
|
166
|
+
generated: undefined;
|
|
167
|
+
}, {}, {}>;
|
|
168
|
+
security: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
169
|
+
name: "security";
|
|
170
|
+
tableName: "userProfile";
|
|
171
|
+
dataType: "string";
|
|
172
|
+
columnType: "SQLiteText";
|
|
173
|
+
data: string;
|
|
174
|
+
driverParam: string;
|
|
175
|
+
notNull: true;
|
|
176
|
+
hasDefault: false;
|
|
177
|
+
isPrimaryKey: false;
|
|
178
|
+
isAutoincrement: false;
|
|
179
|
+
hasRuntimeDefault: false;
|
|
180
|
+
enumValues: [string, ...string[]];
|
|
181
|
+
baseColumn: never;
|
|
182
|
+
identity: undefined;
|
|
183
|
+
generated: undefined;
|
|
184
|
+
}, {}, {
|
|
185
|
+
length: number | undefined;
|
|
186
|
+
}>;
|
|
187
|
+
session: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
188
|
+
name: "session";
|
|
189
|
+
tableName: "userProfile";
|
|
190
|
+
dataType: "custom";
|
|
191
|
+
columnType: "SQLiteCustomColumn";
|
|
192
|
+
data: Uint8Array<ArrayBufferLike>;
|
|
193
|
+
driverParam: Uint8Array<ArrayBufferLike>;
|
|
194
|
+
notNull: true;
|
|
195
|
+
hasDefault: false;
|
|
196
|
+
isPrimaryKey: false;
|
|
197
|
+
isAutoincrement: false;
|
|
198
|
+
hasRuntimeDefault: false;
|
|
199
|
+
enumValues: undefined;
|
|
200
|
+
baseColumn: never;
|
|
201
|
+
identity: undefined;
|
|
202
|
+
generated: undefined;
|
|
203
|
+
}, {}, {
|
|
204
|
+
sqliteColumnBuilderBrand: "SQLiteCustomColumnBuilderBrand";
|
|
205
|
+
}>;
|
|
206
|
+
};
|
|
207
|
+
dialect: "sqlite";
|
|
208
|
+
}>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { sqliteTable, text, integer, index } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { bytes } from './_helpers';
|
|
3
|
+
export const userProfile = sqliteTable('userProfile', {
|
|
4
|
+
userId: text('userId').primaryKey(),
|
|
5
|
+
username: text('username').notNull(),
|
|
6
|
+
avatar: text('avatar'),
|
|
7
|
+
bio: text('bio'),
|
|
8
|
+
status: text('status').notNull(),
|
|
9
|
+
lastSeen: integer('lastSeen', { mode: 'timestamp_ms' }).notNull(),
|
|
10
|
+
createdAt: integer('createdAt', { mode: 'timestamp_ms' }).notNull(),
|
|
11
|
+
updatedAt: integer('updatedAt', { mode: 'timestamp_ms' }).notNull(),
|
|
12
|
+
lastPublicKeyPush: integer('lastPublicKeyPush', {
|
|
13
|
+
mode: 'timestamp_ms',
|
|
14
|
+
}),
|
|
15
|
+
security: text('security').notNull(),
|
|
16
|
+
session: bytes('session').notNull(),
|
|
17
|
+
}, table => [
|
|
18
|
+
index('userProfile_username_idx').on(table.username),
|
|
19
|
+
index('userProfile_status_idx').on(table.status),
|
|
20
|
+
]);
|
|
@@ -12,43 +12,15 @@
|
|
|
12
12
|
import SQLiteESMFactory from 'wa-sqlite/dist/wa-sqlite.mjs';
|
|
13
13
|
import * as SQLite from 'wa-sqlite';
|
|
14
14
|
import { AccessHandlePoolVFS } from 'wa-sqlite/src/examples/AccessHandlePoolVFS.js';
|
|
15
|
+
import { execStatements } from './exec-utils.js';
|
|
15
16
|
let sqlite3 = null;
|
|
16
17
|
let dbHandle = null;
|
|
17
18
|
// Bind Worker's postMessage (avoids DOM lib signature mismatch at compile time)
|
|
18
19
|
const post = globalThis.postMessage.bind(globalThis);
|
|
19
|
-
function copyRow(row) {
|
|
20
|
-
return row.map(v => (v instanceof Uint8Array ? new Uint8Array(v) : v));
|
|
21
|
-
}
|
|
22
20
|
async function execSql(sql, params) {
|
|
23
21
|
if (!sqlite3 || dbHandle === null)
|
|
24
22
|
throw new Error('SQLite not initialized');
|
|
25
|
-
const rows =
|
|
26
|
-
if (params.length === 0) {
|
|
27
|
-
await sqlite3.exec(dbHandle, sql, (row) => {
|
|
28
|
-
rows.push(copyRow(row));
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
const str = sqlite3.str_new(dbHandle, sql);
|
|
33
|
-
try {
|
|
34
|
-
const prepared = await sqlite3.prepare_v2(dbHandle, sqlite3.str_value(str));
|
|
35
|
-
if (prepared) {
|
|
36
|
-
try {
|
|
37
|
-
sqlite3.bind_collection(prepared.stmt, params);
|
|
38
|
-
while ((await sqlite3.step(prepared.stmt)) === SQLite.SQLITE_ROW) {
|
|
39
|
-
rows.push(copyRow(sqlite3.row(prepared.stmt)));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
finally {
|
|
43
|
-
await sqlite3.finalize(prepared.stmt);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
sqlite3.str_finish(str);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// Only capture lastInsertRowId for INSERT statements (avoid unnecessary query on SELECT/UPDATE/DELETE)
|
|
23
|
+
const rows = await execStatements(sqlite3, dbHandle, sql, params);
|
|
52
24
|
let lastInsertRowId = 0;
|
|
53
25
|
if (sql.trimStart().toUpperCase().startsWith('INSERT')) {
|
|
54
26
|
await sqlite3.exec(dbHandle, 'SELECT last_insert_rowid()', (row) => {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite initialization module for the Gossip SDK.
|
|
3
|
+
*
|
|
4
|
+
* Uses wa-sqlite (WASM) with Drizzle ORM's sqlite-proxy driver.
|
|
5
|
+
* Two execution paths:
|
|
6
|
+
* - Browser (opfsPath set): Web Worker + AccessHandlePoolVFS — OPFS persistence,
|
|
7
|
+
* off the main thread. Uses the sync WASM build (wa-sqlite).
|
|
8
|
+
* - In-memory (tests): :memory: in-process — no persistence, fast, isolated.
|
|
9
|
+
* Uses the sync WASM build with wasmBinary passed directly.
|
|
10
|
+
*
|
|
11
|
+
* In Phase C the VFS will be swapped for the encrypted PlausibleDeniableVFS.
|
|
12
|
+
*/
|
|
13
|
+
import { type SqliteRemoteDatabase } from 'drizzle-orm/sqlite-proxy';
|
|
14
|
+
import * as schema from './schema/index.js';
|
|
15
|
+
export type GossipDatabase = SqliteRemoteDatabase<typeof schema>;
|
|
16
|
+
export interface InitDbOptions {
|
|
17
|
+
/**
|
|
18
|
+
* OPFS directory path for persistent storage.
|
|
19
|
+
* When set, spawns a Web Worker with AccessHandlePoolVFS for OPFS persistence.
|
|
20
|
+
* When omitted, uses an in-memory database (for tests).
|
|
21
|
+
*/
|
|
22
|
+
opfsPath?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Pre-loaded WASM binary for environments where fetch() is unavailable
|
|
25
|
+
* (e.g. Node.js tests). When omitted, the factory uses fetch() to load
|
|
26
|
+
* the .wasm file (browser default).
|
|
27
|
+
*/
|
|
28
|
+
wasmBinary?: ArrayBuffer;
|
|
29
|
+
/**
|
|
30
|
+
* URL to the wa-sqlite WASM file. Used in browser to tell the Emscripten
|
|
31
|
+
* factory where to fetch the WASM binary (needed when bundlers like Vite
|
|
32
|
+
* rewrite asset paths). When omitted, the factory uses its default path.
|
|
33
|
+
*/
|
|
34
|
+
wasmUrl?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Initialize wa-sqlite and create the Drizzle ORM instance.
|
|
38
|
+
* Idempotent — subsequent calls are no-ops.
|
|
39
|
+
*
|
|
40
|
+
* @param options.opfsPath - Set to persist via OPFS Worker (production).
|
|
41
|
+
* Omit for in-memory database (tests).
|
|
42
|
+
*/
|
|
43
|
+
export declare function initDb(options?: InitDbOptions): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Get the Drizzle ORM database instance.
|
|
46
|
+
* Throws if initDb() has not been called.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getSqliteDb(): GossipDatabase;
|
|
49
|
+
export declare function isSqliteOpen(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Run a callback inside a SQLite transaction (BEGIN / COMMIT / ROLLBACK).
|
|
52
|
+
* All Drizzle operations inside the callback share the same transaction
|
|
53
|
+
* and the same dbLock hold, so they cannot interleave with outside queries.
|
|
54
|
+
*/
|
|
55
|
+
export declare function withTransaction<T>(fn: () => Promise<T>): Promise<T>;
|
|
56
|
+
export declare function clearAllTables(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Clear only conversation-related tables (contacts, discussions, messages).
|
|
59
|
+
* Preserves user profiles and other data.
|
|
60
|
+
*/
|
|
61
|
+
export declare function clearConversationTables(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Get the last auto-increment row ID inserted via this connection.
|
|
64
|
+
* Used after INSERT into tables with INTEGER PRIMARY KEY AUTOINCREMENT.
|
|
65
|
+
*
|
|
66
|
+
* Browser path: returns the cached value from the last Worker exec response
|
|
67
|
+
* (returned atomically with every exec — no race condition).
|
|
68
|
+
* Test path: queries directly (same connection, serialized by dbLock).
|
|
69
|
+
*/
|
|
70
|
+
export declare function getLastInsertRowId(): Promise<number>;
|
|
71
|
+
/**
|
|
72
|
+
* Close the database and release all resources.
|
|
73
|
+
* Browser path: sends close to Worker, then terminates it.
|
|
74
|
+
* Test path: closes in-process database handle.
|
|
75
|
+
* Atomically resets all state by replacing with a fresh default.
|
|
76
|
+
*/
|
|
77
|
+
export declare function closeSqlite(): Promise<void>;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite initialization module for the Gossip SDK.
|
|
3
|
+
*
|
|
4
|
+
* Uses wa-sqlite (WASM) with Drizzle ORM's sqlite-proxy driver.
|
|
5
|
+
* Two execution paths:
|
|
6
|
+
* - Browser (opfsPath set): Web Worker + AccessHandlePoolVFS — OPFS persistence,
|
|
7
|
+
* off the main thread. Uses the sync WASM build (wa-sqlite).
|
|
8
|
+
* - In-memory (tests): :memory: in-process — no persistence, fast, isolated.
|
|
9
|
+
* Uses the sync WASM build with wasmBinary passed directly.
|
|
10
|
+
*
|
|
11
|
+
* In Phase C the VFS will be swapped for the encrypted PlausibleDeniableVFS.
|
|
12
|
+
*/
|
|
13
|
+
import SQLiteESMFactory from 'wa-sqlite/dist/wa-sqlite.mjs';
|
|
14
|
+
import * as SQLite from 'wa-sqlite';
|
|
15
|
+
import { drizzle } from 'drizzle-orm/sqlite-proxy';
|
|
16
|
+
import * as schema from './schema/index.js';
|
|
17
|
+
import { DDL } from './generated-ddl.js';
|
|
18
|
+
import { execStatements } from './exec-utils.js';
|
|
19
|
+
function createDefaultState() {
|
|
20
|
+
return {
|
|
21
|
+
worker: null,
|
|
22
|
+
msgId: 0,
|
|
23
|
+
pending: new Map(),
|
|
24
|
+
lastInsertRowIdCache: 0,
|
|
25
|
+
sqlite3: null,
|
|
26
|
+
dbHandle: null,
|
|
27
|
+
useWorker: false,
|
|
28
|
+
drizzleDb: null,
|
|
29
|
+
dbLock: Promise.resolve(),
|
|
30
|
+
inTransaction: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
let db = createDefaultState();
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Worker communication (browser path)
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
function postToWorker(msg
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const id = ++db.msgId;
|
|
42
|
+
db.pending.set(id, { resolve, reject });
|
|
43
|
+
db.worker.postMessage({ ...msg, id });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function handleWorkerMessage(e) {
|
|
47
|
+
const { id, type, ...rest } = e.data;
|
|
48
|
+
const p = db.pending.get(id);
|
|
49
|
+
if (!p)
|
|
50
|
+
return;
|
|
51
|
+
db.pending.delete(id);
|
|
52
|
+
if (type === 'error') {
|
|
53
|
+
p.reject(new Error(rest.message));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
p.resolve(rest);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Drizzle instance factory
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
function createDrizzleInstance() {
|
|
63
|
+
return drizzle(async (sql, params, method) => {
|
|
64
|
+
const rows = await execRaw(sql, params);
|
|
65
|
+
if (method === 'get') {
|
|
66
|
+
return { rows: rows[0] };
|
|
67
|
+
}
|
|
68
|
+
return { rows };
|
|
69
|
+
}, { schema });
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Raw SQL execution
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
async function execRaw(sql, params = []) {
|
|
75
|
+
// When inside a withTransaction(), the outer lock is already held —
|
|
76
|
+
// skip re-acquisition to avoid deadlock.
|
|
77
|
+
if (db.inTransaction) {
|
|
78
|
+
return execRawDirect(sql, params);
|
|
79
|
+
}
|
|
80
|
+
const prev = db.dbLock;
|
|
81
|
+
let release;
|
|
82
|
+
db.dbLock = new Promise(r => (release = r));
|
|
83
|
+
await prev;
|
|
84
|
+
try {
|
|
85
|
+
return await execRawDirect(sql, params);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
release();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function execRawDirect(sql, params = []) {
|
|
92
|
+
if (db.useWorker) {
|
|
93
|
+
const result = await postToWorker({ type: 'exec', sql, params });
|
|
94
|
+
db.lastInsertRowIdCache = result.lastInsertRowId;
|
|
95
|
+
return result.rows;
|
|
96
|
+
}
|
|
97
|
+
return execRawInProcess(sql, params);
|
|
98
|
+
}
|
|
99
|
+
async function execRawInProcess(sql, params = []) {
|
|
100
|
+
if (!db.sqlite3 || db.dbHandle === null) {
|
|
101
|
+
throw new Error('SQLite not initialized');
|
|
102
|
+
}
|
|
103
|
+
return execStatements(db.sqlite3, db.dbHandle, sql, params);
|
|
104
|
+
}
|
|
105
|
+
/** PRAGMAs applied before migrations. */
|
|
106
|
+
const PRAGMAS = `
|
|
107
|
+
PRAGMA journal_mode=MEMORY;
|
|
108
|
+
PRAGMA temp_store=MEMORY;
|
|
109
|
+
`;
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Public API
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
/**
|
|
114
|
+
* Initialize wa-sqlite and create the Drizzle ORM instance.
|
|
115
|
+
* Idempotent — subsequent calls are no-ops.
|
|
116
|
+
*
|
|
117
|
+
* @param options.opfsPath - Set to persist via OPFS Worker (production).
|
|
118
|
+
* Omit for in-memory database (tests).
|
|
119
|
+
*/
|
|
120
|
+
export async function initDb(options = {}) {
|
|
121
|
+
if (db.drizzleDb)
|
|
122
|
+
return;
|
|
123
|
+
if (options.opfsPath) {
|
|
124
|
+
// Browser path: spawn Worker with OPFS + AccessHandlePoolVFS.
|
|
125
|
+
// The sync WASM build runs in the Worker (no Asyncify needed).
|
|
126
|
+
db.worker = new Worker(new URL('./sqlite-worker.ts', import.meta.url), {
|
|
127
|
+
type: 'module',
|
|
128
|
+
});
|
|
129
|
+
db.worker.onmessage = handleWorkerMessage;
|
|
130
|
+
db.useWorker = true;
|
|
131
|
+
await postToWorker({
|
|
132
|
+
type: 'init',
|
|
133
|
+
opfsPath: options.opfsPath,
|
|
134
|
+
wasmUrl: options.wasmUrl,
|
|
135
|
+
initSql: PRAGMAS,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// In-memory mode (tests): sync WASM build, in-process, fast, isolated.
|
|
140
|
+
const moduleArg = {};
|
|
141
|
+
if (options.wasmBinary) {
|
|
142
|
+
moduleArg.wasmBinary = options.wasmBinary;
|
|
143
|
+
}
|
|
144
|
+
const module = await SQLiteESMFactory(moduleArg);
|
|
145
|
+
db.sqlite3 = SQLite.Factory(module);
|
|
146
|
+
db.dbHandle = await db.sqlite3.open_v2(':memory:');
|
|
147
|
+
db.useWorker = false;
|
|
148
|
+
await db.sqlite3.exec(db.dbHandle, PRAGMAS);
|
|
149
|
+
}
|
|
150
|
+
db.drizzleDb = createDrizzleInstance();
|
|
151
|
+
// Run DDL (generated by npm run db:generate). Uses IF NOT EXISTS.
|
|
152
|
+
for (const stmt of DDL) {
|
|
153
|
+
await execRaw(stmt);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get the Drizzle ORM database instance.
|
|
158
|
+
* Throws if initDb() has not been called.
|
|
159
|
+
*/
|
|
160
|
+
export function getSqliteDb() {
|
|
161
|
+
if (!db.drizzleDb) {
|
|
162
|
+
throw new Error('SQLite not initialized. Call initDb() first.');
|
|
163
|
+
}
|
|
164
|
+
return db.drizzleDb;
|
|
165
|
+
}
|
|
166
|
+
export function isSqliteOpen() {
|
|
167
|
+
return db.drizzleDb !== null;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Run a callback inside a SQLite transaction (BEGIN / COMMIT / ROLLBACK).
|
|
171
|
+
* All Drizzle operations inside the callback share the same transaction
|
|
172
|
+
* and the same dbLock hold, so they cannot interleave with outside queries.
|
|
173
|
+
*/
|
|
174
|
+
export async function withTransaction(fn) {
|
|
175
|
+
const prev = db.dbLock;
|
|
176
|
+
let release;
|
|
177
|
+
db.dbLock = new Promise(r => (release = r));
|
|
178
|
+
await prev;
|
|
179
|
+
try {
|
|
180
|
+
await execRawDirect('BEGIN');
|
|
181
|
+
db.inTransaction = true;
|
|
182
|
+
try {
|
|
183
|
+
const result = await fn();
|
|
184
|
+
await execRawDirect('COMMIT');
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
await execRawDirect('ROLLBACK');
|
|
189
|
+
throw e;
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
db.inTransaction = false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
release();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export async function clearAllTables() {
|
|
200
|
+
await withTransaction(async () => {
|
|
201
|
+
const drizzleDb = getSqliteDb();
|
|
202
|
+
await drizzleDb.delete(schema.messages);
|
|
203
|
+
await drizzleDb.delete(schema.discussions);
|
|
204
|
+
await drizzleDb.delete(schema.contacts);
|
|
205
|
+
await drizzleDb.delete(schema.userProfile);
|
|
206
|
+
await drizzleDb.delete(schema.pendingEncryptedMessages);
|
|
207
|
+
await drizzleDb.delete(schema.pendingAnnouncements);
|
|
208
|
+
await drizzleDb.delete(schema.activeSeekers);
|
|
209
|
+
await drizzleDb.delete(schema.announcementCursors);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Clear only conversation-related tables (contacts, discussions, messages).
|
|
214
|
+
* Preserves user profiles and other data.
|
|
215
|
+
*/
|
|
216
|
+
export async function clearConversationTables() {
|
|
217
|
+
await withTransaction(async () => {
|
|
218
|
+
const drizzleDb = getSqliteDb();
|
|
219
|
+
await drizzleDb.delete(schema.messages);
|
|
220
|
+
await drizzleDb.delete(schema.discussions);
|
|
221
|
+
await drizzleDb.delete(schema.contacts);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get the last auto-increment row ID inserted via this connection.
|
|
226
|
+
* Used after INSERT into tables with INTEGER PRIMARY KEY AUTOINCREMENT.
|
|
227
|
+
*
|
|
228
|
+
* Browser path: returns the cached value from the last Worker exec response
|
|
229
|
+
* (returned atomically with every exec — no race condition).
|
|
230
|
+
* Test path: queries directly (same connection, serialized by dbLock).
|
|
231
|
+
*/
|
|
232
|
+
export async function getLastInsertRowId() {
|
|
233
|
+
if (db.useWorker) {
|
|
234
|
+
return db.lastInsertRowIdCache;
|
|
235
|
+
}
|
|
236
|
+
const rows = await execRaw('SELECT last_insert_rowid()');
|
|
237
|
+
return rows[0][0];
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Close the database and release all resources.
|
|
241
|
+
* Browser path: sends close to Worker, then terminates it.
|
|
242
|
+
* Test path: closes in-process database handle.
|
|
243
|
+
* Atomically resets all state by replacing with a fresh default.
|
|
244
|
+
*/
|
|
245
|
+
export async function closeSqlite() {
|
|
246
|
+
if (db.useWorker && db.worker) {
|
|
247
|
+
await postToWorker({ type: 'close' });
|
|
248
|
+
db.worker.terminate();
|
|
249
|
+
}
|
|
250
|
+
else if (db.dbHandle !== null && db.sqlite3) {
|
|
251
|
+
await db.sqlite3.close(db.dbHandle);
|
|
252
|
+
}
|
|
253
|
+
db = createDefaultState();
|
|
254
|
+
}
|
package/dist/gossip.d.ts
CHANGED
|
@@ -1,44 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GossipSdk - Singleton SDK with clean lifecycle API
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```typescript
|
|
6
|
-
* import { gossipSdk } from '@massalabs/gossip-sdk';
|
|
7
|
-
*
|
|
8
|
-
* // Initialize once at app startup
|
|
9
|
-
* await gossipSdk.init({
|
|
10
|
-
* db,
|
|
11
|
-
* protocolBaseUrl: 'https://api.example.com',
|
|
12
|
-
* });
|
|
13
|
-
*
|
|
14
|
-
* // Open session (login) - SDK handles keys/session internally
|
|
15
|
-
* await gossipSdk.openSession({
|
|
16
|
-
* mnemonic: 'word1 word2 ...',
|
|
17
|
-
* onPersist: async (blob) => { /* save to db *\/ },
|
|
18
|
-
* });
|
|
19
|
-
*
|
|
20
|
-
* // Or restore existing session
|
|
21
|
-
* await gossipSdk.openSession({
|
|
22
|
-
* mnemonic: 'word1 word2 ...',
|
|
23
|
-
* encryptedSession: savedBlob,
|
|
24
|
-
* encryptionKey: key,
|
|
25
|
-
* onPersist: async (blob) => { /* save to db *\/ },
|
|
26
|
-
* });
|
|
27
|
-
*
|
|
28
|
-
* // Use clean API
|
|
29
|
-
* await gossipSdk.messages.send(contactId, 'Hello!');
|
|
30
|
-
* await gossipSdk.discussions.start(contact);
|
|
31
|
-
* const contacts = await gossipSdk.contacts.list(ownerUserId);
|
|
32
|
-
*
|
|
33
|
-
* // Events
|
|
34
|
-
* gossipSdk.on('message', (msg) => { ... });
|
|
35
|
-
* gossipSdk.on('discussionRequest', (discussion, contact) => { ... });
|
|
36
|
-
*
|
|
37
|
-
* // Logout
|
|
38
|
-
* await gossipSdk.closeSession();
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
import { type Contact, type Discussion, type Message } from './db';
|
|
42
1
|
import { type SdkConfig, type DeepPartial } from './config/sdk';
|
|
43
2
|
import { SessionStatus, SessionConfig } from './assets/generated/wasm/gossip_wasm';
|
|
44
3
|
import { EncryptionKey } from './wasm/encryption';
|
|
@@ -48,6 +7,7 @@ import { type MessageResult, type SendMessageResult } from './services/message';
|
|
|
48
7
|
import { AuthService } from './services/auth';
|
|
49
8
|
import type { DeleteContactResult, UpdateContactNameResult } from './utils/contacts';
|
|
50
9
|
import { type ValidationResult } from './utils/validation';
|
|
10
|
+
import { type Contact, type Discussion, type Message } from './db';
|
|
51
11
|
import type { UserPublicKeys } from './wasm/bindings';
|
|
52
12
|
import { SdkEventType, type SdkEventHandlers } from './core/SdkEventEmitter';
|
|
53
13
|
import { AnnouncementPayload } from './utils/announcementPayload';
|