@massalabs/gossip-sdk 0.0.2-dev.20260223131319 → 0.0.2-dev.20260224143454

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.
Files changed (47) hide show
  1. package/dist/db/generated-migrations.d.ts +7 -0
  2. package/dist/db/generated-migrations.js +45 -0
  3. package/dist/db/migrate.d.ts +11 -0
  4. package/dist/db/migrate.js +26 -0
  5. package/dist/db/node-fs-vfs.d.ts +32 -0
  6. package/dist/db/node-fs-vfs.js +165 -0
  7. package/dist/db/queries/activeSeekers.d.ts +6 -1
  8. package/dist/db/queries/activeSeekers.js +19 -11
  9. package/dist/db/queries/announcementCursors.d.ts +7 -2
  10. package/dist/db/queries/announcementCursors.js +26 -17
  11. package/dist/db/queries/contacts.d.ts +13 -8
  12. package/dist/db/queries/contacts.js +45 -36
  13. package/dist/db/queries/discussions.d.ts +15 -16
  14. package/dist/db/queries/discussions.js +73 -70
  15. package/dist/db/queries/index.d.ts +31 -7
  16. package/dist/db/queries/index.js +73 -7
  17. package/dist/db/queries/messages.d.ts +23 -23
  18. package/dist/db/queries/messages.js +120 -116
  19. package/dist/db/queries/pendingAnnouncements.d.ts +7 -2
  20. package/dist/db/queries/pendingAnnouncements.js +17 -8
  21. package/dist/db/queries/userProfile.d.ts +19 -14
  22. package/dist/db/queries/userProfile.js +72 -63
  23. package/dist/db/sqlite.d.ts +45 -68
  24. package/dist/db/sqlite.js +219 -218
  25. package/dist/gossip.d.ts +35 -86
  26. package/dist/gossip.js +115 -130
  27. package/dist/services/announcement.d.ts +7 -1
  28. package/dist/services/announcement.js +42 -28
  29. package/dist/services/contact.d.ts +24 -0
  30. package/dist/services/contact.js +51 -0
  31. package/dist/services/discussion.d.ts +29 -1
  32. package/dist/services/discussion.js +99 -15
  33. package/dist/services/message.d.ts +28 -1
  34. package/dist/services/message.js +93 -36
  35. package/dist/services/profile.d.ts +22 -0
  36. package/dist/services/profile.js +85 -0
  37. package/dist/services/refresh.d.ts +3 -1
  38. package/dist/services/refresh.js +10 -4
  39. package/dist/utils/contacts.d.ts +5 -4
  40. package/dist/utils/contacts.js +13 -14
  41. package/dist/utils/discussions.d.ts +2 -1
  42. package/dist/utils/discussions.js +3 -4
  43. package/dist/utils/validation.d.ts +3 -2
  44. package/dist/utils/validation.js +4 -5
  45. package/package.json +1 -1
  46. package/dist/db/generated-ddl.d.ts +0 -1
  47. package/dist/db/generated-ddl.js +0 -31
