@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,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,3 @@
1
+ import { SerializedMajikContact } from "@majikah/majik-contact";
2
+ import { MajikStorageAdapter } from "../../storage-adapter";
3
+ export type MajikContactStorageAdapter = MajikStorageAdapter<SerializedMajikContact>;
@@ -0,0 +1,3 @@
1
+ import { SerializedMajikContact } from "@majikah/majik-contact";
2
+ import { IDBGenericAdapter } from "../../idb-adapter";
3
+ export declare const IDB_ADAPTER_CONTACT: IDBGenericAdapter<SerializedMajikContact>;
@@ -0,0 +1,5 @@
1
+ import { IDBGenericAdapter } from "../../idb-adapter";
2
+ const IDB_DB_NAME = "majik-contacts";
3
+ const IDB_STORE_NAME = "contacts";
4
+ const IDB_VERSION = 1;
5
+ export const IDB_ADAPTER_CONTACT = new IDBGenericAdapter(IDB_DB_NAME, IDB_STORE_NAME, IDB_VERSION);
@@ -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,3 @@
1
+ import { SerializedMajikContactGroup } from "@majikah/majik-contact";
2
+ import { MajikStorageAdapter } from "../../storage-adapter";
3
+ export type MajikContactGroupStorageAdapter = MajikStorageAdapter<SerializedMajikContactGroup>;
@@ -0,0 +1,3 @@
1
+ import { SerializedMajikContactGroup } from "@majikah/majik-contact";
2
+ import { IDBGenericAdapter } from "../../idb-adapter";
3
+ export declare const IDB_ADAPTER_CONTACT_GROUP: IDBGenericAdapter<SerializedMajikContactGroup>;
@@ -0,0 +1,5 @@
1
+ import { IDBGenericAdapter } from "../../idb-adapter";
2
+ const IDB_DB_NAME = "majik-contact-groups";
3
+ const IDB_STORE_NAME = "groups";
4
+ const IDB_VERSION = 1;
5
+ export const IDB_ADAPTER_CONTACT_GROUP = new IDBGenericAdapter(IDB_DB_NAME, IDB_STORE_NAME, IDB_VERSION);
@@ -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
+ }