@majikah/majik-message 0.3.6 → 0.3.8

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 (67) hide show
  1. package/README.md +3 -3
  2. package/dist/core/client-state-manager.d.ts +105 -0
  3. package/dist/core/client-state-manager.js +250 -0
  4. package/dist/core/contacts/majik-contact-directory.d.ts +0 -5
  5. package/dist/core/contacts/majik-contact-directory.js +0 -12
  6. package/dist/core/contacts/majik-contact-groups.d.ts +1 -0
  7. package/dist/core/contacts/majik-contact-groups.js +5 -0
  8. package/dist/core/contacts/majik-contact-manager.d.ts +99 -185
  9. package/dist/core/contacts/majik-contact-manager.js +469 -289
  10. package/dist/core/contacts/types.d.ts +1 -0
  11. package/dist/core/crypto/keystore-manager.d.ts +166 -0
  12. package/dist/core/crypto/keystore-manager.js +371 -0
  13. package/dist/core/storage/chats/_types.d.ts +8 -0
  14. package/dist/core/storage/chats/_types.js +1 -0
  15. package/dist/core/storage/chats/adapter-idb.d.ts +3 -0
  16. package/dist/core/storage/chats/adapter-idb.js +5 -0
  17. package/dist/core/storage/chats/adapter-memory.d.ts +23 -0
  18. package/dist/core/storage/chats/adapter-memory.js +44 -0
  19. package/dist/core/storage/chats/adapter-sql.d.ts +17 -0
  20. package/dist/core/storage/chats/adapter-sql.js +85 -0
  21. package/dist/core/storage/client-state/_types.d.ts +37 -0
  22. package/dist/core/storage/client-state/_types.js +16 -0
  23. package/dist/core/storage/client-state/adapter-idb.d.ts +17 -0
  24. package/dist/core/storage/client-state/adapter-idb.js +19 -0
  25. package/dist/core/storage/client-state/adapter-memory.d.ts +20 -0
  26. package/dist/core/storage/client-state/adapter-memory.js +44 -0
  27. package/dist/core/storage/client-state/adapter-sql.d.ts +41 -0
  28. package/dist/core/storage/client-state/adapter-sql.js +104 -0
  29. package/dist/core/storage/contact-directory/contacts/_types.d.ts +3 -0
  30. package/dist/core/storage/contact-directory/contacts/_types.js +1 -0
  31. package/dist/core/storage/contact-directory/contacts/adapter-idb.d.ts +3 -0
  32. package/dist/core/storage/contact-directory/contacts/adapter-idb.js +5 -0
  33. package/dist/core/storage/contact-directory/contacts/adapter-memory.d.ts +14 -0
  34. package/dist/core/storage/contact-directory/contacts/adapter-memory.js +32 -0
  35. package/dist/core/storage/contact-directory/contacts/adapter-sql.d.ts +18 -0
  36. package/dist/core/storage/contact-directory/contacts/adapter-sql.js +97 -0
  37. package/dist/core/storage/contact-directory/groups/_types.d.ts +3 -0
  38. package/dist/core/storage/contact-directory/groups/_types.js +1 -0
  39. package/dist/core/storage/contact-directory/groups/adapter-idb.d.ts +3 -0
  40. package/dist/core/storage/contact-directory/groups/adapter-idb.js +5 -0
  41. package/dist/core/storage/contact-directory/groups/adapter-memory.d.ts +14 -0
  42. package/dist/core/storage/contact-directory/groups/adapter-memory.js +32 -0
  43. package/dist/core/storage/contact-directory/groups/adapter-sql.d.ts +16 -0
  44. package/dist/core/storage/contact-directory/groups/adapter-sql.js +72 -0
  45. package/dist/core/storage/idb-adapter.d.ts +21 -0
  46. package/dist/core/storage/idb-adapter.js +107 -0
  47. package/dist/core/storage/index.d.ts +24 -0
  48. package/dist/core/storage/index.js +19 -0
  49. package/dist/core/storage/keystore/_types.d.ts +3 -0
  50. package/dist/core/storage/keystore/_types.js +1 -0
  51. package/dist/core/storage/keystore/adapter-idb.d.ts +3 -0
  52. package/dist/core/storage/keystore/adapter-idb.js +5 -0
  53. package/dist/core/storage/keystore/adapter-memory.d.ts +14 -0
  54. package/dist/core/storage/keystore/adapter-memory.js +32 -0
  55. package/dist/core/storage/keystore/adapter-sql.d.ts +18 -0
  56. package/dist/core/storage/keystore/adapter-sql.js +93 -0
  57. package/dist/core/storage/sql-db-manager.d.ts +13 -0
  58. package/dist/core/storage/sql-db-manager.js +59 -0
  59. package/dist/core/storage/sql-schema.d.ts +25 -0
  60. package/dist/core/storage/sql-schema.js +122 -0
  61. package/dist/core/storage/storage-adapter.d.ts +22 -0
  62. package/dist/core/storage/storage-adapter.js +1 -0
  63. package/dist/index.d.ts +2 -4
  64. package/dist/index.js +2 -4
  65. package/dist/majik-message.d.ts +114 -174
  66. package/dist/majik-message.js +449 -675
  67. package/package.json +5 -6