@@ -0,0 +1,7 @@
1
+ export interface EmbeddedMigration {
2
+ idx: number;
3
+ tag: string;
4
+ when: number;
5
+ statements: string[];
6
+ }
7
+ export declare const MIGRATIONS: EmbeddedMigration[];
@@ -0,0 +1,45 @@
1
+ // Auto-generated from drizzle migrations — do not edit manually.
2
+ // Regenerate with: npm run db:generate
3
+ export const MIGRATIONS = [
4
+ {
5
+ idx: 0,
6
+ tag: '0000_nifty_molly_hayes',
7
+ when: 1771825473415,
8
+ statements: [
9
+ 'CREATE TABLE `activeSeekers` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`seeker` blob NOT NULL\n);',
10
+ 'CREATE INDEX `active_seekers_seeker_idx` ON `activeSeekers` (`seeker`);',
11
+ 'CREATE TABLE `announcementCursors` (\n\t`userId` text PRIMARY KEY NOT NULL,\n\t`counter` text NOT NULL\n);',
12
+ 'CREATE TABLE `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);',
13
+ 'CREATE INDEX `contacts_owner_user_idx` ON `contacts` (`ownerUserId`,`userId`);',
14
+ 'CREATE INDEX `contacts_owner_name_idx` ON `contacts` (`ownerUserId`,`name`);',
15
+ 'CREATE TABLE `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);',
16
+ 'CREATE UNIQUE INDEX `discussions_owner_contact_idx` ON `discussions` (`ownerUserId`,`contactUserId`);',
17
+ 'CREATE INDEX `discussions_owner_status_idx` ON `discussions` (`ownerUserId`,`status`);',
18
+ 'CREATE TABLE `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);',
19
+ 'CREATE INDEX `messages_owner_contact_idx` ON `messages` (`ownerUserId`,`contactUserId`);',
20
+ 'CREATE INDEX `messages_owner_status_idx` ON `messages` (`ownerUserId`,`status`);',
21
+ 'CREATE INDEX `messages_owner_contact_status_idx` ON `messages` (`ownerUserId`,`contactUserId`,`status`);',
22
+ 'CREATE INDEX `messages_owner_seeker_idx` ON `messages` (`ownerUserId`,`seeker`);',
23
+ 'CREATE INDEX `messages_owner_contact_dir_idx` ON `messages` (`ownerUserId`,`contactUserId`,`direction`);',
24
+ 'CREATE INDEX `messages_owner_dir_status_idx` ON `messages` (`ownerUserId`,`direction`,`status`);',
25
+ 'CREATE INDEX `messages_timestamp_idx` ON `messages` (`timestamp`);',
26
+ 'CREATE TABLE `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);',
27
+ 'CREATE UNIQUE INDEX `pending_announcements_announcement_idx` ON `pendingAnnouncements` (`announcement`);',
28
+ 'CREATE INDEX `pending_announcements_fetchedAt_idx` ON `pendingAnnouncements` (`fetchedAt`);',
29
+ 'CREATE TABLE `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);',
30
+ 'CREATE INDEX `pending_encrypted_seeker_idx` ON `pendingEncryptedMessages` (`seeker`);',
31
+ 'CREATE INDEX `pending_encrypted_fetchedAt_idx` ON `pendingEncryptedMessages` (`fetchedAt`);',
32
+ 'CREATE TABLE `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);',
33
+ 'CREATE INDEX `userProfile_username_idx` ON `userProfile` (`username`);',
34
+ 'CREATE INDEX `userProfile_status_idx` ON `userProfile` (`status`);',
35
+ ],
36
+ },
37
+ {
38
+ idx: 1,
39
+ tag: '0001_flowery_storm',
40
+ when: 1771827090330,
41
+ statements: [
42
+ 'CREATE INDEX `messages_owner_contact_msgid_idx` ON `messages` (`ownerUserId`,`contactUserId`,`messageId`);',
43
+ ],
44
+ },
45
+ ];
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Migration runner for the Gossip SDK SQLite database.
3
+ *
4
+ * Tracks applied migrations in a `_migrations` table and runs only
5
+ * pending ones. Each migration executes in its own transaction so
6
+ * a failure at migration N leaves 0..N-1 committed.
7
+ */
8
+ type ExecRaw = (sql: string, params?: unknown[]) => Promise<unknown[][]>;
9
+ type WithTransaction = <T>(fn: () => Promise<T>) => Promise<T>;
10
+ export declare function runMigrations(execRaw: ExecRaw, withTransaction: WithTransaction): Promise<void>;
11
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Migration runner for the Gossip SDK SQLite database.
3
+ *
4
+ * Tracks applied migrations in a `_migrations` table and runs only
5
+ * pending ones. Each migration executes in its own transaction so
6
+ * a failure at migration N leaves 0..N-1 committed.
7
+ */
8
+ import { MIGRATIONS } from './generated-migrations.js';
9
+ export async function runMigrations(execRaw, withTransaction) {
10
+ await execRaw(`CREATE TABLE IF NOT EXISTS _migrations (
11
+ idx INTEGER PRIMARY KEY,
12
+ tag TEXT NOT NULL,
13
+ applied_at INTEGER NOT NULL
14
+ )`);
15
+ const rows = await execRaw('SELECT MAX(idx) FROM _migrations');
16
+ const maxApplied = rows[0][0];
17
+ const pending = MIGRATIONS.filter(m => m.idx > (maxApplied ?? -1));
18
+ for (const migration of pending) {
19
+ await withTransaction(async () => {
20
+ for (const stmt of migration.statements) {
21
+ await execRaw(stmt);
22
+ }
23
+ await execRaw('INSERT INTO _migrations (idx, tag, applied_at) VALUES (?, ?, ?)', [migration.idx, migration.tag, Date.now()]);
24
+ });
25
+ }
26
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Node.js file-system VFS for wa-sqlite.
3
+ *
4
+ * Extends VFS.Base using Node.js `fs` sync APIs for file persistence.
5
+ * Follows the MemoryVFS pattern but writes to real files on disk.
6
+ *
7
+ * Locking: inherited no-ops from VFS.Base (sufficient for single-process).
8
+ * This VFS will later be replaceable by the encrypted VFS from the storage branch.
9
+ */
10
+ import * as VFS from 'wa-sqlite/src/VFS.js';
11
+ export declare class NodeFsVFS extends VFS.Base {
12
+ name: string;
13
+ private directory;
14
+ private mapIdToFile;
15
+ constructor(directory: string);
16
+ private resolvePath;
17
+ xOpen(name: string | null, fileId: number, flags: number, pOutFlags: DataView): number;
18
+ xClose(fileId: number): number;
19
+ xRead(fileId: number, pData_: {
20
+ size: number;
21
+ value: Uint8Array;
22
+ }, iOffset: number): number;
23
+ xWrite(fileId: number, pData_: {
24
+ size: number;
25
+ value: Uint8Array;
26
+ }, iOffset: number): number;
27
+ xTruncate(fileId: number, iSize: number): number;
28
+ xSync(fileId: number, _flags: number): number;
29
+ xFileSize(fileId: number, pSize64: DataView): number;
30
+ xDelete(name: string, _syncDir: number): number;
31
+ xAccess(name: string, _flags: number, pResOut: DataView): number;
32
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Node.js file-system VFS for wa-sqlite.
3
+ *
4
+ * Extends VFS.Base using Node.js `fs` sync APIs for file persistence.
5
+ * Follows the MemoryVFS pattern but writes to real files on disk.
6
+ *
7
+ * Locking: inherited no-ops from VFS.Base (sufficient for single-process).
8
+ * This VFS will later be replaceable by the encrypted VFS from the storage branch.
9
+ */
10
+ import * as fs from 'node:fs';
11
+ import * as path from 'node:path';
12
+ import * as VFS from 'wa-sqlite/src/VFS.js';
13
+ export class NodeFsVFS extends VFS.Base {
14
+ constructor(directory) {
15
+ super();
16
+ Object.defineProperty(this, "name", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: 'node-fs'
21
+ });
22
+ Object.defineProperty(this, "directory", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: void 0
27
+ });
28
+ Object.defineProperty(this, "mapIdToFile", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: new Map()
33
+ });
34
+ this.mxPathName = 255;
35
+ this.directory = directory;
36
+ fs.mkdirSync(directory, { recursive: true });
37
+ }
38
+ resolvePath(name) {
39
+ return path.join(this.directory, name);
40
+ }
41
+ xOpen(name, fileId, flags, pOutFlags) {
42
+ name =
43
+ name || Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36);
44
+ const filePath = this.resolvePath(name);
45
+ let openFlags = fs.constants.O_RDWR;
46
+ if (flags & VFS.SQLITE_OPEN_CREATE) {
47
+ openFlags |= fs.constants.O_CREAT;
48
+ }
49
+ try {
50
+ const fd = fs.openSync(filePath, openFlags, 0o644);
51
+ this.mapIdToFile.set(fileId, { name, fd, flags });
52
+ pOutFlags.setInt32(0, flags, true);
53
+ return VFS.SQLITE_OK;
54
+ }
55
+ catch {
56
+ return VFS.SQLITE_CANTOPEN;
57
+ }
58
+ }
59
+ xClose(fileId) {
60
+ const file = this.mapIdToFile.get(fileId);
61
+ if (!file)
62
+ return VFS.SQLITE_OK;
63
+ this.mapIdToFile.delete(fileId);
64
+ try {
65
+ fs.closeSync(file.fd);
66
+ }
67
+ catch {
68
+ // ignore close errors
69
+ }
70
+ if (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
71
+ try {
72
+ fs.unlinkSync(this.resolvePath(file.name));
73
+ }
74
+ catch {
75
+ // ignore delete errors
76
+ }
77
+ }
78
+ return VFS.SQLITE_OK;
79
+ }
80
+ // wa-sqlite d.ts declares pData as { size; value } but runtime passes
81
+ // a plain Uint8Array. Cast to match the base class signature.
82
+ xRead(fileId, pData_, iOffset) {
83
+ const pData = pData_;
84
+ const file = this.mapIdToFile.get(fileId);
85
+ if (!file)
86
+ return VFS.SQLITE_IOERR;
87
+ try {
88
+ const nRead = fs.readSync(file.fd, pData, 0, pData.byteLength, iOffset);
89
+ if (nRead < pData.byteLength) {
90
+ pData.fill(0, nRead);
91
+ return VFS.SQLITE_IOERR_SHORT_READ;
92
+ }
93
+ return VFS.SQLITE_OK;
94
+ }
95
+ catch {
96
+ return VFS.SQLITE_IOERR;
97
+ }
98
+ }
99
+ xWrite(fileId, pData_, iOffset) {
100
+ const pData = pData_;
101
+ const file = this.mapIdToFile.get(fileId);
102
+ if (!file)
103
+ return VFS.SQLITE_IOERR;
104
+ try {
105
+ fs.writeSync(file.fd, pData, 0, pData.byteLength, iOffset);
106
+ return VFS.SQLITE_OK;
107
+ }
108
+ catch {
109
+ return VFS.SQLITE_IOERR;
110
+ }
111
+ }
112
+ xTruncate(fileId, iSize) {
113
+ const file = this.mapIdToFile.get(fileId);
114
+ if (!file)
115
+ return VFS.SQLITE_IOERR;
116
+ try {
117
+ fs.ftruncateSync(file.fd, iSize);
118
+ return VFS.SQLITE_OK;
119
+ }
120
+ catch {
121
+ return VFS.SQLITE_IOERR;
122
+ }
123
+ }
124
+ xSync(fileId, _flags) {
125
+ const file = this.mapIdToFile.get(fileId);
126
+ if (!file)
127
+ return VFS.SQLITE_IOERR;
128
+ try {
129
+ fs.fsyncSync(file.fd);
130
+ return VFS.SQLITE_OK;
131
+ }
132
+ catch {
133
+ return VFS.SQLITE_IOERR;
134
+ }
135
+ }
136
+ xFileSize(fileId, pSize64) {
137
+ const file = this.mapIdToFile.get(fileId);
138
+ if (!file)
139
+ return VFS.SQLITE_IOERR;
140
+ try {
141
+ const size = fs.fstatSync(file.fd).size;
142
+ pSize64.setBigInt64(0, BigInt(size), true);
143
+ return VFS.SQLITE_OK;
144
+ }
145
+ catch {
146
+ return VFS.SQLITE_IOERR;
147
+ }
148
+ }
149
+ xDelete(name, _syncDir) {
150
+ try {
151
+ fs.unlinkSync(this.resolvePath(name));
152
+ }
153
+ catch (e) {
154
+ if (e.code !== 'ENOENT') {
155
+ return VFS.SQLITE_IOERR;
156
+ }
157
+ }
158
+ return VFS.SQLITE_OK;
159
+ }
160
+ xAccess(name, _flags, pResOut) {
161
+ const exists = fs.existsSync(this.resolvePath(name));
162
+ pResOut.setInt32(0, exists ? 1 : 0, true);
163
+ return VFS.SQLITE_OK;
164
+ }
165
+ }
@@ -1 +1,6 @@
1
- export declare function replaceActiveSeekers(seekers: Uint8Array[]): Promise<void>;
1
+ import type { DatabaseConnection } from '../sqlite';
2
+ export declare class ActiveSeekerQueries {
3
+ private conn;
4
+ constructor(conn: DatabaseConnection);
5
+ replaceAll(seekers: Uint8Array[]): Promise<void>;
6
+ }
@@ -1,13 +1,21 @@
1
1
  import * as schema from '../schema';
