@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.
- package/README.md +3 -3
- package/dist/core/client-state-manager.d.ts +105 -0
- package/dist/core/client-state-manager.js +250 -0
- package/dist/core/contacts/majik-contact-directory.d.ts +0 -5
- package/dist/core/contacts/majik-contact-directory.js +0 -12
- package/dist/core/contacts/majik-contact-groups.d.ts +1 -0
- package/dist/core/contacts/majik-contact-groups.js +5 -0
- package/dist/core/contacts/majik-contact-manager.d.ts +99 -185
- package/dist/core/contacts/majik-contact-manager.js +469 -289
- package/dist/core/contacts/types.d.ts +1 -0
- package/dist/core/crypto/keystore-manager.d.ts +166 -0
- package/dist/core/crypto/keystore-manager.js +371 -0
- package/dist/core/storage/chats/_types.d.ts +8 -0
- package/dist/core/storage/chats/_types.js +1 -0
- package/dist/core/storage/chats/adapter-idb.d.ts +3 -0
- package/dist/core/storage/chats/adapter-idb.js +5 -0
- package/dist/core/storage/chats/adapter-memory.d.ts +23 -0
- package/dist/core/storage/chats/adapter-memory.js +44 -0
- package/dist/core/storage/chats/adapter-sql.d.ts +17 -0
- package/dist/core/storage/chats/adapter-sql.js +85 -0
- package/dist/core/storage/client-state/_types.d.ts +37 -0
- package/dist/core/storage/client-state/_types.js +16 -0
- package/dist/core/storage/client-state/adapter-idb.d.ts +17 -0
- package/dist/core/storage/client-state/adapter-idb.js +19 -0
- package/dist/core/storage/client-state/adapter-memory.d.ts +20 -0
- package/dist/core/storage/client-state/adapter-memory.js +44 -0
- package/dist/core/storage/client-state/adapter-sql.d.ts +41 -0
- package/dist/core/storage/client-state/adapter-sql.js +104 -0
- package/dist/core/storage/contact-directory/contacts/_types.d.ts +3 -0
- package/dist/core/storage/contact-directory/contacts/_types.js +1 -0
- package/dist/core/storage/contact-directory/contacts/adapter-idb.d.ts +3 -0
- package/dist/core/storage/contact-directory/contacts/adapter-idb.js +5 -0
- package/dist/core/storage/contact-directory/contacts/adapter-memory.d.ts +14 -0
- package/dist/core/storage/contact-directory/contacts/adapter-memory.js +32 -0
- package/dist/core/storage/contact-directory/contacts/adapter-sql.d.ts +18 -0
- package/dist/core/storage/contact-directory/contacts/adapter-sql.js +97 -0
- package/dist/core/storage/contact-directory/groups/_types.d.ts +3 -0
- package/dist/core/storage/contact-directory/groups/_types.js +1 -0
- package/dist/core/storage/contact-directory/groups/adapter-idb.d.ts +3 -0
- package/dist/core/storage/contact-directory/groups/adapter-idb.js +5 -0
- package/dist/core/storage/contact-directory/groups/adapter-memory.d.ts +14 -0
- package/dist/core/storage/contact-directory/groups/adapter-memory.js +32 -0
- package/dist/core/storage/contact-directory/groups/adapter-sql.d.ts +16 -0
- package/dist/core/storage/contact-directory/groups/adapter-sql.js +72 -0
- package/dist/core/storage/idb-adapter.d.ts +21 -0
- package/dist/core/storage/idb-adapter.js +107 -0
- package/dist/core/storage/index.d.ts +24 -0
- package/dist/core/storage/index.js +19 -0
- package/dist/core/storage/keystore/_types.d.ts +3 -0
- package/dist/core/storage/keystore/_types.js +1 -0
- package/dist/core/storage/keystore/adapter-idb.d.ts +3 -0
- package/dist/core/storage/keystore/adapter-idb.js +5 -0
- package/dist/core/storage/keystore/adapter-memory.d.ts +14 -0
- package/dist/core/storage/keystore/adapter-memory.js +32 -0
- package/dist/core/storage/keystore/adapter-sql.d.ts +18 -0
- package/dist/core/storage/keystore/adapter-sql.js +93 -0
- package/dist/core/storage/sql-db-manager.d.ts +13 -0
- package/dist/core/storage/sql-db-manager.js +59 -0
- package/dist/core/storage/sql-schema.d.ts +25 -0
- package/dist/core/storage/sql-schema.js +122 -0
- package/dist/core/storage/storage-adapter.d.ts +22 -0
- package/dist/core/storage/storage-adapter.js +1 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +2 -4
- package/dist/majik-message.d.ts +114 -174
- package/dist/majik-message.js +449 -675
- 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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 {};
|