@@ -0,0 +1,72 @@
1
+ import { MAJIKAH_SQL_TABLES } from "../../sql-schema";
2
+ export class SQLiteContactGroupAdapter {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ async save(group) {
8
+ await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}
9
+ (id, json, name, created_at, updated_at, is_system)
10
+ VALUES (?, ?, ?, ?, ?, ?)`, [
11
+ group.id,
12
+ JSON.stringify(group),
13
+ group.meta?.name ?? null,
14
+ group.meta?.createdAt ?? null,
15
+ group.meta?.updatedAt ?? null,
16
+ group.isSystem ? 1 : 0,
17
+ ]);
18
+ }
19
+ async getById(id) {
20
+ const row = await this.db.get(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS} WHERE id = ?`, [id]);
21
+ return row ? JSON.parse(row.json) : null;
22
+ }
23
+ async list() {
24
+ const rows = await this.db.all(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}`);
25
+ return rows.map((r) => JSON.parse(r.json));
26
+ }
27
+ async remove(id) {
28
+ const exists = await this.exists(id);
29
+ if (!exists)
30
+ return false;
31
+ await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS} WHERE id = ?`, [id]);
32
+ return true;
33
+ }
34
+ async clear() {
35
+ await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}`);
36
+ }
37
+ async count() {
38
+ const row = await this.db.get(`SELECT COUNT(*) as n FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}`);
39
+ return row?.n ?? 0;
40
+ }
41
+ async exists(id) {
42
+ const row = await this.db.get(`SELECT 1 FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS} WHERE id = ?`, [id]);
43
+ return !!row;
44
+ }
45
+ async bulkSave(groups) {
46
+ if (groups.length === 0)
47
+ return;
48
+ await this.db.transaction(async (tx) => {
49
+ for (const g of groups) {
50
+ await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}
51
+ (id, json, name, created_at, updated_at, is_system)
52
+ VALUES (?, ?, ?, ?, ?, ?)`, [
53
+ g.id,
54
+ JSON.stringify(g),
55
+ g.meta?.name ?? null,
56
+ g.meta?.createdAt ?? null,
57
+ g.meta?.updatedAt ?? null,
58
+ g.isSystem ? 1 : 0,
59
+ ]);
60
+ }
61
+ });
62
+ }
63
+ async bulkRemove(ids) {
64
+ if (ids.length === 0)
65
+ return;
66
+ await this.db.transaction(async (tx) => {
67
+ for (const id of ids) {
68
+ await tx.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS} WHERE id = ?`, [id]);
69
+ }
70
+ });
71
+ }
72
+ }
@@ -0,0 +1,21 @@
1
+ import { MajikStorageAdapter } from "./storage-adapter";
2
+ export declare class IDBGenericAdapter<T extends {
3
+ id: string;
4
+ }> implements MajikStorageAdapter<T> {
5
+ private dbName;
6
+ private storeName;
7
+ private version;
8
+ constructor(dbName: string, storeName: string, version?: number);
9
+ private db;
10
+ private open;
11
+ private tx;
12
+ save(item: T): Promise<void>;
13
+ getById(id: string): Promise<T | null>;
14
+ list(): Promise<T[]>;
15
+ remove(id: string): Promise<boolean>;
16
+ clear(): Promise<void>;
17
+ count(): Promise<number>;
18
+ exists(id: string): Promise<boolean>;
19
+ bulkSave(items: T[]): Promise<void>;
20
+ bulkRemove(ids: string[]): Promise<void>;
21
+ }
@@ -0,0 +1,107 @@
1
+ export class IDBGenericAdapter {
2
+ dbName;
3
+ storeName;
4
+ version;
5
+ constructor(dbName, storeName, version = 1) {
6
+ this.dbName = dbName;
7
+ this.storeName = storeName;
8
+ this.version = version;
9
+ }
10
+ db = null;
11
+ async open() {
12
+ if (this.db)
13
+ return this.db;
14
+ return new Promise((resolve, reject) => {
15
+ const req = indexedDB.open(this.dbName, this.version);
16
+ req.onupgradeneeded = () => {
17
+ const db = req.result;
18
+ const stores = [this.storeName]; // or pass array if you want
19
+ for (const name of stores) {
20
+ if (!db.objectStoreNames.contains(name)) {
21
+ db.createObjectStore(name, { keyPath: "id" });
22
+ }
23
+ }
24
+ };
25
+ req.onsuccess = () => {
26
+ this.db = req.result;
27
+ resolve(this.db);
28
+ };
29
+ req.onerror = () => reject(req.error);
30
+ });
31
+ }
32
+ async tx(mode, fn) {
33
+ const db = await this.open();
34
+ return new Promise((resolve, reject) => {
35
+ const tx = db.transaction(this.storeName, mode);
36
+ const store = tx.objectStore(this.storeName);
37
+ const req = fn(store);
38
+ if (req) {
39
+ req.onsuccess = () => resolve(req.result);
40
+ req.onerror = () => reject(req.error);
41
+ tx.onabort = () => reject(tx.error); // 👈 ADD THIS
42
+ }
43
+ else {
44
+ tx.oncomplete = () => resolve(undefined);
45
+ tx.onerror = () => reject(tx.error);
46
+ tx.onabort = () => reject(tx.error); // 👈 ADD THIS
47
+ }
48
+ });
49
+ }
50
+ async save(item) {
51
+ await this.tx("readwrite", (s) => s.put(item));
52
+ }
53
+ async getById(id) {
54
+ const result = await this.tx("readonly", (s) => s.get(id));
55
+ return result ?? null;
56
+ }
57
+ async list() {
58
+ return await this.tx("readonly", (s) => s.getAll());
59
+ }
60
+ async remove(id) {
61
+ const exists = await this.exists(id);
62
+ if (!exists)
63
+ return false;
64
+ await this.tx("readwrite", (s) => s.delete(id));
65
+ return true;
66
+ }
67
+ async clear() {
68
+ await this.tx("readwrite", (s) => s.clear());
69
+ }
70
+ async count() {
71
+ return await this.tx("readonly", (s) => s.count());
72
+ }
73
+ async exists(id) {
74
+ const key = await this.tx("readonly", (s) => s.getKey(id));
75
+ return key != null;
76
+ }
77
+ async bulkSave(items) {
78
+ if (items.length === 0)
79
+ return;
80
+ const db = await this.open();
81
+ return new Promise((resolve, reject) => {
82
+ const tx = db.transaction(this.storeName, "readwrite");
83
+ const store = tx.objectStore(this.storeName);
84
+ for (const item of items) {
85
+ store.put(item);
86
+ }
87
+ tx.oncomplete = () => resolve();
88
+ tx.onerror = () => reject(tx.error);
89
+ tx.onabort = () => reject(tx.error);
90
+ });
91
+ }
92
+ async bulkRemove(ids) {
93
+ if (ids.length === 0)
94
+ return;
95
+ const db = await this.open();
96
+ return new Promise((resolve, reject) => {
97
+ const tx = db.transaction(this.storeName, "readwrite");
98
+ const store = tx.objectStore(this.storeName);
99
+ for (const id of ids) {
100
+ store.delete(id);
101
+ }
102
+ tx.oncomplete = () => resolve();
103
+ tx.onerror = () => reject(tx.error);
104
+ tx.onabort = () => reject(tx.error);
105
+ });
106
+ }
107
+ }
@@ -0,0 +1,24 @@
1
+ export * from "./sql-schema";
2
+ export * from "./sql-db-manager";
3
+ export * from "./storage-adapter";
4
+ export * from "./idb-adapter";
5
+ export * from "./client-state/adapter-idb";
6
+ export * from "./client-state/adapter-sql";
7
+ export * from "./client-state/adapter-memory";
8
+ export type * from "./client-state/_types";
9
+ export * from "./contact-directory/contacts/adapter-idb";
10
+ export * from "./contact-directory/contacts/adapter-sql";
11
+ export * from "./contact-directory/contacts/adapter-memory";
12
+ export type * from "./contact-directory/contacts/_types";
13
+ export * from "./contact-directory/groups/adapter-idb";
14
+ export * from "./contact-directory/groups/adapter-sql";
15
+ export * from "./contact-directory/groups/adapter-memory";
16
+ export type * from "./contact-directory/groups/_types";
17
+ export * from "./chats/adapter-idb";
18
+ export * from "./chats/adapter-sql";
19
+ export * from "./chats/adapter-memory";
20
+ export type * from "./chats/_types";
21
+ export * from "./keystore/adapter-idb";
22
+ export * from "./keystore/adapter-sql";
23
+ export * from "./keystore/adapter-memory";
24
+ export type * from "./keystore/_types";
@@ -0,0 +1,19 @@
1
+ export * from "./sql-schema";
2
+ export * from "./sql-db-manager";
3
+ export * from "./storage-adapter";
4
+ export * from "./idb-adapter";
5
+ export * from "./client-state/adapter-idb";
6
+ export * from "./client-state/adapter-sql";
7
+ export * from "./client-state/adapter-memory";
8
+ export * from "./contact-directory/contacts/adapter-idb";
9
+ export * from "./contact-directory/contacts/adapter-sql";
10
+ export * from "./contact-directory/contacts/adapter-memory";
11
+ export * from "./contact-directory/groups/adapter-idb";
12
+ export * from "./contact-directory/groups/adapter-sql";
13
+ export * from "./contact-directory/groups/adapter-memory";
14
+ export * from "./chats/adapter-idb";
15
+ export * from "./chats/adapter-sql";
16
+ export * from "./chats/adapter-memory";
17
+ export * from "./keystore/adapter-idb";
18
+ export * from "./keystore/adapter-sql";
19
+ export * from "./keystore/adapter-memory";
@@ -0,0 +1,3 @@
1
+ import { MajikKeyJSON } from "@majikah/majik-key";
2
+ import { MajikStorageAdapter } from "../storage-adapter";
3
+ export type MajikKeyStorageAdapter = MajikStorageAdapter<MajikKeyJSON>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { MajikKeyJSON } from "@majikah/majik-key";
2
+ import { IDBGenericAdapter } from "../idb-adapter";
3
+ export declare const IDB_ADAPTER_KEYSTORE: IDBGenericAdapter<MajikKeyJSON>;
@@ -0,0 +1,5 @@
1
+ import { IDBGenericAdapter } from "../idb-adapter";
2
+ const IDB_DB_NAME = "majik-keys";
3
+ const IDB_STORE_NAME = "identities";
4
+ const IDB_VERSION = 1;
5
+ export const IDB_ADAPTER_KEYSTORE = new IDBGenericAdapter(IDB_DB_NAME, IDB_STORE_NAME, IDB_VERSION);
@@ -0,0 +1,14 @@
1
+ import { MajikKeyJSON } from "@majikah/majik-key";
2
+ import { MajikKeyStorageAdapter } from "./_types";
3
+ export declare class InMemoryKeystoreAdapter implements MajikKeyStorageAdapter {
4
+ private _store;
5
+ save(invoice: MajikKeyJSON): Promise<void>;
6
+ getById(id: string): Promise<MajikKeyJSON | null>;
7
+ list(): Promise<MajikKeyJSON[]>;
8
+ remove(id: string): Promise<boolean>;
9
+ clear(): Promise<void>;
10
+ count(): Promise<number>;
11
+ exists(id: string): Promise<boolean>;
12
+ bulkSave(invoices: MajikKeyJSON[]): Promise<void>;
13
+ bulkRemove(ids: string[]): Promise<void>;
14
+ }
@@ -0,0 +1,32 @@
1
+ export class InMemoryKeystoreAdapter {
2
+ _store = new Map();
3
+ async save(invoice) {
4
+ this._store.set(invoice.id, invoice);
5
+ }
6
+ async getById(id) {
7
+ return this._store.get(id) ?? null;
8
+ }
9
+ async list() {
10
+ return Array.from(this._store.values());
11
+ }
12
+ async remove(id) {
13
+ return this._store.delete(id);
14
+ }
15
+ async clear() {
16
+ this._store.clear();
17
+ }
18
+ async count() {
19
+ return this._store.size;
20
+ }
21
+ async exists(id) {
22
+ return this._store.has(id);
23
+ }
24
+ async bulkSave(invoices) {
25
+ for (const inv of invoices)
26
+ this._store.set(inv.id, inv);
27
+ }
28
+ async bulkRemove(ids) {
29
+ for (const id of ids)
30
+ this._store.delete(id);
31
+ }
32
+ }
@@ -0,0 +1,18 @@
1
+ import { MajikKeyJSON } from "@majikah/majik-key";
2
+ import { MajikKeyStorageAdapter } from "./_types";
3
+ import { SQLiteDatabase } from "../sql-db-manager";
4
+ import { StorageQuery } from "../storage-adapter";
5
+ export declare class SQLiteKeystoreAdapter implements MajikKeyStorageAdapter {
6
+ private db;
7
+ constructor(db: SQLiteDatabase);
8
+ save(key: MajikKeyJSON): Promise<void>;
9
+ getById(id: string): Promise<MajikKeyJSON | null>;
10
+ list(): Promise<MajikKeyJSON[]>;
11
+ remove(id: string): Promise<boolean>;
12
+ clear(): Promise<void>;
13
+ count(): Promise<number>;
14
+ exists(id: string): Promise<boolean>;
15
+ bulkSave(keys: MajikKeyJSON[]): Promise<void>;
16
+ bulkRemove(ids: string[]): Promise<void>;
17
+ query(query: StorageQuery<MajikKeyJSON>): Promise<MajikKeyJSON[]>;
18
+ }
@@ -0,0 +1,93 @@
1
+ import { MAJIKAH_SQL_TABLES } from "../sql-schema";
2
+ export class SQLiteKeystoreAdapter {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ async save(key) {
8
+ await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}
9
+ (id, json, timestamp, public_key)
10
+ VALUES (?, ?, ?, ?)`, [
11
+ key.id,
12
+ JSON.stringify(key),
13
+ key.timestamp ?? new Date().toISOString(),
14
+ key.publicKey,
15
+ ]);
16
+ }
17
+ async getById(id) {
18
+ const row = await this.db.get("SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_KEYS} WHERE id = ?", [id]);
19
+ return row ? JSON.parse(row.json) : null;
20
+ }
21
+ async list() {
22
+ const rows = await this.db.all(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}`);
23
+ return rows.map((r) => JSON.parse(r.json));
24
+ }
25
+ async remove(id) {
26
+ const exists = await this.exists(id);
27
+ if (!exists)
28
+ return false;
29
+ await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_KEYS} WHERE id = ?`, [id]);
30
+ return true;
31
+ }
32
+ async clear() {
33
+ await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}`);
34
+ }
35
+ async count() {
36
+ const row = await this.db.get(`SELECT COUNT(*) as n FROM ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}`);
37
+ return row?.n ?? 0;
38
+ }
39
+ async exists(id) {
40
+ const row = await this.db.get(`SELECT 1 FROM ${MAJIKAH_SQL_TABLES.MAJIK_KEYS} WHERE id = ?`, [id]);
41
+ return !!row;
42
+ }
43
+ async bulkSave(keys) {
44
+ if (keys.length === 0)
45
+ return;
46
+ await this.db.transaction(async (tx) => {
47
+ for (const g of keys) {
48
+ await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}
49
+ (id, json, timestamp, public_key)
50
+ VALUES (?, ?, ?, ?)`, [
51
+ g.id,
52
+ JSON.stringify(g),
53
+ g.timestamp ?? new Date().toISOString(),
54
+ g.publicKey,
55
+ ]);
56
+ }
57
+ });
58
+ }
59
+ async bulkRemove(ids) {
60
+ if (ids.length === 0)
61
+ return;
62
+ await this.db.transaction(async (tx) => {
63
+ for (const id of ids) {
64
+ await tx.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_KEYS} WHERE id = ?`, [id]);
65
+ }
66
+ });
67
+ }
68
+ async query(query) {
69
+ const clauses = [];
70
+ const values = [];
71
+ if (query.where) {
72
+ for (const [key, value] of Object.entries(query.where)) {
73
+ clauses.push(`${key} = ?`);
74
+ values.push(value);
75
+ }
76
+ }
77
+ let sql = `SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}`;
78
+ if (clauses.length > 0) {
79
+ sql += ` WHERE ${clauses.join(" AND ")}`;
80
+ }
81
+ if (query.orderBy) {
82
+ sql += ` ORDER BY ${String(query.orderBy)} ${query.orderDirection ?? "asc"}`;
83
+ }
84
+ if (query.limit) {
85
+ sql += ` LIMIT ${query.limit}`;
86
+ }
87
+ if (query.offset) {
88
+ sql += ` OFFSET ${query.offset}`;
89
+ }
90
+ const rows = await this.db.all(sql, values);
91
+ return rows.map((r) => JSON.parse(r.json));
92
+ }
93
+ }
@@ -0,0 +1,13 @@
1
+ export declare class SQLiteDatabase {
2
+ private worker;
3
+ constructor(worker: Worker);
4
+ private call;
5
+ run(sql: string, params?: any[]): Promise<void>;
6
+ get<T = any>(sql: string, params?: any[]): Promise<T | null>;
7
+ all<T = any>(sql: string, params?: any[]): Promise<T[]>;
8
+ exec(sql: string): Promise<void>;
9
+ optimize(): Promise<void>;
10
+ vacuum(): Promise<void>;
11
+ checkpoint(mode?: "PASSIVE" | "FULL"): Promise<void>;
12
+ transaction(fn: (tx: SQLiteDatabase) => Promise<void>): Promise<void>;
13
+ }
@@ -0,0 +1,59 @@
1
+ export class SQLiteDatabase {
2
+ worker;
3
+ constructor(worker) {
4
+ this.worker = worker;
5
+ }
6
+ call(message) {
7
+ const id = crypto.randomUUID();
8
+ return new Promise((resolve, reject) => {
9
+ const handler = (e) => {
10
+ if (e.data.id !== id)
11
+ return;
12
+ this.worker.removeEventListener("message", handler);
13
+ if (e.data.ok)
14
+ resolve(e.data.result);
15
+ else
16
+ reject(new Error(e.data.error));
17
+ };
18
+ this.worker.addEventListener("message", handler);
19
+ this.worker.postMessage({
20
+ ...message,
21
+ id,
22
+ });
23
+ });
24
+ }
25
+ async run(sql, params = []) {
26
+ await this.call({ type: "run", sql, params });
27
+ }
28
+ async get(sql, params = []) {
29
+ return this.call({ type: "get", sql, params });
30
+ }
31
+ async all(sql, params = []) {
32
+ return this.call({ type: "all", sql, params });
33
+ }
34
+ async exec(sql) {
35
+ await this.call({ type: "exec", sql });
36
+ }
37
+ async optimize() {
38
+ await this.run("PRAGMA optimize;");
39
+ await this.run("ANALYZE;");
40
+ await this.run("PRAGMA wal_checkpoint(FULL);");
41
+ }
42
+ async vacuum() {
43
+ await this.exec("VACUUM;");
44
+ }
45
+ async checkpoint(mode = "PASSIVE") {
46
+ await this.run(`PRAGMA wal_checkpoint(${mode});`);
47
+ }
48
+ async transaction(fn) {
49
+ await this.run("BEGIN");
50
+ try {
51
+ await fn(this);
52
+ await this.run("COMMIT");
53
+ }
54
+ catch (err) {
55
+ await this.run("ROLLBACK");
56
+ throw err;
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,25 @@
1
+ type MajikahSQLSchema = string;
2
+ /**
3
+ * Centralized SQLite table registry.
4
+ * - `as const` keeps literal types
5
+ * - `MajikahSQLTable` becomes a strict union type
6
+ */
7
+ export declare const MAJIKAH_SQL_TABLES: {
8
+ readonly MAJIK_CLIENT_STATE: "majik_client_state";
9
+ readonly MAJIK_KEYS: "majik_keys";
10
+ readonly MAJIK_MESSAGE_CHATS: "majik_message_chats";
11
+ readonly MAJIK_MESSAGE_FILES: "majik_message_files";
12
+ readonly MAJIK_MESSAGE_THREAD_MAILS: "majik_message_thread_mails";
13
+ readonly MAJIK_CONTACTS: "majik_contacts";
14
+ readonly MAJIK_CONTACT_GROUPS: "majik_contact_groups";
15
+ };
16
+ export type MajikahSQLTable = (typeof MAJIKAH_SQL_TABLES)[keyof typeof MAJIKAH_SQL_TABLES];
17
+ export declare function buildSchemaSQL(schemas: MajikahSQLSchema[]): MajikahSQLSchema;
18
+ export declare const MAJIKAH_SQL_SCHEMA_MAJIK_CLIENT_STATE: MajikahSQLSchema;
19
+ export declare const MAJIKAH_SQL_SCHEMA_MAJIK_KEYS: MajikahSQLSchema;
20
+ export declare const MAJIKAH_SQL_SCHEMA_MAJIK_MESSAGE_CHATS: MajikahSQLSchema;
21
+ export declare const MAJIKAH_SQL_SCHEMA_MAJIK_MESSAGE_FILES: MajikahSQLSchema;
22
+ export declare const MAJIKAH_SQL_SCHEMA_MAJIK_CONTACTS: MajikahSQLSchema;
23
+ export declare const MAJIKAH_SQL_SCHEMA_MAJIK_CONTACT_GROUPS: MajikahSQLSchema;
24
+ export declare const MAJIKAH_SQL_SCHEMA_FULL: MajikahSQLSchema;
25
+ export {};
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Centralized SQLite table registry.
3
+ * - `as const` keeps literal types
4
+ * - `MajikahSQLTable` becomes a strict union type
5
+ */
6
+ export const MAJIKAH_SQL_TABLES = {
7
+ MAJIK_CLIENT_STATE: "majik_client_state",
8
+ MAJIK_KEYS: "majik_keys",
9
+ MAJIK_MESSAGE_CHATS: "majik_message_chats",
10
+ MAJIK_MESSAGE_FILES: "majik_message_files",
11
+ MAJIK_MESSAGE_THREAD_MAILS: "majik_message_thread_mails",
12
+ MAJIK_CONTACTS: "majik_contacts",
13
+ MAJIK_CONTACT_GROUPS: "majik_contact_groups",
14
+ };
15
+ function normalizeSQL(sql) {
16
+ return sql
17
+ .trim()
18
+ .replace(/\s+/g, " ") // collapse all whitespace
19
+ .toLowerCase();
20
+ }
21
+ export function buildSchemaSQL(schemas) {
22
+ const seen = new Set();
23
+ return schemas
24
+ .map((schema) => schema.trim())
25
+ .filter(Boolean)
26
+ .filter((schema) => {
27
+ const normalized = normalizeSQL(schema);
28
+ if (seen.has(normalized))
29
+ return false; // silently skip
30
+ seen.add(normalized);
31
+ return true;
32
+ })
33
+ .join("\n\n");
34
+ }
35
+ export const MAJIKAH_SQL_SCHEMA_MAJIK_CLIENT_STATE = `
36
+ CREATE TABLE IF NOT EXISTS ${MAJIKAH_SQL_TABLES.MAJIK_CLIENT_STATE} (
37
+ key TEXT PRIMARY KEY,
38
+ value TEXT NOT NULL,
39
+ updated_at TEXT DEFAULT (datetime('now'))
40
+ );
41
+ `;
42
+ export const MAJIKAH_SQL_SCHEMA_MAJIK_KEYS = `
43
+ CREATE TABLE IF NOT EXISTS ${MAJIKAH_SQL_TABLES.MAJIK_KEYS} (
44
+ id TEXT PRIMARY KEY,
45
+ json TEXT NOT NULL,
46
+ timestamp TEXT NOT NULL,
47
+ public_key TEXT NOT NULL
48
+ );
49
+
50
+ CREATE INDEX IF NOT EXISTS idx_majik_keys_timestamp
51
+ ON ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}(timestamp);
52
+
53
+ CREATE INDEX IF NOT EXISTS idx_majik_keys_public_key
54
+ ON ${MAJIKAH_SQL_TABLES.MAJIK_KEYS}(public_key);
55
+ `;
56
+ export const MAJIKAH_SQL_SCHEMA_MAJIK_MESSAGE_CHATS = `
57
+ CREATE TABLE IF NOT EXISTS ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} (
58
+ id TEXT PRIMARY KEY,
59
+ json TEXT NOT NULL,
60
+ created_at TEXT NOT NULL,
61
+ source TEXT NOT NULL DEFAULT 'local'
62
+ CHECK(source IN ('local', 'cloud'))
63
+ );
64
+
65
+ CREATE INDEX IF NOT EXISTS idx_majik_message_chats_created_at
66
+ ON ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS}(created_at);
67
+
68
+
69
+ CREATE INDEX IF NOT EXISTS idx_majik_message_chats_source
70
+ ON ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS}(source);
71
+ `;
72
+ export const MAJIKAH_SQL_SCHEMA_MAJIK_MESSAGE_FILES = `
73
+ CREATE TABLE IF NOT EXISTS ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_FILES} (
74
+ id TEXT PRIMARY KEY,
75
+ json TEXT NOT NULL,
76
+ created_at TEXT NOT NULL,
77
+ binary BLOB,
78
+ source TEXT NOT NULL DEFAULT 'local'
79
+ CHECK(source IN ('local', 'cloud'))
80
+ );
81
+
82
+ CREATE INDEX IF NOT EXISTS idx_majik_message_files_created_at
83
+ ON ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_FILES}(created_at);
84
+
85
+
86
+ CREATE INDEX IF NOT EXISTS idx_majik_message_files_source
87
+ ON ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_FILES}(source);
88
+ `;
89
+ export const MAJIKAH_SQL_SCHEMA_MAJIK_CONTACTS = `
90
+ CREATE TABLE IF NOT EXISTS ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS} (
91
+ id TEXT PRIMARY KEY,
92
+ json TEXT NOT NULL,
93
+ fingerprint TEXT,
94
+ label TEXT,
95
+ created_at TEXT,
96
+ updated_at TEXT
97
+ );
98
+
99
+ CREATE INDEX IF NOT EXISTS idx_majik_contacts_created_at
100
+ ON ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}(created_at);
101
+ `;
102
+ export const MAJIKAH_SQL_SCHEMA_MAJIK_CONTACT_GROUPS = `
103
+ CREATE TABLE IF NOT EXISTS ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS} (
104
+ id TEXT PRIMARY KEY,
105
+ json TEXT NOT NULL,
106
+ name TEXT,
107
+ created_at TEXT,
108
+ updated_at TEXT,
109
+ is_system INTEGER DEFAULT 0 CHECK(is_system IN (0,1))
110
+ );
111
+
112
+ CREATE INDEX IF NOT EXISTS idx_majik_contact_groups_created_at
113
+ ON ${MAJIKAH_SQL_TABLES.MAJIK_CONTACT_GROUPS}(created_at);
114
+ `;
115
+ export const MAJIKAH_SQL_SCHEMA_FULL = buildSchemaSQL([
116
+ MAJIKAH_SQL_SCHEMA_MAJIK_CLIENT_STATE,
117
+ MAJIKAH_SQL_SCHEMA_MAJIK_KEYS,
118
+ MAJIKAH_SQL_SCHEMA_MAJIK_MESSAGE_CHATS,
119
+ MAJIKAH_SQL_SCHEMA_MAJIK_CONTACTS,
120
+ MAJIKAH_SQL_SCHEMA_MAJIK_CONTACT_GROUPS,
121
+ MAJIKAH_SQL_SCHEMA_MAJIK_MESSAGE_FILES,
122
+ ]);
@@ -0,0 +1,22 @@
1
+ export type StorageSource = "local" | "cloud";
2
+ export interface StorageQuery<T> {
3
+ where?: Partial<T>;
4
+ limit?: number;
5
+ offset?: number;
6
+ orderBy?: keyof T;
7
+ orderDirection?: "asc" | "desc";
8
+ }
9
+ export interface MajikStorageAdapter<T extends {
10
+ id: string;
11
+ }> {
12
+ save(item: T, source?: StorageSource): Promise<void>;
13
+ getById(id: string, source?: StorageSource): Promise<T | null>;
14
+ list(source?: StorageSource): Promise<T[]>;
15
+ remove(id: string, source?: StorageSource): Promise<boolean>;
16
+ clear(source?: StorageSource): Promise<void>;
17
+ count(source?: StorageSource): Promise<number>;
18
+ exists(id: string, source?: StorageSource): Promise<boolean>;
19
+ bulkSave(items: T[], source?: StorageSource): Promise<void>;
20
+ bulkRemove(ids: string[], source?: StorageSource): Promise<void>;
21
+ query?(query: StorageQuery<T>, source?: StorageSource): Promise<T[]>;
22
+ }
@@ -0,0 +1 @@
1
+ export {};