2
- import { getSqliteDb, withTransaction } from '../sqlite';
3
- export async function replaceActiveSeekers(seekers) {
4
- await withTransaction(async () => {
5
- const db = getSqliteDb();
6
- await db.delete(schema.activeSeekers);
7
- if (seekers.length > 0) {
8
- await db
9
- .insert(schema.activeSeekers)
10
- .values(seekers.map(seeker => ({ seeker })));
11
- }
12
- });
2
+ export class ActiveSeekerQueries {
3
+ constructor(conn) {
4
+ Object.defineProperty(this, "conn", {
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true,
8
+ value: conn
9
+ });
10
+ }
11
+ async replaceAll(seekers) {
12
+ await this.conn.withTransaction(async () => {
13
+ await this.conn.db.delete(schema.activeSeekers);
14
+ if (seekers.length > 0) {
15
+ await this.conn.db
16
+ .insert(schema.activeSeekers)
17
+ .values(seekers.map(seeker => ({ seeker })));
18
+ }
19
+ });
20
+ }
13
21
  }
@@ -1,2 +1,7 @@
1
- export declare function getAnnouncementCursor(userId: string): Promise<string | undefined>;
2
- export declare function upsertAnnouncementCursor(userId: string, counter: string): Promise<void>;
1
+ import type { DatabaseConnection } from '../sqlite';
2
+ export declare class AnnouncementCursorQueries {
3
+ private conn;
4
+ constructor(conn: DatabaseConnection);
5
+ get(userId: string): Promise<string | undefined>;
6
+ upsert(userId: string, counter: string): Promise<void>;
7
+ }
@@ -1,20 +1,29 @@
1
1
  import { eq } from 'drizzle-orm';
2
2
  import * as schema from '../schema';
3
- import { getSqliteDb } from '../sqlite';
4
- export async function getAnnouncementCursor(userId) {
5
- const row = await getSqliteDb()
6
- .select({ counter: schema.announcementCursors.counter })
7
- .from(schema.announcementCursors)
8
- .where(eq(schema.announcementCursors.userId, userId))
9
- .get();
10
- return row?.counter;
11
- }
12
- export async function upsertAnnouncementCursor(userId, counter) {
13
- await getSqliteDb()
14
- .insert(schema.announcementCursors)
15
- .values({ userId, counter })
16
- .onConflictDoUpdate({
17
- target: schema.announcementCursors.userId,
18
- set: { counter },
19
- });
3
+ export class AnnouncementCursorQueries {
4
+ constructor(conn) {
5
+ Object.defineProperty(this, "conn", {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value: conn
10
+ });
11
+ }
12
+ async get(userId) {
13
+ const row = await this.conn.db
14
+ .select({ counter: schema.announcementCursors.counter })
15
+ .from(schema.announcementCursors)
16
+ .where(eq(schema.announcementCursors.userId, userId))
17
+ .get();
18
+ return row?.counter;
19
+ }
20
+ async upsert(userId, counter) {
21
+ await this.conn.db
22
+ .insert(schema.announcementCursors)
23
+ .values({ userId, counter })
24
+ .onConflictDoUpdate({
25
+ target: schema.announcementCursors.userId,
26
+ set: { counter },
27
+ });
28
+ }
20
29
  }
@@ -1,13 +1,18 @@
1
1
  import * as schema from '../schema';
2
+ import type { DatabaseConnection } from '../sqlite';
2
3
  import { Contact } from '../db';
3
4
  export type ContactRow = typeof schema.contacts.$inferSelect;
4
5
  type ContactInsert = typeof schema.contacts.$inferInsert;
5
- export declare function getContactByOwnerAndUser(ownerUserId: string, userId: string): Promise<ContactRow | undefined>;
6
- export declare function getContactsByOwner(ownerUserId: string): Promise<Contact[]>;
7
- export declare function insertContact(values: ContactInsert): Promise<number>;
8
- export declare function updateContactByOwnerAndUser(ownerUserId: string, userId: string, data: Partial<ContactInsert>): Promise<void>;
9
- export declare function deleteContactByOwnerAndUser(ownerUserId: string, userId: string): Promise<void>;
10
- export declare function getContactNamesByPrefix(ownerUserId: string, prefix: string): Promise<{
11
- name: string;
12
- }[]>;
6
+ export declare class ContactQueries {
7
+ private conn;
8
+ constructor(conn: DatabaseConnection);
9
+ getByOwnerAndUser(ownerUserId: string, userId: string): Promise<ContactRow | undefined>;
10
+ getByOwner(ownerUserId: string): Promise<Contact[]>;
11
+ insert(values: ContactInsert): Promise<number>;
12
+ updateByOwnerAndUser(ownerUserId: string, userId: string, data: Partial<ContactInsert>): Promise<void>;
13
+ deleteByOwnerAndUser(ownerUserId: string, userId: string): Promise<void>;
14
+ getNamesByPrefix(ownerUserId: string, prefix: string): Promise<{
15
+ name: string;
16
+ }[]>;
17
+ }
13
18
  export {};
@@ -1,43 +1,52 @@
1
1
  import { eq, and, sql } from 'drizzle-orm';
2
2
  import * as schema from '../schema';
3
- import { getSqliteDb, getLastInsertRowId } from '../sqlite';
4
- export async function getContactByOwnerAndUser(ownerUserId, userId) {
5
- return getSqliteDb()
6
- .select()
7
- .from(schema.contacts)
8
- .where(and(eq(schema.contacts.ownerUserId, ownerUserId), eq(schema.contacts.userId, userId)))
9
- .get();
10
- }
11
- export async function getContactsByOwner(ownerUserId) {
12
- return getSqliteDb()
13
- .select()
14
- .from(schema.contacts)
15
- .where(eq(schema.contacts.ownerUserId, ownerUserId))
16
- .all();
17
- }
18
- export async function insertContact(values) {
19
- await getSqliteDb().insert(schema.contacts).values(values);
20
- return getLastInsertRowId();
21
- }
22
- export async function updateContactByOwnerAndUser(ownerUserId, userId, data) {
23
- await getSqliteDb()
24
- .update(schema.contacts)
25
- .set(data)
26
- .where(and(eq(schema.contacts.ownerUserId, ownerUserId), eq(schema.contacts.userId, userId)));
27
- }
28
- export async function deleteContactByOwnerAndUser(ownerUserId, userId) {
29
- await getSqliteDb()
30
- .delete(schema.contacts)
31
- .where(and(eq(schema.contacts.ownerUserId, ownerUserId), eq(schema.contacts.userId, userId)));
32
- }
33
3
  /** Escape LIKE wildcard characters so they match literally. */
34
4
  function escapeLike(value) {
35
5
  return value.replace(/[%_]/g, '\\$&');
36
6
  }
37
- export async function getContactNamesByPrefix(ownerUserId, prefix) {
38
- return getSqliteDb()
39
- .select({ name: schema.contacts.name })
40
- .from(schema.contacts)
41
- .where(and(eq(schema.contacts.ownerUserId, ownerUserId), sql `${schema.contacts.name} LIKE ${escapeLike(prefix) + '%'} ESCAPE '\\'`))
42
- .all();
7
+ export class ContactQueries {
8
+ constructor(conn) {
9
+ Object.defineProperty(this, "conn", {
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true,
13
+ value: conn
14
+ });
15
+ }
16
+ async getByOwnerAndUser(ownerUserId, userId) {
17
+ return this.conn.db
18
+ .select()
19
+ .from(schema.contacts)
20
+ .where(and(eq(schema.contacts.ownerUserId, ownerUserId), eq(schema.contacts.userId, userId)))
21
+ .get();
22
+ }
23
+ async getByOwner(ownerUserId) {
24
+ return this.conn.db
25
+ .select()
26
+ .from(schema.contacts)
27
+ .where(eq(schema.contacts.ownerUserId, ownerUserId))
28
+ .all();
29
+ }
30
+ async insert(values) {
31
+ await this.conn.db.insert(schema.contacts).values(values);
32
+ return this.conn.getLastInsertRowId();
33
+ }
34
+ async updateByOwnerAndUser(ownerUserId, userId, data) {
35
+ await this.conn.db
36
+ .update(schema.contacts)
37
+ .set(data)
38
+ .where(and(eq(schema.contacts.ownerUserId, ownerUserId), eq(schema.contacts.userId, userId)));
39
+ }
40
+ async deleteByOwnerAndUser(ownerUserId, userId) {
41
+ await this.conn.db
42
+ .delete(schema.contacts)
43
+ .where(and(eq(schema.contacts.ownerUserId, ownerUserId), eq(schema.contacts.userId, userId)));
44
+ }
45
+ async getNamesByPrefix(ownerUserId, prefix) {
46
+ return this.conn.db
47
+ .select({ name: schema.contacts.name })
48
+ .from(schema.contacts)
49
+ .where(and(eq(schema.contacts.ownerUserId, ownerUserId), sql `${schema.contacts.name} LIKE ${escapeLike(prefix) + '%'} ESCAPE '\\'`))
50
+ .all();
51
+ }
43
52
  }
@@ -1,21 +1,20 @@
1
1
  import * as schema from '../schema';
2
+ import type { DatabaseConnection } from '../sqlite';
2
3
  import type { DiscussionStatus } from '../db';
3
4
  export type DiscussionRow = typeof schema.discussions.$inferSelect;
4
5
  type DiscussionInsert = typeof schema.discussions.$inferInsert;
5
- export declare function getDiscussionByOwnerAndContact(ownerUserId: string, contactUserId: string): Promise<DiscussionRow | undefined>;
6
- export declare function getDiscussionsByOwner(ownerUserId: string): Promise<DiscussionRow[]>;
7
- export declare function getDiscussionById(id: number): Promise<DiscussionRow | undefined>;
8
- export declare function insertDiscussion(values: DiscussionInsert): Promise<number>;
9
- export declare function updateDiscussionById(id: number, data: Partial<DiscussionInsert>): Promise<void>;
10
- export declare function deleteDiscussionById(id: number): Promise<void>;
11
- export declare function deleteDiscussionsByOwnerAndContact(ownerUserId: string, contactUserId: string): Promise<void>;
12
- /**
13
- * Atomically increment unreadCount by 1.
14
- */
15
- export declare function incrementUnreadCount(discussionId: number): Promise<void>;
16
- /**
17
- * Atomically decrement unreadCount by 1, ensuring it never goes below 0.
18
- */
19
- export declare function decrementUnreadCount(discussionId: number): Promise<void>;
20
- export declare function getDiscussionsByOwnerAndStatus(ownerUserId: string, status: DiscussionStatus): Promise<DiscussionRow[]>;
6
+ export declare class DiscussionQueries {
7
+ private conn;
8
+ constructor(conn: DatabaseConnection);
9
+ getByOwnerAndContact(ownerUserId: string, contactUserId: string): Promise<DiscussionRow | undefined>;
10
+ getByOwner(ownerUserId: string): Promise<DiscussionRow[]>;
11
+ getById(id: number): Promise<DiscussionRow | undefined>;
12
+ insert(values: DiscussionInsert): Promise<number>;
13
+ updateById(id: number, data: Partial<DiscussionInsert>): Promise<void>;
14
+ deleteById(id: number): Promise<void>;
15
+ deleteByOwnerAndContact(ownerUserId: string, contactUserId: string): Promise<void>;
16
+ incrementUnreadCount(discussionId: number): Promise<void>;
17
+ decrementUnreadCount(discussionId: number): Promise<void>;
18
+ getByOwnerAndStatus(ownerUserId: string, status: DiscussionStatus): Promise<DiscussionRow[]>;
19
+ }
21
20
  export {};