@meerkapp/electron-bridge 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 meerkapp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # @meerkapp/electron-bridge
2
+
3
+ Bridge between Electron main process and PWA renderer for the Meerk warehouse management system. Provides SQLite persistence via a UtilityProcess with a MessageChannel-based IPC API and a SignalDB storage adapter.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ pnpm add @meerkapp/electron-bridge
9
+ ```
10
+
11
+ > **Note:** `better-sqlite3` requires native compilation. After installing, run:
12
+ > ```sh
13
+ > npx @electron/rebuild
14
+ > ```
15
+
16
+ ## Usage
17
+
18
+ ### Main process
19
+
20
+ ```ts
21
+ import { app, ipcMain } from 'electron'
22
+ import { setupElectronBridge } from '@meerkapp/electron-bridge'
23
+
24
+ app.whenReady().then(() => {
25
+ setupElectronBridge(app, ipcMain)
26
+ // ...create BrowserWindow etc.
27
+ })
28
+ ```
29
+
30
+ ### Preload script
31
+
32
+ Point Electron's `webPreferences.preload` to the bundled preload file:
33
+
34
+ ```ts
35
+ import { BrowserWindow } from 'electron'
36
+ import path from 'path'
37
+
38
+ new BrowserWindow({
39
+ webPreferences: {
40
+ preload: path.join(
41
+ path.dirname(require.resolve('@meerkapp/electron-bridge')),
42
+ 'preload.cjs'
43
+ ),
44
+ contextIsolation: true,
45
+ sandbox: false,
46
+ },
47
+ })
48
+ ```
49
+
50
+ ### Renderer / PWA
51
+
52
+ ```ts
53
+ import { createSQLiteIPCAdapter, isElectron } from '@meerkapp/electron-bridge'
54
+ import { Collection } from '@signaldb/core'
55
+
56
+ if (isElectron()) {
57
+ const adapter = createSQLiteIPCAdapter<Product, string>(
58
+ '/path/to/products.db',
59
+ 'products'
60
+ )
61
+
62
+ const products = new Collection({ persistence: adapter })
63
+ }
64
+ ```
65
+
66
+ ### Accessing `window.db` directly
67
+
68
+ `window.db` is exposed by the preload script and provides the full `DbApi`:
69
+
70
+ ```ts
71
+ const result = await window.db.checkFileExists({ filePath: '/path/to/file.db' })
72
+ if (result.success && result.data?.exists) {
73
+ // ...
74
+ }
75
+ ```
76
+
77
+ ## Native module rebuild
78
+
79
+ `better-sqlite3` is a native Node.js addon and must be compiled against the Electron version in use. Run after `pnpm install`:
80
+
81
+ ```sh
82
+ npx @electron/rebuild -f -w better-sqlite3
83
+ ```
84
+
85
+ Add this to your `postinstall` script to automate it:
86
+
87
+ ```json
88
+ {
89
+ "scripts": {
90
+ "postinstall": "electron-rebuild -f -w better-sqlite3"
91
+ }
92
+ }
93
+ ```
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/db-process.ts
26
+ var import_path = __toESM(require("path"), 1);
27
+ var import_fs = __toESM(require("fs"), 1);
28
+ var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
29
+ process.on("uncaughtException", (error) => {
30
+ console.error("[DB Process] Uncaught Exception:", error);
31
+ });
32
+ process.on("unhandledRejection", (reason) => {
33
+ console.error("[DB Process] Unhandled Rejection:", reason);
34
+ });
35
+ var dbConnections = /* @__PURE__ */ new Map();
36
+ function getConnection(dbPath) {
37
+ if (dbConnections.has(dbPath)) return dbConnections.get(dbPath);
38
+ const dir = import_path.default.dirname(dbPath);
39
+ if (!import_fs.default.existsSync(dir)) import_fs.default.mkdirSync(dir, { recursive: true });
40
+ const db = new import_better_sqlite3.default(dbPath);
41
+ db.pragma("journal_mode = WAL");
42
+ dbConnections.set(dbPath, db);
43
+ return db;
44
+ }
45
+ if (!process.parentPort) {
46
+ console.error("[DB Process] Must be run as UtilityProcess");
47
+ process.exit(1);
48
+ }
49
+ var port = null;
50
+ process.parentPort.on("message", (e) => {
51
+ if (e.data?.type === "port") {
52
+ port = e.ports[0];
53
+ port.on("message", handleMessage);
54
+ port.start();
55
+ }
56
+ });
57
+ function handleMessage(e) {
58
+ const { id, method, args } = e.data;
59
+ try {
60
+ const result = executeMethod(method, args);
61
+ port?.postMessage({ id, success: true, ...result });
62
+ } catch (error) {
63
+ console.error(`[DB Process] Error in ${method}:`, error);
64
+ port?.postMessage({ id, success: false, error: error.message });
65
+ }
66
+ }
67
+ function executeMethod(method, args) {
68
+ const { dbPath, tableName, filePath, items, ids, field } = args;
69
+ switch (method) {
70
+ case "setup": {
71
+ const db = getConnection(dbPath);
72
+ db.exec(`CREATE TABLE IF NOT EXISTS "${tableName}" (id TEXT PRIMARY KEY, data TEXT)`);
73
+ return {};
74
+ }
75
+ case "readAll": {
76
+ const db = getConnection(dbPath);
77
+ const rows = db.prepare(`SELECT data FROM "${tableName}"`).all();
78
+ return { data: rows.map((r) => JSON.parse(r.data)) };
79
+ }
80
+ case "readIds": {
81
+ const db = getConnection(dbPath);
82
+ const placeholders = ids.map(() => "?").join(", ");
83
+ const rows = db.prepare(`SELECT data FROM "${tableName}" WHERE id IN (${placeholders})`).all(...ids);
84
+ return { data: rows.map((r) => JSON.parse(r.data)) };
85
+ }
86
+ case "insert":
87
+ case "replace": {
88
+ const db = getConnection(dbPath);
89
+ const stmt = db.prepare(`INSERT OR REPLACE INTO "${tableName}" (id, data) VALUES (?, ?)`);
90
+ const run = db.transaction((rows) => {
91
+ for (const item of rows) stmt.run(String(item.id), JSON.stringify(item));
92
+ });
93
+ run(items);
94
+ return {};
95
+ }
96
+ case "remove": {
97
+ const db = getConnection(dbPath);
98
+ const stmt = db.prepare(`DELETE FROM "${tableName}" WHERE id = ?`);
99
+ const run = db.transaction((rows) => {
100
+ for (const item of rows) stmt.run(String(item.id));
101
+ });
102
+ run(items);
103
+ return {};
104
+ }
105
+ case "removeAll": {
106
+ const db = getConnection(dbPath);
107
+ db.prepare(`DELETE FROM "${tableName}"`).run();
108
+ return {};
109
+ }
110
+ case "createIndex": {
111
+ const db = getConnection(dbPath);
112
+ const colName = `idx_${field}`;
113
+ const existing = db.pragma(`table_xinfo("${tableName}")`);
114
+ if (!existing.some((col) => col.name === colName)) {
115
+ db.exec(
116
+ `ALTER TABLE "${tableName}" ADD COLUMN "${colName}" TEXT GENERATED ALWAYS AS (json_extract(data, '$.${field}')) VIRTUAL`
117
+ );
118
+ }
119
+ db.exec(`CREATE INDEX IF NOT EXISTS "idx_${tableName}_${field}" ON "${tableName}" ("${colName}")`);
120
+ return {};
121
+ }
122
+ case "dropIndex": {
123
+ const db = getConnection(dbPath);
124
+ db.exec(`DROP INDEX IF EXISTS "idx_${tableName}_${field}"`);
125
+ return {};
126
+ }
127
+ case "readIndex": {
128
+ const db = getConnection(dbPath);
129
+ const rows = db.prepare(`SELECT id, json_extract(data, '$.${field}') as field_value FROM "${tableName}"`).all();
130
+ const index = {};
131
+ for (const row of rows) {
132
+ if (row.field_value == null) continue;
133
+ const key = String(row.field_value);
134
+ if (!index[key]) index[key] = [];
135
+ index[key].push(row.id);
136
+ }
137
+ return { data: index };
138
+ }
139
+ case "getMaxUpdatedAt": {
140
+ const db = getConnection(dbPath);
141
+ const cols = db.pragma(`table_xinfo("${tableName}")`);
142
+ const hasColumn = cols.some((col) => col.name === "idx_updated_at");
143
+ if (!hasColumn) return { data: { maxUpdatedAt: null, hasColumn: false } };
144
+ const row = db.prepare(`SELECT MAX(idx_updated_at) as maxUpdatedAt FROM "${tableName}"`).get();
145
+ return { data: { maxUpdatedAt: row.maxUpdatedAt, hasColumn: true } };
146
+ }
147
+ case "checkFileExists": {
148
+ return { data: { exists: import_fs.default.existsSync(filePath) } };
149
+ }
150
+ case "deleteDbFile": {
151
+ if (dbConnections.has(filePath)) {
152
+ dbConnections.get(filePath).close();
153
+ dbConnections.delete(filePath);
154
+ }
155
+ const base = filePath.endsWith(".db") ? filePath.slice(0, -3) : filePath;
156
+ for (const p of [base + ".db", base + ".db-wal", base + ".db-shm"]) {
157
+ if (import_fs.default.existsSync(p)) import_fs.default.unlinkSync(p);
158
+ }
159
+ return {};
160
+ }
161
+ default:
162
+ throw new Error(`Unknown method: ${method}`);
163
+ }
164
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ createSQLiteIPCAdapter: () => createSQLiteIPCAdapter,
34
+ isElectron: () => isElectron,
35
+ setupElectronBridge: () => setupElectronBridge
36
+ });
37
+ module.exports = __toCommonJS(src_exports);
38
+
39
+ // src/signaldb-adapter.ts
40
+ var import_core = require("@signaldb/core");
41
+ function createSQLiteIPCAdapter(dbPath, tableName) {
42
+ return (0, import_core.createStorageAdapter)({
43
+ async setup() {
44
+ const result = await window.db.setup({ dbPath, tableName });
45
+ if (!result.success) throw new Error(result.error || "Failed to setup table");
46
+ },
47
+ async teardown() {
48
+ },
49
+ async readAll() {
50
+ const result = await window.db.readAll({ dbPath, tableName });
51
+ if (!result.success) throw new Error(result.error || "Failed to read data");
52
+ return result.data;
53
+ },
54
+ async readIds(ids) {
55
+ const result = await window.db.readIds({ dbPath, tableName, ids });
56
+ if (!result.success) throw new Error(result.error || "Failed to read items by IDs");
57
+ return result.data;
58
+ },
59
+ async createIndex(field) {
60
+ const result = await window.db.createIndex({ dbPath, tableName, field });
61
+ if (!result.success) throw new Error(result.error || `Failed to create index for field: ${field}`);
62
+ },
63
+ async dropIndex(field) {
64
+ const result = await window.db.dropIndex({ dbPath, tableName, field });
65
+ if (!result.success) throw new Error(result.error || `Failed to drop index for field: ${field}`);
66
+ },
67
+ async readIndex(field) {
68
+ const result = await window.db.readIndex({ dbPath, tableName, field });
69
+ if (!result.success) throw new Error(result.error || `Failed to read index for field: ${field}`);
70
+ const indexMap = /* @__PURE__ */ new Map();
71
+ if (result.data) {
72
+ for (const [key, ids] of Object.entries(result.data)) {
73
+ indexMap.set(key, new Set(ids));
74
+ }
75
+ }
76
+ return indexMap;
77
+ },
78
+ async insert(items) {
79
+ const result = await window.db.insert({ dbPath, tableName, items });
80
+ if (!result.success) throw new Error(result.error || "Failed to insert items");
81
+ },
82
+ async replace(items) {
83
+ const result = await window.db.replace({ dbPath, tableName, items });
84
+ if (!result.success) throw new Error(result.error || "Failed to replace items");
85
+ },
86
+ async remove(items) {
87
+ const result = await window.db.remove({ dbPath, tableName, items });
88
+ if (!result.success) throw new Error(result.error || "Failed to remove items");
89
+ },
90
+ async removeAll() {
91
+ const result = await window.db.removeAll({ dbPath, tableName });
92
+ if (!result.success) throw new Error(result.error || "Failed to remove all items");
93
+ }
94
+ });
95
+ }
96
+
97
+ // src/main.ts
98
+ var import_electron = require("electron");
99
+ var import_path = __toESM(require("path"), 1);
100
+ function setupElectronBridge(electronApp, electronIpcMain) {
101
+ const dbProcessPath = import_path.default.join(__dirname, "db-process.cjs");
102
+ const child = import_electron.utilityProcess.fork(dbProcessPath);
103
+ electronIpcMain.on("db:port", (event) => {
104
+ child.postMessage({ type: "port" }, [event.ports[0]]);
105
+ });
106
+ electronApp.on("before-quit", () => child.kill());
107
+ }
108
+
109
+ // src/index.ts
110
+ var isElectron = () => typeof window !== "undefined" && !!window.db;
111
+ // Annotate the CommonJS export names for ESM import in node:
112
+ 0 && (module.exports = {
113
+ createSQLiteIPCAdapter,
114
+ isElectron,
115
+ setupElectronBridge
116
+ });
@@ -0,0 +1,87 @@
1
+ import * as _signaldb_core from '@signaldb/core';
2
+ import { app, ipcMain } from 'electron';
3
+
4
+ declare function createSQLiteIPCAdapter<T extends {
5
+ id: I;
6
+ } & Record<string, any>, I extends string | number>(dbPath: string, tableName: string): _signaldb_core.StorageAdapter<T, I>;
7
+
8
+ declare function setupElectronBridge(electronApp: typeof app, electronIpcMain: typeof ipcMain): void;
9
+
10
+ interface DbResult<T = void> {
11
+ success: boolean;
12
+ data?: T;
13
+ error?: string;
14
+ }
15
+ interface DbApi {
16
+ setup(args: {
17
+ dbPath: string;
18
+ tableName: string;
19
+ }): Promise<DbResult>;
20
+ readAll(args: {
21
+ dbPath: string;
22
+ tableName: string;
23
+ }): Promise<DbResult<any[]>>;
24
+ readIds(args: {
25
+ dbPath: string;
26
+ tableName: string;
27
+ ids: (string | number)[];
28
+ }): Promise<DbResult<any[]>>;
29
+ insert(args: {
30
+ dbPath: string;
31
+ tableName: string;
32
+ items: any[];
33
+ }): Promise<DbResult>;
34
+ replace(args: {
35
+ dbPath: string;
36
+ tableName: string;
37
+ items: any[];
38
+ }): Promise<DbResult>;
39
+ remove(args: {
40
+ dbPath: string;
41
+ tableName: string;
42
+ items: any[];
43
+ }): Promise<DbResult>;
44
+ removeAll(args: {
45
+ dbPath: string;
46
+ tableName: string;
47
+ }): Promise<DbResult>;
48
+ createIndex(args: {
49
+ dbPath: string;
50
+ tableName: string;
51
+ field: string;
52
+ }): Promise<DbResult>;
53
+ dropIndex(args: {
54
+ dbPath: string;
55
+ tableName: string;
56
+ field: string;
57
+ }): Promise<DbResult>;
58
+ readIndex(args: {
59
+ dbPath: string;
60
+ tableName: string;
61
+ field: string;
62
+ }): Promise<DbResult<Record<string, (string | number)[]>>>;
63
+ getMaxUpdatedAt(args: {
64
+ dbPath: string;
65
+ tableName: string;
66
+ }): Promise<DbResult<{
67
+ maxUpdatedAt: string | null;
68
+ hasColumn: boolean;
69
+ }>>;
70
+ checkFileExists(args: {
71
+ filePath: string;
72
+ }): Promise<DbResult<{
73
+ exists: boolean;
74
+ }>>;
75
+ deleteDbFile(args: {
76
+ filePath: string;
77
+ }): Promise<DbResult>;
78
+ }
79
+ declare global {
80
+ interface Window {
81
+ db: DbApi;
82
+ }
83
+ }
84
+
85
+ declare const isElectron: () => boolean;
86
+
87
+ export { type DbApi, type DbResult, createSQLiteIPCAdapter, isElectron, setupElectronBridge };
@@ -0,0 +1,87 @@
1
+ import * as _signaldb_core from '@signaldb/core';
2
+ import { app, ipcMain } from 'electron';
3
+
4
+ declare function createSQLiteIPCAdapter<T extends {
5
+ id: I;
6
+ } & Record<string, any>, I extends string | number>(dbPath: string, tableName: string): _signaldb_core.StorageAdapter<T, I>;
7
+
8
+ declare function setupElectronBridge(electronApp: typeof app, electronIpcMain: typeof ipcMain): void;
9
+
10
+ interface DbResult<T = void> {
11
+ success: boolean;
12
+ data?: T;
13
+ error?: string;
14
+ }
15
+ interface DbApi {
16
+ setup(args: {
17
+ dbPath: string;
18
+ tableName: string;
19
+ }): Promise<DbResult>;
20
+ readAll(args: {
21
+ dbPath: string;
22
+ tableName: string;
23
+ }): Promise<DbResult<any[]>>;
24
+ readIds(args: {
25
+ dbPath: string;
26
+ tableName: string;
27
+ ids: (string | number)[];
28
+ }): Promise<DbResult<any[]>>;
29
+ insert(args: {
30
+ dbPath: string;
31
+ tableName: string;
32
+ items: any[];
33
+ }): Promise<DbResult>;
34
+ replace(args: {
35
+ dbPath: string;
36
+ tableName: string;
37
+ items: any[];
38
+ }): Promise<DbResult>;
39
+ remove(args: {
40
+ dbPath: string;
41
+ tableName: string;
42
+ items: any[];
43
+ }): Promise<DbResult>;
44
+ removeAll(args: {
45
+ dbPath: string;
46
+ tableName: string;
47
+ }): Promise<DbResult>;
48
+ createIndex(args: {
49
+ dbPath: string;
50
+ tableName: string;
51
+ field: string;
52
+ }): Promise<DbResult>;
53
+ dropIndex(args: {
54
+ dbPath: string;
55
+ tableName: string;
56
+ field: string;
57
+ }): Promise<DbResult>;
58
+ readIndex(args: {
59
+ dbPath: string;
60
+ tableName: string;
61
+ field: string;
62
+ }): Promise<DbResult<Record<string, (string | number)[]>>>;
63
+ getMaxUpdatedAt(args: {
64
+ dbPath: string;
65
+ tableName: string;
66
+ }): Promise<DbResult<{
67
+ maxUpdatedAt: string | null;
68
+ hasColumn: boolean;
69
+ }>>;
70
+ checkFileExists(args: {
71
+ filePath: string;
72
+ }): Promise<DbResult<{
73
+ exists: boolean;
74
+ }>>;
75
+ deleteDbFile(args: {
76
+ filePath: string;
77
+ }): Promise<DbResult>;
78
+ }
79
+ declare global {
80
+ interface Window {
81
+ db: DbApi;
82
+ }
83
+ }
84
+
85
+ declare const isElectron: () => boolean;
86
+
87
+ export { type DbApi, type DbResult, createSQLiteIPCAdapter, isElectron, setupElectronBridge };
package/dist/index.js ADDED
@@ -0,0 +1,77 @@
1
+ // src/signaldb-adapter.ts
2
+ import { createStorageAdapter } from "@signaldb/core";
3
+ function createSQLiteIPCAdapter(dbPath, tableName) {
4
+ return createStorageAdapter({
5
+ async setup() {
6
+ const result = await window.db.setup({ dbPath, tableName });
7
+ if (!result.success) throw new Error(result.error || "Failed to setup table");
8
+ },
9
+ async teardown() {
10
+ },
11
+ async readAll() {
12
+ const result = await window.db.readAll({ dbPath, tableName });
13
+ if (!result.success) throw new Error(result.error || "Failed to read data");
14
+ return result.data;
15
+ },
16
+ async readIds(ids) {
17
+ const result = await window.db.readIds({ dbPath, tableName, ids });
18
+ if (!result.success) throw new Error(result.error || "Failed to read items by IDs");
19
+ return result.data;
20
+ },
21
+ async createIndex(field) {
22
+ const result = await window.db.createIndex({ dbPath, tableName, field });
23
+ if (!result.success) throw new Error(result.error || `Failed to create index for field: ${field}`);
24
+ },
25
+ async dropIndex(field) {
26
+ const result = await window.db.dropIndex({ dbPath, tableName, field });
27
+ if (!result.success) throw new Error(result.error || `Failed to drop index for field: ${field}`);
28
+ },
29
+ async readIndex(field) {
30
+ const result = await window.db.readIndex({ dbPath, tableName, field });
31
+ if (!result.success) throw new Error(result.error || `Failed to read index for field: ${field}`);
32
+ const indexMap = /* @__PURE__ */ new Map();
33
+ if (result.data) {
34
+ for (const [key, ids] of Object.entries(result.data)) {
35
+ indexMap.set(key, new Set(ids));
36
+ }
37
+ }
38
+ return indexMap;
39
+ },
40
+ async insert(items) {
41
+ const result = await window.db.insert({ dbPath, tableName, items });
42
+ if (!result.success) throw new Error(result.error || "Failed to insert items");
43
+ },
44
+ async replace(items) {
45
+ const result = await window.db.replace({ dbPath, tableName, items });
46
+ if (!result.success) throw new Error(result.error || "Failed to replace items");
47
+ },
48
+ async remove(items) {
49
+ const result = await window.db.remove({ dbPath, tableName, items });
50
+ if (!result.success) throw new Error(result.error || "Failed to remove items");
51
+ },
52
+ async removeAll() {
53
+ const result = await window.db.removeAll({ dbPath, tableName });
54
+ if (!result.success) throw new Error(result.error || "Failed to remove all items");
55
+ }
56
+ });
57
+ }
58
+
59
+ // src/main.ts
60
+ import { utilityProcess } from "electron";
61
+ import path from "path";
62
+ function setupElectronBridge(electronApp, electronIpcMain) {
63
+ const dbProcessPath = path.join(__dirname, "db-process.cjs");
64
+ const child = utilityProcess.fork(dbProcessPath);
65
+ electronIpcMain.on("db:port", (event) => {
66
+ child.postMessage({ type: "port" }, [event.ports[0]]);
67
+ });
68
+ electronApp.on("before-quit", () => child.kill());
69
+ }
70
+
71
+ // src/index.ts
72
+ var isElectron = () => typeof window !== "undefined" && !!window.db;
73
+ export {
74
+ createSQLiteIPCAdapter,
75
+ isElectron,
76
+ setupElectronBridge
77
+ };
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ // src/preload.ts
4
+ var import_electron = require("electron");
5
+ var { port1, port2 } = new MessageChannel();
6
+ import_electron.ipcRenderer.postMessage("db:port", null, [port2]);
7
+ var requestId = 0;
8
+ var pending = /* @__PURE__ */ new Map();
9
+ port1.onmessage = (e) => {
10
+ const { id, success, error, ...data } = e.data;
11
+ const handler = pending.get(id);
12
+ if (!handler) return;
13
+ pending.delete(id);
14
+ if (success) handler.resolve(data);
15
+ else handler.reject(new Error(error));
16
+ };
17
+ port1.start();
18
+ function invoke(method, args) {
19
+ return new Promise((resolve, reject) => {
20
+ const id = ++requestId;
21
+ pending.set(id, { resolve, reject });
22
+ port1.postMessage({ id, method, args });
23
+ });
24
+ }
25
+ import_electron.contextBridge.exposeInMainWorld("db", {
26
+ setup: (args) => invoke("setup", args),
27
+ readAll: (args) => invoke("readAll", args),
28
+ readIds: (args) => invoke("readIds", args),
29
+ insert: (args) => invoke("insert", args),
30
+ replace: (args) => invoke("replace", args),
31
+ remove: (args) => invoke("remove", args),
32
+ removeAll: (args) => invoke("removeAll", args),
33
+ createIndex: (args) => invoke("createIndex", args),
34
+ dropIndex: (args) => invoke("dropIndex", args),
35
+ readIndex: (args) => invoke("readIndex", args),
36
+ getMaxUpdatedAt: (args) => invoke("getMaxUpdatedAt", args),
37
+ checkFileExists: (args) => invoke("checkFileExists", args),
38
+ deleteDbFile: (args) => invoke("deleteDbFile", args)
39
+ });
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,37 @@
1
+ // src/preload.ts
2
+ import { contextBridge, ipcRenderer } from "electron";
3
+ var { port1, port2 } = new MessageChannel();
4
+ ipcRenderer.postMessage("db:port", null, [port2]);
5
+ var requestId = 0;
6
+ var pending = /* @__PURE__ */ new Map();
7
+ port1.onmessage = (e) => {
8
+ const { id, success, error, ...data } = e.data;
9
+ const handler = pending.get(id);
10
+ if (!handler) return;
11
+ pending.delete(id);
12
+ if (success) handler.resolve(data);
13
+ else handler.reject(new Error(error));
14
+ };
15
+ port1.start();
16
+ function invoke(method, args) {
17
+ return new Promise((resolve, reject) => {
18
+ const id = ++requestId;
19
+ pending.set(id, { resolve, reject });
20
+ port1.postMessage({ id, method, args });
21
+ });
22
+ }
23
+ contextBridge.exposeInMainWorld("db", {
24
+ setup: (args) => invoke("setup", args),
25
+ readAll: (args) => invoke("readAll", args),
26
+ readIds: (args) => invoke("readIds", args),
27
+ insert: (args) => invoke("insert", args),
28
+ replace: (args) => invoke("replace", args),
29
+ remove: (args) => invoke("remove", args),
30
+ removeAll: (args) => invoke("removeAll", args),
31
+ createIndex: (args) => invoke("createIndex", args),
32
+ dropIndex: (args) => invoke("dropIndex", args),
33
+ readIndex: (args) => invoke("readIndex", args),
34
+ getMaxUpdatedAt: (args) => invoke("getMaxUpdatedAt", args),
35
+ checkFileExists: (args) => invoke("checkFileExists", args),
36
+ deleteDbFile: (args) => invoke("deleteDbFile", args)
37
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@meerkapp/electron-bridge",
3
+ "version": "0.1.0",
4
+ "description": "Bridge between Electron main process and PWA renderer for Meerk warehouse management system",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./db-process": "./dist/db-process.cjs",
16
+ "./preload": "./dist/preload.cjs"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "dependencies": {
22
+ "better-sqlite3": "^11.0.0"
23
+ },
24
+ "peerDependencies": {
25
+ "@signaldb/core": "2.0.0-beta.6",
26
+ "electron": "^37.5.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/better-sqlite3": "^7.6.12",
30
+ "@types/node": "^22.0.0",
31
+ "electron": "^37.5.0",
32
+ "tsup": "^8.0.0",
33
+ "typescript": "^5.0.0"
34
+ },
35
+ "publishConfig": {
36
+ "registry": "https://npm.pkg.github.com"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsup --watch"
41
+ }
42
+ }