@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
package/dist/sqlite.js
DELETED
|
@@ -1,448 +0,0 @@
|
|
|
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.js';
|
|
17
|
-
function createDefaultState() {
|
|
18
|
-
return {
|
|
19
|
-
worker: null,
|
|
20
|
-
msgId: 0,
|
|
21
|
-
pending: new Map(),
|
|
22
|
-
lastInsertRowIdCache: 0,
|
|
23
|
-
sqlite3: null,
|
|
24
|
-
dbHandle: null,
|
|
25
|
-
useWorker: false,
|
|
26
|
-
drizzleDb: null,
|
|
27
|
-
dbLock: Promise.resolve(),
|
|
28
|
-
inTransaction: false,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
let db = createDefaultState();
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Worker communication (browser path)
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
function postToWorker(msg
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
-
) {
|
|
38
|
-
return new Promise((resolve, reject) => {
|
|
39
|
-
const id = ++db.msgId;
|
|
40
|
-
db.pending.set(id, { resolve, reject });
|
|
41
|
-
db.worker.postMessage({ ...msg, id });
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
function handleWorkerMessage(e) {
|
|
45
|
-
const { id, type, ...rest } = e.data;
|
|
46
|
-
const p = db.pending.get(id);
|
|
47
|
-
if (!p)
|
|
48
|
-
return;
|
|
49
|
-
db.pending.delete(id);
|
|
50
|
-
if (type === 'error') {
|
|
51
|
-
p.reject(new Error(rest.message));
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
p.resolve(rest);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// Drizzle instance factory
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
function createDrizzleInstance() {
|
|
61
|
-
return drizzle(async (sql, params, method) => {
|
|
62
|
-
const rows = await execRaw(sql, params);
|
|
63
|
-
if (method === 'get') {
|
|
64
|
-
return { rows: rows[0] };
|
|
65
|
-
}
|
|
66
|
-
return { rows };
|
|
67
|
-
}, { schema });
|
|
68
|
-
}
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Raw SQL execution
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
async function execRaw(sql, params = []) {
|
|
73
|
-
// When inside a withTransaction(), the outer lock is already held —
|
|
74
|
-
// skip re-acquisition to avoid deadlock.
|
|
75
|
-
if (db.inTransaction) {
|
|
76
|
-
return execRawDirect(sql, params);
|
|
77
|
-
}
|
|
78
|
-
const prev = db.dbLock;
|
|
79
|
-
let release;
|
|
80
|
-
db.dbLock = new Promise(r => (release = r));
|
|
81
|
-
await prev;
|
|
82
|
-
try {
|
|
83
|
-
return await execRawDirect(sql, params);
|
|
84
|
-
}
|
|
85
|
-
finally {
|
|
86
|
-
release();
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
async function execRawDirect(sql, params = []) {
|
|
90
|
-
if (db.useWorker) {
|
|
91
|
-
const result = await postToWorker({ type: 'exec', sql, params });
|
|
92
|
-
db.lastInsertRowIdCache = result.lastInsertRowId;
|
|
93
|
-
return result.rows;
|
|
94
|
-
}
|
|
95
|
-
return execRawInProcess(sql, params);
|
|
96
|
-
}
|
|
97
|
-
// wa-sqlite's column_blob() returns a Uint8Array VIEW into WASM linear memory
|
|
98
|
-
// (Module.HEAPU8.subarray). These views become stale after finalize() frees the
|
|
99
|
-
// prepared statement, or if WASM memory grows. Copy blob values so callers get
|
|
100
|
-
// owned data that survives beyond the current query.
|
|
101
|
-
function copyRow(row) {
|
|
102
|
-
return row.map(v => (v instanceof Uint8Array ? new Uint8Array(v) : v));
|
|
103
|
-
}
|
|
104
|
-
async function execRawInProcess(sql, params = []) {
|
|
105
|
-
if (!db.sqlite3 || db.dbHandle === null) {
|
|
106
|
-
throw new Error('SQLite not initialized');
|
|
107
|
-
}
|
|
108
|
-
// Simple path for parameterless statements
|
|
109
|
-
if (params.length === 0) {
|
|
110
|
-
const rows = [];
|
|
111
|
-
await db.sqlite3.exec(db.dbHandle, sql, (row) => {
|
|
112
|
-
rows.push(copyRow(row));
|
|
113
|
-
});
|
|
114
|
-
return rows;
|
|
115
|
-
}
|
|
116
|
-
// Prepared statement path for parameterized queries
|
|
117
|
-
const str = db.sqlite3.str_new(db.dbHandle, sql);
|
|
118
|
-
try {
|
|
119
|
-
const prepared = await db.sqlite3.prepare_v2(db.dbHandle, db.sqlite3.str_value(str));
|
|
120
|
-
if (!prepared)
|
|
121
|
-
return [];
|
|
122
|
-
try {
|
|
123
|
-
db.sqlite3.bind_collection(prepared.stmt, params);
|
|
124
|
-
const rows = [];
|
|
125
|
-
while ((await db.sqlite3.step(prepared.stmt)) === SQLite.SQLITE_ROW) {
|
|
126
|
-
rows.push(copyRow(db.sqlite3.row(prepared.stmt)));
|
|
127
|
-
}
|
|
128
|
-
return rows;
|
|
129
|
-
}
|
|
130
|
-
finally {
|
|
131
|
-
await db.sqlite3.finalize(prepared.stmt);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
db.sqlite3.str_finish(str);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
// DDL — CREATE TABLE + indexes
|
|
140
|
-
//
|
|
141
|
-
// IMPORTANT: This DDL must stay in sync with schema.ts.
|
|
142
|
-
// When adding/removing/renaming columns, tables, or indexes, update BOTH files.
|
|
143
|
-
// schema.ts is the Drizzle type source; this DDL is the runtime CREATE source.
|
|
144
|
-
// ---------------------------------------------------------------------------
|
|
145
|
-
const DDL = `
|
|
146
|
-
CREATE TABLE IF NOT EXISTS contacts (
|
|
147
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
148
|
-
ownerUserId TEXT NOT NULL,
|
|
149
|
-
userId TEXT NOT NULL,
|
|
150
|
-
name TEXT NOT NULL,
|
|
151
|
-
avatar TEXT,
|
|
152
|
-
publicKeys BLOB NOT NULL,
|
|
153
|
-
isOnline INTEGER NOT NULL DEFAULT 0,
|
|
154
|
-
lastSeen INTEGER NOT NULL,
|
|
155
|
-
createdAt INTEGER NOT NULL
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
159
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
160
|
-
ownerUserId TEXT NOT NULL,
|
|
161
|
-
contactUserId TEXT NOT NULL,
|
|
162
|
-
messageId BLOB,
|
|
163
|
-
content TEXT NOT NULL,
|
|
164
|
-
serializedContent BLOB,
|
|
165
|
-
type TEXT NOT NULL,
|
|
166
|
-
direction TEXT NOT NULL,
|
|
167
|
-
status TEXT NOT NULL,
|
|
168
|
-
timestamp INTEGER NOT NULL,
|
|
169
|
-
metadata TEXT,
|
|
170
|
-
seeker BLOB,
|
|
171
|
-
replyTo TEXT,
|
|
172
|
-
forwardOf TEXT,
|
|
173
|
-
encryptedMessage BLOB,
|
|
174
|
-
whenToSend INTEGER
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
CREATE TABLE IF NOT EXISTS userProfile (
|
|
178
|
-
userId TEXT PRIMARY KEY,
|
|
179
|
-
username TEXT NOT NULL,
|
|
180
|
-
avatar TEXT,
|
|
181
|
-
bio TEXT,
|
|
182
|
-
status TEXT NOT NULL,
|
|
183
|
-
lastSeen INTEGER NOT NULL,
|
|
184
|
-
createdAt INTEGER NOT NULL,
|
|
185
|
-
updatedAt INTEGER NOT NULL,
|
|
186
|
-
lastPublicKeyPush INTEGER,
|
|
187
|
-
security TEXT NOT NULL,
|
|
188
|
-
session BLOB NOT NULL
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
CREATE TABLE IF NOT EXISTS discussions (
|
|
192
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
193
|
-
ownerUserId TEXT NOT NULL,
|
|
194
|
-
contactUserId TEXT NOT NULL,
|
|
195
|
-
direction TEXT NOT NULL,
|
|
196
|
-
status TEXT NOT NULL,
|
|
197
|
-
weAccepted INTEGER NOT NULL DEFAULT 0,
|
|
198
|
-
sendAnnouncement TEXT,
|
|
199
|
-
nextSeeker BLOB,
|
|
200
|
-
initiationAnnouncement BLOB,
|
|
201
|
-
announcementMessage TEXT,
|
|
202
|
-
lastSyncTimestamp INTEGER,
|
|
203
|
-
customName TEXT,
|
|
204
|
-
lastMessageId INTEGER,
|
|
205
|
-
lastMessageContent TEXT,
|
|
206
|
-
lastMessageTimestamp INTEGER,
|
|
207
|
-
unreadCount INTEGER NOT NULL DEFAULT 0,
|
|
208
|
-
createdAt INTEGER NOT NULL,
|
|
209
|
-
updatedAt INTEGER NOT NULL
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
CREATE TABLE IF NOT EXISTS pendingEncryptedMessages (
|
|
213
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
214
|
-
seeker BLOB NOT NULL,
|
|
215
|
-
ciphertext BLOB NOT NULL,
|
|
216
|
-
fetchedAt INTEGER NOT NULL
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
CREATE TABLE IF NOT EXISTS pendingAnnouncements (
|
|
220
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
221
|
-
announcement BLOB NOT NULL,
|
|
222
|
-
fetchedAt INTEGER NOT NULL,
|
|
223
|
-
counter TEXT
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
CREATE TABLE IF NOT EXISTS activeSeekers (
|
|
227
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
228
|
-
seeker BLOB NOT NULL
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
-- Indexes: contacts
|
|
232
|
-
CREATE INDEX IF NOT EXISTS contacts_owner_user_idx ON contacts(ownerUserId, userId);
|
|
233
|
-
CREATE INDEX IF NOT EXISTS contacts_owner_name_idx ON contacts(ownerUserId, name);
|
|
234
|
-
|
|
235
|
-
-- Indexes: messages
|
|
236
|
-
CREATE INDEX IF NOT EXISTS messages_owner_contact_idx ON messages(ownerUserId, contactUserId);
|
|
237
|
-
CREATE INDEX IF NOT EXISTS messages_owner_status_idx ON messages(ownerUserId, status);
|
|
238
|
-
CREATE INDEX IF NOT EXISTS messages_owner_contact_status_idx ON messages(ownerUserId, contactUserId, status);
|
|
239
|
-
CREATE INDEX IF NOT EXISTS messages_owner_seeker_idx ON messages(ownerUserId, seeker);
|
|
240
|
-
CREATE INDEX IF NOT EXISTS messages_owner_contact_dir_idx ON messages(ownerUserId, contactUserId, direction);
|
|
241
|
-
CREATE INDEX IF NOT EXISTS messages_owner_dir_status_idx ON messages(ownerUserId, direction, status);
|
|
242
|
-
CREATE INDEX IF NOT EXISTS messages_timestamp_idx ON messages(timestamp);
|
|
243
|
-
|
|
244
|
-
-- Indexes: userProfile
|
|
245
|
-
CREATE INDEX IF NOT EXISTS userProfile_username_idx ON userProfile(username);
|
|
246
|
-
CREATE INDEX IF NOT EXISTS userProfile_status_idx ON userProfile(status);
|
|
247
|
-
|
|
248
|
-
-- Indexes: discussions
|
|
249
|
-
CREATE UNIQUE INDEX IF NOT EXISTS discussions_owner_contact_idx ON discussions(ownerUserId, contactUserId);
|
|
250
|
-
CREATE INDEX IF NOT EXISTS discussions_owner_status_idx ON discussions(ownerUserId, status);
|
|
251
|
-
|
|
252
|
-
-- Indexes: pendingEncryptedMessages
|
|
253
|
-
CREATE INDEX IF NOT EXISTS pending_encrypted_seeker_idx ON pendingEncryptedMessages(seeker);
|
|
254
|
-
CREATE INDEX IF NOT EXISTS pending_encrypted_fetchedAt_idx ON pendingEncryptedMessages(fetchedAt);
|
|
255
|
-
|
|
256
|
-
-- Indexes: pendingAnnouncements
|
|
257
|
-
CREATE UNIQUE INDEX IF NOT EXISTS pending_announcements_announcement_idx ON pendingAnnouncements(announcement);
|
|
258
|
-
CREATE INDEX IF NOT EXISTS pending_announcements_fetchedAt_idx ON pendingAnnouncements(fetchedAt);
|
|
259
|
-
|
|
260
|
-
-- Indexes: activeSeekers
|
|
261
|
-
CREATE INDEX IF NOT EXISTS active_seekers_seeker_idx ON activeSeekers(seeker);
|
|
262
|
-
|
|
263
|
-
CREATE TABLE IF NOT EXISTS announcementCursors (
|
|
264
|
-
userId TEXT PRIMARY KEY,
|
|
265
|
-
counter TEXT NOT NULL
|
|
266
|
-
);
|
|
267
|
-
`;
|
|
268
|
-
// ---------------------------------------------------------------------------
|
|
269
|
-
// Migrations — add columns that may be missing from older schema versions.
|
|
270
|
-
// ALTER TABLE … ADD COLUMN is idempotent via try/catch (SQLite errors on
|
|
271
|
-
// duplicate columns; we simply ignore that error).
|
|
272
|
-
// ---------------------------------------------------------------------------
|
|
273
|
-
/**
|
|
274
|
-
* Run schema migrations that can't be expressed with CREATE TABLE IF NOT EXISTS.
|
|
275
|
-
* Each migration is a single ALTER TABLE statement; duplicates are caught and ignored.
|
|
276
|
-
*/
|
|
277
|
-
async function runMigrations(tryExec) {
|
|
278
|
-
const alterStatements = [
|
|
279
|
-
// v1: add messageId column to messages table
|
|
280
|
-
'ALTER TABLE messages ADD COLUMN messageId BLOB;',
|
|
281
|
-
];
|
|
282
|
-
for (const sql of alterStatements) {
|
|
283
|
-
await tryExec(sql);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
/** PRAGMAs + DDL combined — sent to Worker during init or run in-process. */
|
|
287
|
-
const INIT_SQL = `
|
|
288
|
-
PRAGMA journal_mode=MEMORY;
|
|
289
|
-
PRAGMA temp_store=MEMORY;
|
|
290
|
-
${DDL}
|
|
291
|
-
`;
|
|
292
|
-
// ---------------------------------------------------------------------------
|
|
293
|
-
// Public API
|
|
294
|
-
// ---------------------------------------------------------------------------
|
|
295
|
-
/**
|
|
296
|
-
* Initialize wa-sqlite and create the Drizzle ORM instance.
|
|
297
|
-
* Idempotent — subsequent calls are no-ops.
|
|
298
|
-
*
|
|
299
|
-
* @param options.opfsPath - Set to persist via OPFS Worker (production).
|
|
300
|
-
* Omit for in-memory database (tests).
|
|
301
|
-
*/
|
|
302
|
-
export async function initDb(options = {}) {
|
|
303
|
-
if (db.drizzleDb)
|
|
304
|
-
return;
|
|
305
|
-
if (options.opfsPath) {
|
|
306
|
-
// Browser path: spawn Worker with OPFS + AccessHandlePoolVFS.
|
|
307
|
-
// The sync WASM build runs in the Worker (no Asyncify needed).
|
|
308
|
-
db.worker = new Worker(new URL('./sqlite-worker.ts', import.meta.url), {
|
|
309
|
-
type: 'module',
|
|
310
|
-
});
|
|
311
|
-
db.worker.onmessage = handleWorkerMessage;
|
|
312
|
-
db.useWorker = true;
|
|
313
|
-
await postToWorker({
|
|
314
|
-
type: 'init',
|
|
315
|
-
opfsPath: options.opfsPath,
|
|
316
|
-
wasmUrl: options.wasmUrl,
|
|
317
|
-
initSql: INIT_SQL,
|
|
318
|
-
});
|
|
319
|
-
// Run migrations via Worker exec (ignore "duplicate column" errors)
|
|
320
|
-
await runMigrations(async (sql) => {
|
|
321
|
-
try {
|
|
322
|
-
await postToWorker({ type: 'exec', sql, params: [] });
|
|
323
|
-
}
|
|
324
|
-
catch {
|
|
325
|
-
// Expected: "duplicate column name: …" when column already exists
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
// In-memory mode (tests): sync WASM build, in-process, fast, isolated.
|
|
331
|
-
const moduleArg = {};
|
|
332
|
-
if (options.wasmBinary) {
|
|
333
|
-
moduleArg.wasmBinary = options.wasmBinary;
|
|
334
|
-
}
|
|
335
|
-
const module = await SQLiteESMFactory(moduleArg);
|
|
336
|
-
db.sqlite3 = SQLite.Factory(module);
|
|
337
|
-
db.dbHandle = await db.sqlite3.open_v2(':memory:');
|
|
338
|
-
db.useWorker = false;
|
|
339
|
-
await db.sqlite3.exec(db.dbHandle, INIT_SQL);
|
|
340
|
-
// Run migrations in-process (ignore "duplicate column" errors)
|
|
341
|
-
await runMigrations(async (sql) => {
|
|
342
|
-
try {
|
|
343
|
-
await db.sqlite3.exec(db.dbHandle, sql);
|
|
344
|
-
}
|
|
345
|
-
catch {
|
|
346
|
-
// Expected: "duplicate column name: …" when column already exists
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
db.drizzleDb = createDrizzleInstance();
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Get the Drizzle ORM database instance.
|
|
354
|
-
* Throws if initDb() has not been called.
|
|
355
|
-
*/
|
|
356
|
-
export function getSqliteDb() {
|
|
357
|
-
if (!db.drizzleDb) {
|
|
358
|
-
throw new Error('SQLite not initialized. Call initDb() first.');
|
|
359
|
-
}
|
|
360
|
-
return db.drizzleDb;
|
|
361
|
-
}
|
|
362
|
-
export function isSqliteOpen() {
|
|
363
|
-
return db.drizzleDb !== null;
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Run a callback inside a SQLite transaction (BEGIN / COMMIT / ROLLBACK).
|
|
367
|
-
* All Drizzle operations inside the callback share the same transaction
|
|
368
|
-
* and the same dbLock hold, so they cannot interleave with outside queries.
|
|
369
|
-
*/
|
|
370
|
-
export async function withTransaction(fn) {
|
|
371
|
-
const prev = db.dbLock;
|
|
372
|
-
let release;
|
|
373
|
-
db.dbLock = new Promise(r => (release = r));
|
|
374
|
-
await prev;
|
|
375
|
-
try {
|
|
376
|
-
await execRawDirect('BEGIN');
|
|
377
|
-
db.inTransaction = true;
|
|
378
|
-
try {
|
|
379
|
-
const result = await fn();
|
|
380
|
-
await execRawDirect('COMMIT');
|
|
381
|
-
return result;
|
|
382
|
-
}
|
|
383
|
-
catch (e) {
|
|
384
|
-
await execRawDirect('ROLLBACK');
|
|
385
|
-
throw e;
|
|
386
|
-
}
|
|
387
|
-
finally {
|
|
388
|
-
db.inTransaction = false;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
finally {
|
|
392
|
-
release();
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
export async function clearAllTables() {
|
|
396
|
-
const drizzleDb = getSqliteDb();
|
|
397
|
-
await drizzleDb.delete(schema.messages);
|
|
398
|
-
await drizzleDb.delete(schema.discussions);
|
|
399
|
-
await drizzleDb.delete(schema.contacts);
|
|
400
|
-
await drizzleDb.delete(schema.userProfile);
|
|
401
|
-
await drizzleDb.delete(schema.pendingEncryptedMessages);
|
|
402
|
-
await drizzleDb.delete(schema.pendingAnnouncements);
|
|
403
|
-
await drizzleDb.delete(schema.activeSeekers);
|
|
404
|
-
await drizzleDb.delete(schema.announcementCursors);
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Clear only conversation-related tables (contacts, discussions, messages).
|
|
408
|
-
* Preserves user profiles and other data.
|
|
409
|
-
*/
|
|
410
|
-
export async function clearConversationTables() {
|
|
411
|
-
const drizzleDb = getSqliteDb();
|
|
412
|
-
await drizzleDb.delete(schema.messages);
|
|
413
|
-
await drizzleDb.delete(schema.discussions);
|
|
414
|
-
await drizzleDb.delete(schema.contacts);
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Get the last auto-increment row ID inserted via this connection.
|
|
418
|
-
* Used after INSERT into tables with INTEGER PRIMARY KEY AUTOINCREMENT.
|
|
419
|
-
*
|
|
420
|
-
* Browser path: returns the cached value from the last Worker exec response
|
|
421
|
-
* (returned atomically with every exec — no race condition).
|
|
422
|
-
* Test path: queries directly (same connection, serialized by dbLock).
|
|
423
|
-
*/
|
|
424
|
-
export async function getLastInsertRowId() {
|
|
425
|
-
if (db.useWorker) {
|
|
426
|
-
return db.lastInsertRowIdCache;
|
|
427
|
-
}
|
|
428
|
-
const rows = await execRaw('SELECT last_insert_rowid()');
|
|
429
|
-
return rows[0][0];
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Close the database and release all resources.
|
|
433
|
-
* Browser path: sends close to Worker, then terminates it.
|
|
434
|
-
* Test path: closes in-process database handle.
|
|
435
|
-
* Atomically resets all state by replacing with a fresh default.
|
|
436
|
-
*/
|
|
437
|
-
export async function closeSqlite() {
|
|
438
|
-
if (db.useWorker && db.worker) {
|
|
439
|
-
await postToWorker({ type: 'close' });
|
|
440
|
-
db.worker.terminate();
|
|
441
|
-
}
|
|
442
|
-
else if (db.dbHandle !== null && db.sqlite3) {
|
|
443
|
-
await db.sqlite3.close(db.dbHandle);
|
|
444
|
-
}
|
|
445
|
-
db = createDefaultState();
|
|
446
|
-
}
|
|
447
|
-
/** Exported for testing — the raw DDL string used to create tables. */
|
|
448
|
-
export const _DDL_FOR_TESTING = DDL;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|