@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,85 @@
|
|
|
1
|
+
import { MAJIKAH_SQL_TABLES } from "../sql-schema";
|
|
2
|
+
export class SQLiteInvoiceAdapter {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
async save(message, source = `local`) {
|
|
8
|
+
const resolvedSource = source ?? `local`;
|
|
9
|
+
await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS}
|
|
10
|
+
(id, json, created_at, source)
|
|
11
|
+
VALUES (?, ?, ?, ?)`, [message.id, JSON.stringify(message), message.timestamp, resolvedSource]);
|
|
12
|
+
}
|
|
13
|
+
async getById(id, source) {
|
|
14
|
+
const row = source
|
|
15
|
+
? await this.db.get(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE id = ? AND source = ?`, [id, source])
|
|
16
|
+
: await this.db.get(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE id = ?`, [id]);
|
|
17
|
+
return row ? JSON.parse(row.json) : null;
|
|
18
|
+
}
|
|
19
|
+
async list(source) {
|
|
20
|
+
const rows = source
|
|
21
|
+
? await this.db.all(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE source = ?`, [source])
|
|
22
|
+
: await this.db.all(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS}`);
|
|
23
|
+
return rows.map((r) => JSON.parse(r.json));
|
|
24
|
+
}
|
|
25
|
+
async remove(id, source) {
|
|
26
|
+
const exists = await this.exists(id, source);
|
|
27
|
+
if (!exists)
|
|
28
|
+
return false;
|
|
29
|
+
if (source) {
|
|
30
|
+
await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE id = ? AND source = ?`, [id, source]);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE id = ?`, [id]);
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
async clear(source) {
|
|
38
|
+
if (source) {
|
|
39
|
+
await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE source = ?`, [
|
|
40
|
+
source,
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async count(source) {
|
|
48
|
+
const row = source
|
|
49
|
+
? await this.db.get(`SELECT COUNT(*) as n FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE source = ?`, [source])
|
|
50
|
+
: await this.db.get(`SELECT COUNT(*) as n FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS}`);
|
|
51
|
+
return row?.n ?? 0;
|
|
52
|
+
}
|
|
53
|
+
async exists(id, source) {
|
|
54
|
+
const row = source
|
|
55
|
+
? await this.db.get(`SELECT 1 FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE id = ? AND source = ?`, [id, source])
|
|
56
|
+
: await this.db.get(`SELECT 1 FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE id = ?`, [id]);
|
|
57
|
+
return !!row;
|
|
58
|
+
}
|
|
59
|
+
async bulkSave(messages, source = `local`) {
|
|
60
|
+
if (messages.length === 0)
|
|
61
|
+
return;
|
|
62
|
+
const resolvedSource = source ?? `local`;
|
|
63
|
+
await this.db.transaction(async (tx) => {
|
|
64
|
+
for (const msg of messages) {
|
|
65
|
+
await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS}
|
|
66
|
+
(id, json, created_at, source)
|
|
67
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [msg.id, JSON.stringify(msg), msg.timestamp, resolvedSource]);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async bulkRemove(ids, source) {
|
|
72
|
+
if (ids.length === 0)
|
|
73
|
+
return;
|
|
74
|
+
await this.db.transaction(async (tx) => {
|
|
75
|
+
for (const id of ids) {
|
|
76
|
+
if (source) {
|
|
77
|
+
await tx.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE id = ? AND source = ?`, [id, source]);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
await tx.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_MESSAGE_CHATS} WHERE id = ?`, [id]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file _types.ts
|
|
3
|
+
* @description Shared types for the ClientState storage layer.
|
|
4
|
+
*
|
|
5
|
+
* The adapter is intentionally minimal — it is a generic key/value store
|
|
6
|
+
* where each entry carries a plain JSON-serialisable `value`. The
|
|
7
|
+
* ClientStateManager owns all serialisation / deserialisation logic; the
|
|
8
|
+
* adapter only moves bytes.
|
|
9
|
+
*/
|
|
10
|
+
import { MajikStorageAdapter } from "../storage-adapter";
|
|
11
|
+
export interface ClientStateEntry {
|
|
12
|
+
/** Stable string key that identifies this piece of state. */
|
|
13
|
+
id: string;
|
|
14
|
+
/** JSON-serialised value. Always a string on the wire / in storage. */
|
|
15
|
+
value: string;
|
|
16
|
+
/** ISO 8601 datetime — set by the adapter on every write where supported. */
|
|
17
|
+
updatedAt?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const CLIENT_STATE_KEYS: {
|
|
20
|
+
readonly ACCOUNT_ORDER: "user_account_order";
|
|
21
|
+
};
|
|
22
|
+
export type ClientStateKey = (typeof CLIENT_STATE_KEYS)[keyof typeof CLIENT_STATE_KEYS];
|
|
23
|
+
/**
|
|
24
|
+
* Ordered list of own account IDs. The head of the array is the active
|
|
25
|
+
* account. Stored as a JSON array: `["id1", "id2", ...]`.
|
|
26
|
+
*/
|
|
27
|
+
export type AccountOrderValue = string[];
|
|
28
|
+
/**
|
|
29
|
+
* Pluggable persistence backend for client-level state.
|
|
30
|
+
*
|
|
31
|
+
* Implementations must provide IDB, SQLite, and in-memory variants.
|
|
32
|
+
* All methods are async for uniformity — in-memory may resolve immediately.
|
|
33
|
+
*
|
|
34
|
+
* The store is deliberately flat: every piece of state is a `ClientStateEntry`
|
|
35
|
+
* keyed by a stable string ID. There is no relational structure.
|
|
36
|
+
*/
|
|
37
|
+
export type ClientStateStorageAdapter = MajikStorageAdapter<ClientStateEntry>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file _types.ts
|
|
3
|
+
* @description Shared types for the ClientState storage layer.
|
|
4
|
+
*
|
|
5
|
+
* The adapter is intentionally minimal — it is a generic key/value store
|
|
6
|
+
* where each entry carries a plain JSON-serialisable `value`. The
|
|
7
|
+
* ClientStateManager owns all serialisation / deserialisation logic; the
|
|
8
|
+
* adapter only moves bytes.
|
|
9
|
+
*/
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Well-known state keys
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
export const CLIENT_STATE_KEYS = {
|
|
14
|
+
ACCOUNT_ORDER: "user_account_order",
|
|
15
|
+
// INVOICE_DEFAULTS: "invoice_defaults",
|
|
16
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file adapter-idb.ts
|
|
3
|
+
* @description IndexedDB-backed ClientStateStorageAdapter.
|
|
4
|
+
*
|
|
5
|
+
* Uses IDBGenericAdapter<ClientStateEntry> under the hood so the full
|
|
6
|
+
* IDB transaction / error-handling logic lives in one place.
|
|
7
|
+
*
|
|
8
|
+
* Database : "majik-client-state"
|
|
9
|
+
* Store : "client-state"
|
|
10
|
+
* Version : 1
|
|
11
|
+
*
|
|
12
|
+
* Each entry is stored with `id` as the keyPath — identical to the invoice
|
|
13
|
+
* and contact adapters.
|
|
14
|
+
*/
|
|
15
|
+
import type { ClientStateEntry } from "./_types";
|
|
16
|
+
import { IDBGenericAdapter } from "../idb-adapter";
|
|
17
|
+
export declare const IDB_ADAPTER_CLIENT_STATE: IDBGenericAdapter<ClientStateEntry>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file adapter-idb.ts
|
|
3
|
+
* @description IndexedDB-backed ClientStateStorageAdapter.
|
|
4
|
+
*
|
|
5
|
+
* Uses IDBGenericAdapter<ClientStateEntry> under the hood so the full
|
|
6
|
+
* IDB transaction / error-handling logic lives in one place.
|
|
7
|
+
*
|
|
8
|
+
* Database : "majik-client-state"
|
|
9
|
+
* Store : "client-state"
|
|
10
|
+
* Version : 1
|
|
11
|
+
*
|
|
12
|
+
* Each entry is stored with `id` as the keyPath — identical to the invoice
|
|
13
|
+
* and contact adapters.
|
|
14
|
+
*/
|
|
15
|
+
import { IDBGenericAdapter } from "../idb-adapter";
|
|
16
|
+
const IDB_DB_NAME = "majik-client-state";
|
|
17
|
+
const IDB_STORE_NAME = "client-state";
|
|
18
|
+
const IDB_VERSION = 1;
|
|
19
|
+
export const IDB_ADAPTER_CLIENT_STATE = new IDBGenericAdapter(IDB_DB_NAME, IDB_STORE_NAME, IDB_VERSION);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file adapter-memory.ts
|
|
3
|
+
* @description In-memory ClientStateStorageAdapter.
|
|
4
|
+
*
|
|
5
|
+
* Default adapter — zero-config, non-persistent. State is lost on page reload
|
|
6
|
+
* or process restart. Useful for tests, SSR, and headless environments.
|
|
7
|
+
*/
|
|
8
|
+
import type { ClientStateEntry, ClientStateStorageAdapter } from "./_types";
|
|
9
|
+
export declare class InMemoryClientStateAdapter implements ClientStateStorageAdapter {
|
|
10
|
+
private _store;
|
|
11
|
+
save(entry: ClientStateEntry): Promise<void>;
|
|
12
|
+
getById(id: string): Promise<ClientStateEntry | null>;
|
|
13
|
+
list(): Promise<ClientStateEntry[]>;
|
|
14
|
+
remove(id: string): Promise<boolean>;
|
|
15
|
+
clear(): Promise<void>;
|
|
16
|
+
count(): Promise<number>;
|
|
17
|
+
exists(id: string): Promise<boolean>;
|
|
18
|
+
bulkSave(entries: ClientStateEntry[]): Promise<void>;
|
|
19
|
+
bulkRemove(ids: string[]): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file adapter-memory.ts
|
|
3
|
+
* @description In-memory ClientStateStorageAdapter.
|
|
4
|
+
*
|
|
5
|
+
* Default adapter — zero-config, non-persistent. State is lost on page reload
|
|
6
|
+
* or process restart. Useful for tests, SSR, and headless environments.
|
|
7
|
+
*/
|
|
8
|
+
export class InMemoryClientStateAdapter {
|
|
9
|
+
_store = new Map();
|
|
10
|
+
async save(entry) {
|
|
11
|
+
this._store.set(entry.id, {
|
|
12
|
+
...entry,
|
|
13
|
+
updatedAt: new Date().toISOString(),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async getById(id) {
|
|
17
|
+
return this._store.get(id) ?? null;
|
|
18
|
+
}
|
|
19
|
+
async list() {
|
|
20
|
+
return Array.from(this._store.values());
|
|
21
|
+
}
|
|
22
|
+
async remove(id) {
|
|
23
|
+
return this._store.delete(id);
|
|
24
|
+
}
|
|
25
|
+
async clear() {
|
|
26
|
+
this._store.clear();
|
|
27
|
+
}
|
|
28
|
+
async count() {
|
|
29
|
+
return this._store.size;
|
|
30
|
+
}
|
|
31
|
+
async exists(id) {
|
|
32
|
+
return this._store.has(id);
|
|
33
|
+
}
|
|
34
|
+
async bulkSave(entries) {
|
|
35
|
+
const now = new Date().toISOString();
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
this._store.set(entry.id, { ...entry, updatedAt: now });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async bulkRemove(ids) {
|
|
41
|
+
for (const id of ids)
|
|
42
|
+
this._store.delete(id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file adapter-sqlite.ts
|
|
3
|
+
* @description SQLite-backed ClientStateStorageAdapter.
|
|
4
|
+
*
|
|
5
|
+
* Schema (must exist before construction — call createSchema() or include in
|
|
6
|
+
* your db migration runner):
|
|
7
|
+
*
|
|
8
|
+
* ```sql
|
|
9
|
+
* CREATE TABLE IF NOT EXISTS majik_client_state (
|
|
10
|
+
* key TEXT PRIMARY KEY,
|
|
11
|
+
* value TEXT NOT NULL,
|
|
12
|
+
* updated_at TEXT DEFAULT (datetime('now'))
|
|
13
|
+
* );
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The column is named `key` in SQL (reserved-word-safe via quoting where
|
|
17
|
+
* needed) and mapped to the `id` field of ClientStateEntry in TypeScript so
|
|
18
|
+
* the adapter contract is identical to the IDB and memory variants.
|
|
19
|
+
*/
|
|
20
|
+
import type { ClientStateEntry, ClientStateStorageAdapter } from "./_types";
|
|
21
|
+
import type { SQLiteDatabase } from "../sql-db-manager";
|
|
22
|
+
/** DDL — pass to your migration runner or call createSchema() directly. */
|
|
23
|
+
export declare const MAJIK_CLIENT_STATE_SCHEMA: string;
|
|
24
|
+
export declare class SQLiteClientStateAdapter implements ClientStateStorageAdapter {
|
|
25
|
+
private db;
|
|
26
|
+
constructor(db: SQLiteDatabase);
|
|
27
|
+
/**
|
|
28
|
+
* Ensure the table exists. Call this once during app initialisation if your
|
|
29
|
+
* migration runner does not already execute MAJIK_CLIENT_STATE_SCHEMA.
|
|
30
|
+
*/
|
|
31
|
+
save(entry: ClientStateEntry): Promise<void>;
|
|
32
|
+
bulkSave(entries: ClientStateEntry[]): Promise<void>;
|
|
33
|
+
remove(id: string): Promise<boolean>;
|
|
34
|
+
bulkRemove(ids: string[]): Promise<void>;
|
|
35
|
+
clear(): Promise<void>;
|
|
36
|
+
getById(id: string): Promise<ClientStateEntry | null>;
|
|
37
|
+
list(): Promise<ClientStateEntry[]>;
|
|
38
|
+
count(): Promise<number>;
|
|
39
|
+
exists(id: string): Promise<boolean>;
|
|
40
|
+
private _rowToEntry;
|
|
41
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file adapter-sqlite.ts
|
|
3
|
+
* @description SQLite-backed ClientStateStorageAdapter.
|
|
4
|
+
*
|
|
5
|
+
* Schema (must exist before construction — call createSchema() or include in
|
|
6
|
+
* your db migration runner):
|
|
7
|
+
*
|
|
8
|
+
* ```sql
|
|
9
|
+
* CREATE TABLE IF NOT EXISTS majik_client_state (
|
|
10
|
+
* key TEXT PRIMARY KEY,
|
|
11
|
+
* value TEXT NOT NULL,
|
|
12
|
+
* updated_at TEXT DEFAULT (datetime('now'))
|
|
13
|
+
* );
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The column is named `key` in SQL (reserved-word-safe via quoting where
|
|
17
|
+
* needed) and mapped to the `id` field of ClientStateEntry in TypeScript so
|
|
18
|
+
* the adapter contract is identical to the IDB and memory variants.
|
|
19
|
+
*/
|
|
20
|
+
const TABLE = "majik_client_state";
|
|
21
|
+
/** DDL — pass to your migration runner or call createSchema() directly. */
|
|
22
|
+
export const MAJIK_CLIENT_STATE_SCHEMA = `
|
|
23
|
+
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
24
|
+
key TEXT PRIMARY KEY,
|
|
25
|
+
value TEXT NOT NULL,
|
|
26
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
27
|
+
);
|
|
28
|
+
`.trim();
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Adapter
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
export class SQLiteClientStateAdapter {
|
|
33
|
+
db;
|
|
34
|
+
constructor(db) {
|
|
35
|
+
this.db = db;
|
|
36
|
+
}
|
|
37
|
+
// ── Schema helper ──────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Ensure the table exists. Call this once during app initialisation if your
|
|
40
|
+
* migration runner does not already execute MAJIK_CLIENT_STATE_SCHEMA.
|
|
41
|
+
*/
|
|
42
|
+
// async createSchema(): Promise<void> {
|
|
43
|
+
// await this.db.run(MAJIK_CLIENT_STATE_SCHEMA);
|
|
44
|
+
// }
|
|
45
|
+
// ── Write ──────────────────────────────────────────────────────────────────
|
|
46
|
+
async save(entry) {
|
|
47
|
+
await this.db.run(`INSERT OR REPLACE INTO ${TABLE} (key, value, updated_at)
|
|
48
|
+
VALUES (?, ?, datetime('now'))`, [entry.id, entry.value]);
|
|
49
|
+
}
|
|
50
|
+
async bulkSave(entries) {
|
|
51
|
+
if (entries.length === 0)
|
|
52
|
+
return;
|
|
53
|
+
await this.db.transaction(async (tx) => {
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
await tx.run(`INSERT OR REPLACE INTO ${TABLE} (key, value, updated_at)
|
|
56
|
+
VALUES (?, ?, datetime('now'))`, [entry.id, entry.value]);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async remove(id) {
|
|
61
|
+
const exists = await this.exists(id);
|
|
62
|
+
if (!exists)
|
|
63
|
+
return false;
|
|
64
|
+
await this.db.run(`DELETE FROM ${TABLE} WHERE key = ?`, [id]);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
async bulkRemove(ids) {
|
|
68
|
+
if (ids.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
await this.db.transaction(async (tx) => {
|
|
71
|
+
for (const id of ids) {
|
|
72
|
+
await tx.run(`DELETE FROM ${TABLE} WHERE key = ?`, [id]);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async clear() {
|
|
77
|
+
await this.db.run(`DELETE FROM ${TABLE}`);
|
|
78
|
+
}
|
|
79
|
+
// ── Read ───────────────────────────────────────────────────────────────────
|
|
80
|
+
async getById(id) {
|
|
81
|
+
const row = await this.db.get(`SELECT key, value, updated_at FROM ${TABLE} WHERE key = ?`, [id]);
|
|
82
|
+
return row ? this._rowToEntry(row) : null;
|
|
83
|
+
}
|
|
84
|
+
async list() {
|
|
85
|
+
const rows = await this.db.all(`SELECT key, value, updated_at FROM ${TABLE}`);
|
|
86
|
+
return rows.map((r) => this._rowToEntry(r));
|
|
87
|
+
}
|
|
88
|
+
async count() {
|
|
89
|
+
const row = await this.db.get(`SELECT COUNT(*) as n FROM ${TABLE}`);
|
|
90
|
+
return row?.n ?? 0;
|
|
91
|
+
}
|
|
92
|
+
async exists(id) {
|
|
93
|
+
const row = await this.db.get(`SELECT 1 FROM ${TABLE} WHERE key = ?`, [id]);
|
|
94
|
+
return !!row;
|
|
95
|
+
}
|
|
96
|
+
// ── Private helpers ────────────────────────────────────────────────────────
|
|
97
|
+
_rowToEntry(row) {
|
|
98
|
+
return {
|
|
99
|
+
id: row.key,
|
|
100
|
+
value: row.value,
|
|
101
|
+
updatedAt: row.updated_at,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SerializedMajikContact } from "@majikah/majik-contact";
|
|
2
|
+
import { MajikContactStorageAdapter } from "./_types";
|
|
3
|
+
export declare class InMemoryContactAdapter implements MajikContactStorageAdapter {
|
|
4
|
+
private _store;
|
|
5
|
+
save(contact: SerializedMajikContact): Promise<void>;
|
|
6
|
+
getById(id: string): Promise<SerializedMajikContact | null>;
|
|
7
|
+
list(): Promise<SerializedMajikContact[]>;
|
|
8
|
+
remove(id: string): Promise<boolean>;
|
|
9
|
+
clear(): Promise<void>;
|
|
10
|
+
count(): Promise<number>;
|
|
11
|
+
exists(id: string): Promise<boolean>;
|
|
12
|
+
bulkSave(contacts: SerializedMajikContact[]): Promise<void>;
|
|
13
|
+
bulkRemove(ids: string[]): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class InMemoryContactAdapter {
|
|
2
|
+
_store = new Map();
|
|
3
|
+
async save(contact) {
|
|
4
|
+
this._store.set(contact.id, contact);
|
|
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(contacts) {
|
|
25
|
+
for (const inv of contacts)
|
|
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 { SerializedMajikContact } from "@majikah/majik-contact";
|
|
2
|
+
import { MajikContactStorageAdapter } from "./_types";
|
|
3
|
+
import { SQLiteDatabase } from "../../sql-db-manager";
|
|
4
|
+
import { StorageQuery } from "../../storage-adapter";
|
|
5
|
+
export declare class SQLiteContactAdapter implements MajikContactStorageAdapter {
|
|
6
|
+
private db;
|
|
7
|
+
constructor(db: SQLiteDatabase);
|
|
8
|
+
save(contact: SerializedMajikContact): Promise<void>;
|
|
9
|
+
getById(id: string): Promise<SerializedMajikContact | null>;
|
|
10
|
+
list(): Promise<SerializedMajikContact[]>;
|
|
11
|
+
remove(id: string): Promise<boolean>;
|
|
12
|
+
clear(): Promise<void>;
|
|
13
|
+
count(): Promise<number>;
|
|
14
|
+
exists(id: string): Promise<boolean>;
|
|
15
|
+
bulkSave(contacts: SerializedMajikContact[]): Promise<void>;
|
|
16
|
+
bulkRemove(ids: string[]): Promise<void>;
|
|
17
|
+
query(query: StorageQuery<SerializedMajikContact>): Promise<SerializedMajikContact[]>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { MAJIKAH_SQL_TABLES } from "../../sql-schema";
|
|
2
|
+
export class SQLiteContactAdapter {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
async save(contact) {
|
|
8
|
+
await this.db.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}
|
|
9
|
+
(id, json, fingerprint, label, created_at, updated_at)
|
|
10
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
11
|
+
contact.id,
|
|
12
|
+
JSON.stringify(contact),
|
|
13
|
+
contact.fingerprint ?? null,
|
|
14
|
+
contact.meta?.label ?? null,
|
|
15
|
+
contact.meta?.createdAt ?? null,
|
|
16
|
+
contact.meta?.updatedAt ?? null,
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
19
|
+
async getById(id) {
|
|
20
|
+
const row = await this.db.get(`SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS} 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_CONTACTS}`);
|
|
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_CONTACTS} WHERE id = ?`, [id]);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
async clear() {
|
|
35
|
+
await this.db.run(`DELETE FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}`);
|
|
36
|
+
}
|
|
37
|
+
async count() {
|
|
38
|
+
const row = await this.db.get(`SELECT COUNT(*) as n FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}`);
|
|
39
|
+
return row?.n ?? 0;
|
|
40
|
+
}
|
|
41
|
+
async exists(id) {
|
|
42
|
+
const row = await this.db.get(`SELECT 1 FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS} WHERE id = ?`, [id]);
|
|
43
|
+
return !!row;
|
|
44
|
+
}
|
|
45
|
+
async bulkSave(contacts) {
|
|
46
|
+
if (contacts.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
await this.db.transaction(async (tx) => {
|
|
49
|
+
for (const c of contacts) {
|
|
50
|
+
await tx.run(`INSERT OR REPLACE INTO ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}
|
|
51
|
+
(id, json, fingerprint, label, created_at, updated_at)
|
|
52
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
53
|
+
c.id,
|
|
54
|
+
JSON.stringify(c),
|
|
55
|
+
c.fingerprint ?? null,
|
|
56
|
+
c.meta?.label ?? null,
|
|
57
|
+
c.meta?.createdAt ?? null,
|
|
58
|
+
c.meta?.updatedAt ?? null,
|
|
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_CONTACTS} WHERE id = ?`, [id]);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async query(query) {
|
|
73
|
+
const clauses = [];
|
|
74
|
+
const values = [];
|
|
75
|
+
if (query.where) {
|
|
76
|
+
for (const [key, value] of Object.entries(query.where)) {
|
|
77
|
+
clauses.push(`${key} = ?`);
|
|
78
|
+
values.push(value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let sql = `SELECT json FROM ${MAJIKAH_SQL_TABLES.MAJIK_CONTACTS}`;
|
|
82
|
+
if (clauses.length > 0) {
|
|
83
|
+
sql += ` WHERE ${clauses.join(" AND ")}`;
|
|
84
|
+
}
|
|
85
|
+
if (query.orderBy) {
|
|
86
|
+
sql += ` ORDER BY ${String(query.orderBy)} ${query.orderDirection ?? "asc"}`;
|
|
87
|
+
}
|
|
88
|
+
if (query.limit) {
|
|
89
|
+
sql += ` LIMIT ${query.limit}`;
|
|
90
|
+
}
|
|
91
|
+
if (query.offset) {
|
|
92
|
+
sql += ` OFFSET ${query.offset}`;
|
|
93
|
+
}
|
|
94
|
+
const rows = await this.db.all(sql, values);
|
|
95
|
+
return rows.map((r) => JSON.parse(r.json));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SerializedMajikContactGroup } from "@majikah/majik-contact";
|
|
2
|
+
import { MajikContactGroupStorageAdapter } from "./_types";
|
|
3
|
+
export declare class InMemoryContactGroupAdapter implements MajikContactGroupStorageAdapter {
|
|
4
|
+
private _store;
|
|
5
|
+
save(invoice: SerializedMajikContactGroup): Promise<void>;
|
|
6
|
+
getById(id: string): Promise<SerializedMajikContactGroup | null>;
|
|
7
|
+
list(): Promise<SerializedMajikContactGroup[]>;
|
|
8
|
+
remove(id: string): Promise<boolean>;
|
|
9
|
+
clear(): Promise<void>;
|
|
10
|
+
count(): Promise<number>;
|
|
11
|
+
exists(id: string): Promise<boolean>;
|
|
12
|
+
bulkSave(invoices: SerializedMajikContactGroup[]): Promise<void>;
|
|
13
|
+
bulkRemove(ids: string[]): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class InMemoryContactGroupAdapter {
|
|
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,16 @@
|
|
|
1
|
+
import { SerializedMajikContactGroup } from "@majikah/majik-contact";
|
|
2
|
+
import { MajikContactGroupStorageAdapter } from "./_types";
|
|
3
|
+
import { SQLiteDatabase } from "../../sql-db-manager";
|
|
4
|
+
export declare class SQLiteContactGroupAdapter implements MajikContactGroupStorageAdapter {
|
|
5
|
+
private db;
|
|
6
|
+
constructor(db: SQLiteDatabase);
|
|
7
|
+
save(group: SerializedMajikContactGroup): Promise<void>;
|
|
8
|
+
getById(id: string): Promise<SerializedMajikContactGroup | null>;
|
|
9
|
+
list(): Promise<SerializedMajikContactGroup[]>;
|
|
10
|
+
remove(id: string): Promise<boolean>;
|
|
11
|
+
clear(): Promise<void>;
|
|
12
|
+
count(): Promise<number>;
|
|
13
|
+
exists(id: string): Promise<boolean>;
|
|
14
|
+
bulkSave(groups: SerializedMajikContactGroup[]): Promise<void>;
|
|
15
|
+
bulkRemove(ids: string[]): Promise<void>;
|
|
16
|
+
}
|