@janhendry/nanostore-ipc-bridge 0.0.1

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.
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ var electron = require('electron');
4
+
5
+ // src/preload/exposeNanoStoreIPC.ts
6
+ function ch(prefix, c) {
7
+ return prefix ? `${prefix}:${c}` : c;
8
+ }
9
+ function validateIPCValue(value) {
10
+ if (value === null || value === void 0) return;
11
+ const type = typeof value;
12
+ if (type === "string" || type === "number" || type === "boolean") return;
13
+ if (type === "function" || type === "symbol") {
14
+ throw new TypeError(`Cannot serialize ${type} values via IPC`);
15
+ }
16
+ if (type === "object") {
17
+ const proto = Object.getPrototypeOf(value);
18
+ if (proto !== Object.prototype && proto !== Array.prototype && proto !== null) {
19
+ if (!(value instanceof Date || value instanceof RegExp || value instanceof Error)) {
20
+ throw new TypeError("Cannot serialize class instances via IPC");
21
+ }
22
+ }
23
+ if ("__proto__" in value || "constructor" in value || "prototype" in value) {
24
+ const obj = value;
25
+ const proto2 = Object.getPrototypeOf(obj);
26
+ if (proto2 !== Object.prototype && proto2 !== Array.prototype && proto2 !== null) {
27
+ throw new TypeError("Potentially unsafe object structure");
28
+ }
29
+ }
30
+ }
31
+ }
32
+ function validateId(id) {
33
+ if (typeof id !== "string" || id.length === 0) {
34
+ throw new TypeError("ID must be a non-empty string");
35
+ }
36
+ if (id.length > 256) {
37
+ throw new TypeError("ID too long (max 256 characters)");
38
+ }
39
+ }
40
+ function exposeNanoStoreIPC(opts = {}) {
41
+ const channelPrefix = opts.channelPrefix ?? "";
42
+ const globalName = opts.globalName ?? "nanostoreIPC";
43
+ const api = {
44
+ get: (id) => {
45
+ validateId(id);
46
+ return electron.ipcRenderer.invoke(ch(channelPrefix, "ns:get"), id);
47
+ },
48
+ set: (id, value) => {
49
+ validateId(id);
50
+ validateIPCValue(value);
51
+ return electron.ipcRenderer.invoke(ch(channelPrefix, "ns:set"), id, value);
52
+ },
53
+ subscribe: (id, cb) => {
54
+ validateId(id);
55
+ if (typeof cb !== "function") {
56
+ throw new TypeError("Callback must be a function");
57
+ }
58
+ const channel = ch(channelPrefix, "ns:update");
59
+ const handler = (_, snap) => {
60
+ if (snap.id !== id) return;
61
+ cb(snap);
62
+ };
63
+ electron.ipcRenderer.on(channel, handler);
64
+ return () => electron.ipcRenderer.removeListener(channel, handler);
65
+ },
66
+ subscribeAll: (cb) => {
67
+ if (typeof cb !== "function") {
68
+ throw new TypeError("Callback must be a function");
69
+ }
70
+ const channel = ch(channelPrefix, "ns:update");
71
+ const handler = (_, snap) => cb(snap);
72
+ electron.ipcRenderer.on(channel, handler);
73
+ return () => electron.ipcRenderer.removeListener(channel, handler);
74
+ },
75
+ // Service RPC call
76
+ callService: async (serviceId, method, ...args) => {
77
+ validateId(serviceId);
78
+ validateId(method);
79
+ for (const arg of args) {
80
+ validateIPCValue(arg);
81
+ }
82
+ const channel = ch(channelPrefix, "svc:call");
83
+ const result = await electron.ipcRenderer.invoke(
84
+ channel,
85
+ serviceId,
86
+ method,
87
+ args
88
+ );
89
+ if (!result.success) {
90
+ const error = new Error(result.error?.message || "Service call failed");
91
+ error.name = result.error?.code || "ServiceError";
92
+ if (result.error?.stack) {
93
+ error.stack = result.error.stack;
94
+ }
95
+ throw error;
96
+ }
97
+ return result.result;
98
+ },
99
+ // Subscribe to service events
100
+ subscribeServiceEvent: (serviceId, eventName, cb) => {
101
+ validateId(serviceId);
102
+ validateId(eventName);
103
+ if (typeof cb !== "function") {
104
+ throw new TypeError("Callback must be a function");
105
+ }
106
+ const channel = ch(channelPrefix, "svc:event");
107
+ const handler = (_, payload) => {
108
+ if (payload.serviceId !== serviceId || payload.eventName !== eventName)
109
+ return;
110
+ cb(payload.data);
111
+ };
112
+ electron.ipcRenderer.on(channel, handler);
113
+ return () => electron.ipcRenderer.removeListener(channel, handler);
114
+ }
115
+ };
116
+ electron.contextBridge.exposeInMainWorld(globalName, api);
117
+ return api;
118
+ }
119
+
120
+ exports.exposeNanoStoreIPC = exposeNanoStoreIPC;
121
+ //# sourceMappingURL=index.js.map
122
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/preload/exposeNanoStoreIPC.ts"],"names":["proto","ipcRenderer","contextBridge"],"mappings":"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","file":"index.js","sourcesContent":["import { contextBridge, ipcRenderer } from \"electron\";\nimport type { ServiceCallResult, Snapshot } from \"../internal/types\";\n\nexport interface ExposeNanoStoreIPCOptions {\n\tchannelPrefix?: string;\n\t/**\n\t * Name under which the API is exposed to window.\n\t * Default: \"nanostoreIPC\"\n\t */\n\tglobalName?: string;\n}\n\nexport type NanoStoreIPC = {\n\tget: <T = unknown>(id: string) => Promise<Snapshot<T>>;\n\tset: <T = unknown>(id: string, value: T) => Promise<void>;\n\tsubscribe: <T = unknown>(\n\t\tid: string,\n\t\tcb: (snap: Snapshot<T>) => void,\n\t) => () => void;\n\tsubscribeAll: (cb: (snap: Snapshot<unknown>) => void) => () => void;\n\t// Services\n\tcallService: <T = unknown>(\n\t\tserviceId: string,\n\t\tmethod: string,\n\t\t...args: unknown[]\n\t) => Promise<T>;\n\tsubscribeServiceEvent: (\n\t\tserviceId: string,\n\t\teventName: string,\n\t\tcb: (data: unknown) => void,\n\t) => () => void;\n};\n\nfunction ch(prefix: string, c: string) {\n\treturn prefix ? `${prefix}:${c}` : c;\n}\n\n/**\n * Validates that a value is safe to send via IPC.\n * Checks for basic serializability and prevents prototype pollution.\n */\nfunction validateIPCValue(value: unknown): void {\n\tif (value === null || value === undefined) return;\n\n\tconst type = typeof value;\n\n\t// Primitives are always safe\n\tif (type === \"string\" || type === \"number\" || type === \"boolean\") return;\n\n\t// Functions, symbols, and undefined are not serializable\n\tif (type === \"function\" || type === \"symbol\") {\n\t\tthrow new TypeError(`Cannot serialize ${type} values via IPC`);\n\t}\n\n\t// Check objects and arrays recursively (with depth limit)\n\tif (type === \"object\") {\n\t\t// Reject dangerous constructors\n\t\tconst proto = Object.getPrototypeOf(value);\n\t\tif (\n\t\t\tproto !== Object.prototype &&\n\t\t\tproto !== Array.prototype &&\n\t\t\tproto !== null\n\t\t) {\n\t\t\t// Allow Date, RegExp, Error which are structured-cloneable\n\t\t\tif (\n\t\t\t\t!(\n\t\t\t\t\tvalue instanceof Date ||\n\t\t\t\t\tvalue instanceof RegExp ||\n\t\t\t\t\tvalue instanceof Error\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tthrow new TypeError(\"Cannot serialize class instances via IPC\");\n\t\t\t}\n\t\t}\n\n\t\t// Check for prototype pollution attempts\n\t\tif (\n\t\t\t\"__proto__\" in (value as object) ||\n\t\t\t\"constructor\" in (value as object) ||\n\t\t\t\"prototype\" in (value as object)\n\t\t) {\n\t\t\tconst obj = value as Record<string, unknown>;\n\t\t\t// Use Object.getPrototypeOf instead of deprecated __proto__\n\t\t\tconst proto = Object.getPrototypeOf(obj);\n\t\t\tif (\n\t\t\t\tproto !== Object.prototype &&\n\t\t\t\tproto !== Array.prototype &&\n\t\t\t\tproto !== null\n\t\t\t) {\n\t\t\t\tthrow new TypeError(\"Potentially unsafe object structure\");\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Validates string IDs to prevent injection attacks\n */\nfunction validateId(id: string): void {\n\tif (typeof id !== \"string\" || id.length === 0) {\n\t\tthrow new TypeError(\"ID must be a non-empty string\");\n\t}\n\tif (id.length > 256) {\n\t\tthrow new TypeError(\"ID too long (max 256 characters)\");\n\t}\n}\n\nexport function exposeNanoStoreIPC(opts: ExposeNanoStoreIPCOptions = {}) {\n\tconst channelPrefix = opts.channelPrefix ?? \"\";\n\tconst globalName = opts.globalName ?? \"nanostoreIPC\";\n\n\tconst api: NanoStoreIPC = {\n\t\tget: (id) => {\n\t\t\tvalidateId(id);\n\t\t\treturn ipcRenderer.invoke(ch(channelPrefix, \"ns:get\"), id);\n\t\t},\n\t\tset: (id, value) => {\n\t\t\tvalidateId(id);\n\t\t\tvalidateIPCValue(value);\n\t\t\treturn ipcRenderer.invoke(ch(channelPrefix, \"ns:set\"), id, value);\n\t\t},\n\t\tsubscribe: <T>(id: string, cb: (snap: Snapshot<T>) => void) => {\n\t\t\tvalidateId(id);\n\t\t\tif (typeof cb !== \"function\") {\n\t\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t\t}\n\t\t\tconst channel = ch(channelPrefix, \"ns:update\");\n\t\t\tconst handler = (_: unknown, snap: Snapshot<unknown>) => {\n\t\t\t\tif (snap.id !== id) return;\n\t\t\t\tcb(snap as Snapshot<T>);\n\t\t\t};\n\t\t\tipcRenderer.on(channel, handler);\n\t\t\treturn () => ipcRenderer.removeListener(channel, handler);\n\t\t},\n\t\tsubscribeAll: (cb) => {\n\t\t\tif (typeof cb !== \"function\") {\n\t\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t\t}\n\t\t\tconst channel = ch(channelPrefix, \"ns:update\");\n\t\t\tconst handler = (_: unknown, snap: Snapshot<unknown>) => cb(snap);\n\t\t\tipcRenderer.on(channel, handler);\n\t\t\treturn () => ipcRenderer.removeListener(channel, handler);\n\t\t},\n\n\t\t// Service RPC call\n\t\tcallService: async <T = unknown>(\n\t\t\tserviceId: string,\n\t\t\tmethod: string,\n\t\t\t...args: unknown[]\n\t\t): Promise<T> => {\n\t\t\tvalidateId(serviceId);\n\t\t\tvalidateId(method);\n\n\t\t\t// Validate all arguments\n\t\t\tfor (const arg of args) {\n\t\t\t\tvalidateIPCValue(arg);\n\t\t\t}\n\n\t\t\tconst channel = ch(channelPrefix, \"svc:call\");\n\t\t\tconst result: ServiceCallResult = await ipcRenderer.invoke(\n\t\t\t\tchannel,\n\t\t\t\tserviceId,\n\t\t\t\tmethod,\n\t\t\t\targs,\n\t\t\t);\n\n\t\t\tif (!result.success) {\n\t\t\t\t// Reconstruct error from Main\n\t\t\t\tconst error = new Error(result.error?.message || \"Service call failed\");\n\t\t\t\terror.name = result.error?.code || \"ServiceError\";\n\t\t\t\tif (result.error?.stack) {\n\t\t\t\t\terror.stack = result.error.stack;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\treturn result.result as T;\n\t\t},\n\n\t\t// Subscribe to service events\n\t\tsubscribeServiceEvent: (\n\t\t\tserviceId: string,\n\t\t\teventName: string,\n\t\t\tcb: (data: unknown) => void,\n\t\t) => {\n\t\t\tvalidateId(serviceId);\n\t\t\tvalidateId(eventName);\n\t\t\tif (typeof cb !== \"function\") {\n\t\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t\t}\n\t\t\tconst channel = ch(channelPrefix, \"svc:event\");\n\t\t\tconst handler = (\n\t\t\t\t_: unknown,\n\t\t\t\tpayload: { serviceId: string; eventName: string; data: unknown },\n\t\t\t) => {\n\t\t\t\tif (payload.serviceId !== serviceId || payload.eventName !== eventName)\n\t\t\t\t\treturn;\n\t\t\t\tcb(payload.data);\n\t\t\t};\n\t\t\tipcRenderer.on(channel, handler);\n\t\t\treturn () => ipcRenderer.removeListener(channel, handler);\n\t\t},\n\t};\n\n\tcontextBridge.exposeInMainWorld(globalName, api);\n\treturn api;\n}\n"]}
@@ -0,0 +1,120 @@
1
+ import { contextBridge, ipcRenderer } from 'electron';
2
+
3
+ // src/preload/exposeNanoStoreIPC.ts
4
+ function ch(prefix, c) {
5
+ return prefix ? `${prefix}:${c}` : c;
6
+ }
7
+ function validateIPCValue(value) {
8
+ if (value === null || value === void 0) return;
9
+ const type = typeof value;
10
+ if (type === "string" || type === "number" || type === "boolean") return;
11
+ if (type === "function" || type === "symbol") {
12
+ throw new TypeError(`Cannot serialize ${type} values via IPC`);
13
+ }
14
+ if (type === "object") {
15
+ const proto = Object.getPrototypeOf(value);
16
+ if (proto !== Object.prototype && proto !== Array.prototype && proto !== null) {
17
+ if (!(value instanceof Date || value instanceof RegExp || value instanceof Error)) {
18
+ throw new TypeError("Cannot serialize class instances via IPC");
19
+ }
20
+ }
21
+ if ("__proto__" in value || "constructor" in value || "prototype" in value) {
22
+ const obj = value;
23
+ const proto2 = Object.getPrototypeOf(obj);
24
+ if (proto2 !== Object.prototype && proto2 !== Array.prototype && proto2 !== null) {
25
+ throw new TypeError("Potentially unsafe object structure");
26
+ }
27
+ }
28
+ }
29
+ }
30
+ function validateId(id) {
31
+ if (typeof id !== "string" || id.length === 0) {
32
+ throw new TypeError("ID must be a non-empty string");
33
+ }
34
+ if (id.length > 256) {
35
+ throw new TypeError("ID too long (max 256 characters)");
36
+ }
37
+ }
38
+ function exposeNanoStoreIPC(opts = {}) {
39
+ const channelPrefix = opts.channelPrefix ?? "";
40
+ const globalName = opts.globalName ?? "nanostoreIPC";
41
+ const api = {
42
+ get: (id) => {
43
+ validateId(id);
44
+ return ipcRenderer.invoke(ch(channelPrefix, "ns:get"), id);
45
+ },
46
+ set: (id, value) => {
47
+ validateId(id);
48
+ validateIPCValue(value);
49
+ return ipcRenderer.invoke(ch(channelPrefix, "ns:set"), id, value);
50
+ },
51
+ subscribe: (id, cb) => {
52
+ validateId(id);
53
+ if (typeof cb !== "function") {
54
+ throw new TypeError("Callback must be a function");
55
+ }
56
+ const channel = ch(channelPrefix, "ns:update");
57
+ const handler = (_, snap) => {
58
+ if (snap.id !== id) return;
59
+ cb(snap);
60
+ };
61
+ ipcRenderer.on(channel, handler);
62
+ return () => ipcRenderer.removeListener(channel, handler);
63
+ },
64
+ subscribeAll: (cb) => {
65
+ if (typeof cb !== "function") {
66
+ throw new TypeError("Callback must be a function");
67
+ }
68
+ const channel = ch(channelPrefix, "ns:update");
69
+ const handler = (_, snap) => cb(snap);
70
+ ipcRenderer.on(channel, handler);
71
+ return () => ipcRenderer.removeListener(channel, handler);
72
+ },
73
+ // Service RPC call
74
+ callService: async (serviceId, method, ...args) => {
75
+ validateId(serviceId);
76
+ validateId(method);
77
+ for (const arg of args) {
78
+ validateIPCValue(arg);
79
+ }
80
+ const channel = ch(channelPrefix, "svc:call");
81
+ const result = await ipcRenderer.invoke(
82
+ channel,
83
+ serviceId,
84
+ method,
85
+ args
86
+ );
87
+ if (!result.success) {
88
+ const error = new Error(result.error?.message || "Service call failed");
89
+ error.name = result.error?.code || "ServiceError";
90
+ if (result.error?.stack) {
91
+ error.stack = result.error.stack;
92
+ }
93
+ throw error;
94
+ }
95
+ return result.result;
96
+ },
97
+ // Subscribe to service events
98
+ subscribeServiceEvent: (serviceId, eventName, cb) => {
99
+ validateId(serviceId);
100
+ validateId(eventName);
101
+ if (typeof cb !== "function") {
102
+ throw new TypeError("Callback must be a function");
103
+ }
104
+ const channel = ch(channelPrefix, "svc:event");
105
+ const handler = (_, payload) => {
106
+ if (payload.serviceId !== serviceId || payload.eventName !== eventName)
107
+ return;
108
+ cb(payload.data);
109
+ };
110
+ ipcRenderer.on(channel, handler);
111
+ return () => ipcRenderer.removeListener(channel, handler);
112
+ }
113
+ };
114
+ contextBridge.exposeInMainWorld(globalName, api);
115
+ return api;
116
+ }
117
+
118
+ export { exposeNanoStoreIPC };
119
+ //# sourceMappingURL=index.mjs.map
120
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/preload/exposeNanoStoreIPC.ts"],"names":["proto"],"mappings":"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","file":"index.mjs","sourcesContent":["import { contextBridge, ipcRenderer } from \"electron\";\nimport type { ServiceCallResult, Snapshot } from \"../internal/types\";\n\nexport interface ExposeNanoStoreIPCOptions {\n\tchannelPrefix?: string;\n\t/**\n\t * Name under which the API is exposed to window.\n\t * Default: \"nanostoreIPC\"\n\t */\n\tglobalName?: string;\n}\n\nexport type NanoStoreIPC = {\n\tget: <T = unknown>(id: string) => Promise<Snapshot<T>>;\n\tset: <T = unknown>(id: string, value: T) => Promise<void>;\n\tsubscribe: <T = unknown>(\n\t\tid: string,\n\t\tcb: (snap: Snapshot<T>) => void,\n\t) => () => void;\n\tsubscribeAll: (cb: (snap: Snapshot<unknown>) => void) => () => void;\n\t// Services\n\tcallService: <T = unknown>(\n\t\tserviceId: string,\n\t\tmethod: string,\n\t\t...args: unknown[]\n\t) => Promise<T>;\n\tsubscribeServiceEvent: (\n\t\tserviceId: string,\n\t\teventName: string,\n\t\tcb: (data: unknown) => void,\n\t) => () => void;\n};\n\nfunction ch(prefix: string, c: string) {\n\treturn prefix ? `${prefix}:${c}` : c;\n}\n\n/**\n * Validates that a value is safe to send via IPC.\n * Checks for basic serializability and prevents prototype pollution.\n */\nfunction validateIPCValue(value: unknown): void {\n\tif (value === null || value === undefined) return;\n\n\tconst type = typeof value;\n\n\t// Primitives are always safe\n\tif (type === \"string\" || type === \"number\" || type === \"boolean\") return;\n\n\t// Functions, symbols, and undefined are not serializable\n\tif (type === \"function\" || type === \"symbol\") {\n\t\tthrow new TypeError(`Cannot serialize ${type} values via IPC`);\n\t}\n\n\t// Check objects and arrays recursively (with depth limit)\n\tif (type === \"object\") {\n\t\t// Reject dangerous constructors\n\t\tconst proto = Object.getPrototypeOf(value);\n\t\tif (\n\t\t\tproto !== Object.prototype &&\n\t\t\tproto !== Array.prototype &&\n\t\t\tproto !== null\n\t\t) {\n\t\t\t// Allow Date, RegExp, Error which are structured-cloneable\n\t\t\tif (\n\t\t\t\t!(\n\t\t\t\t\tvalue instanceof Date ||\n\t\t\t\t\tvalue instanceof RegExp ||\n\t\t\t\t\tvalue instanceof Error\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tthrow new TypeError(\"Cannot serialize class instances via IPC\");\n\t\t\t}\n\t\t}\n\n\t\t// Check for prototype pollution attempts\n\t\tif (\n\t\t\t\"__proto__\" in (value as object) ||\n\t\t\t\"constructor\" in (value as object) ||\n\t\t\t\"prototype\" in (value as object)\n\t\t) {\n\t\t\tconst obj = value as Record<string, unknown>;\n\t\t\t// Use Object.getPrototypeOf instead of deprecated __proto__\n\t\t\tconst proto = Object.getPrototypeOf(obj);\n\t\t\tif (\n\t\t\t\tproto !== Object.prototype &&\n\t\t\t\tproto !== Array.prototype &&\n\t\t\t\tproto !== null\n\t\t\t) {\n\t\t\t\tthrow new TypeError(\"Potentially unsafe object structure\");\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Validates string IDs to prevent injection attacks\n */\nfunction validateId(id: string): void {\n\tif (typeof id !== \"string\" || id.length === 0) {\n\t\tthrow new TypeError(\"ID must be a non-empty string\");\n\t}\n\tif (id.length > 256) {\n\t\tthrow new TypeError(\"ID too long (max 256 characters)\");\n\t}\n}\n\nexport function exposeNanoStoreIPC(opts: ExposeNanoStoreIPCOptions = {}) {\n\tconst channelPrefix = opts.channelPrefix ?? \"\";\n\tconst globalName = opts.globalName ?? \"nanostoreIPC\";\n\n\tconst api: NanoStoreIPC = {\n\t\tget: (id) => {\n\t\t\tvalidateId(id);\n\t\t\treturn ipcRenderer.invoke(ch(channelPrefix, \"ns:get\"), id);\n\t\t},\n\t\tset: (id, value) => {\n\t\t\tvalidateId(id);\n\t\t\tvalidateIPCValue(value);\n\t\t\treturn ipcRenderer.invoke(ch(channelPrefix, \"ns:set\"), id, value);\n\t\t},\n\t\tsubscribe: <T>(id: string, cb: (snap: Snapshot<T>) => void) => {\n\t\t\tvalidateId(id);\n\t\t\tif (typeof cb !== \"function\") {\n\t\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t\t}\n\t\t\tconst channel = ch(channelPrefix, \"ns:update\");\n\t\t\tconst handler = (_: unknown, snap: Snapshot<unknown>) => {\n\t\t\t\tif (snap.id !== id) return;\n\t\t\t\tcb(snap as Snapshot<T>);\n\t\t\t};\n\t\t\tipcRenderer.on(channel, handler);\n\t\t\treturn () => ipcRenderer.removeListener(channel, handler);\n\t\t},\n\t\tsubscribeAll: (cb) => {\n\t\t\tif (typeof cb !== \"function\") {\n\t\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t\t}\n\t\t\tconst channel = ch(channelPrefix, \"ns:update\");\n\t\t\tconst handler = (_: unknown, snap: Snapshot<unknown>) => cb(snap);\n\t\t\tipcRenderer.on(channel, handler);\n\t\t\treturn () => ipcRenderer.removeListener(channel, handler);\n\t\t},\n\n\t\t// Service RPC call\n\t\tcallService: async <T = unknown>(\n\t\t\tserviceId: string,\n\t\t\tmethod: string,\n\t\t\t...args: unknown[]\n\t\t): Promise<T> => {\n\t\t\tvalidateId(serviceId);\n\t\t\tvalidateId(method);\n\n\t\t\t// Validate all arguments\n\t\t\tfor (const arg of args) {\n\t\t\t\tvalidateIPCValue(arg);\n\t\t\t}\n\n\t\t\tconst channel = ch(channelPrefix, \"svc:call\");\n\t\t\tconst result: ServiceCallResult = await ipcRenderer.invoke(\n\t\t\t\tchannel,\n\t\t\t\tserviceId,\n\t\t\t\tmethod,\n\t\t\t\targs,\n\t\t\t);\n\n\t\t\tif (!result.success) {\n\t\t\t\t// Reconstruct error from Main\n\t\t\t\tconst error = new Error(result.error?.message || \"Service call failed\");\n\t\t\t\terror.name = result.error?.code || \"ServiceError\";\n\t\t\t\tif (result.error?.stack) {\n\t\t\t\t\terror.stack = result.error.stack;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\treturn result.result as T;\n\t\t},\n\n\t\t// Subscribe to service events\n\t\tsubscribeServiceEvent: (\n\t\t\tserviceId: string,\n\t\t\teventName: string,\n\t\t\tcb: (data: unknown) => void,\n\t\t) => {\n\t\t\tvalidateId(serviceId);\n\t\t\tvalidateId(eventName);\n\t\t\tif (typeof cb !== \"function\") {\n\t\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t\t}\n\t\t\tconst channel = ch(channelPrefix, \"svc:event\");\n\t\t\tconst handler = (\n\t\t\t\t_: unknown,\n\t\t\t\tpayload: { serviceId: string; eventName: string; data: unknown },\n\t\t\t) => {\n\t\t\t\tif (payload.serviceId !== serviceId || payload.eventName !== eventName)\n\t\t\t\t\treturn;\n\t\t\t\tcb(payload.data);\n\t\t\t};\n\t\t\tipcRenderer.on(channel, handler);\n\t\t\treturn () => ipcRenderer.removeListener(channel, handler);\n\t\t},\n\t};\n\n\tcontextBridge.exposeInMainWorld(globalName, api);\n\treturn api;\n}\n"]}
@@ -0,0 +1,25 @@
1
+ type Snapshot<T> = {
2
+ id: string;
3
+ rev: number;
4
+ value: T;
5
+ };
6
+ type ErrorHandler = (error: NanoStoreIPCError) => void;
7
+ declare class NanoStoreIPCError extends Error {
8
+ code: "STORE_NOT_FOUND" | "RENDERER_WRITE_DISABLED" | "SERIALIZATION_FAILED" | "IPC_FAILED" | "SERVICE_NOT_FOUND" | "SERVICE_METHOD_NOT_FOUND" | "ALREADY_INITIALIZED";
9
+ storeId?: string | undefined;
10
+ originalError?: unknown | undefined;
11
+ constructor(message: string, code: "STORE_NOT_FOUND" | "RENDERER_WRITE_DISABLED" | "SERIALIZATION_FAILED" | "IPC_FAILED" | "SERVICE_NOT_FOUND" | "SERVICE_METHOD_NOT_FOUND" | "ALREADY_INITIALIZED", storeId?: string | undefined, originalError?: unknown | undefined);
12
+ }
13
+ type ServiceHandler = (...args: unknown[]) => Promise<unknown>;
14
+ type ServiceHandlers = Record<string, ServiceHandler>;
15
+ type ServiceHooks = {
16
+ beforeAll?: (methodName: string, args: unknown[]) => void | Promise<void>;
17
+ afterAll?: (methodName: string, result: unknown, duration: number) => void | Promise<void>;
18
+ };
19
+ type ServiceDefinition<T extends ServiceHandlers = ServiceHandlers> = {
20
+ handlers: T;
21
+ beforeAll?: (methodName: string, args: unknown[]) => void | Promise<void>;
22
+ afterAll?: (methodName: string, result: unknown, duration: number) => void | Promise<void>;
23
+ };
24
+
25
+ export { type ErrorHandler as E, NanoStoreIPCError as N, type Snapshot as S, type ServiceHandlers as a, type ServiceDefinition as b, type ServiceHooks as c };
@@ -0,0 +1,25 @@
1
+ type Snapshot<T> = {
2
+ id: string;
3
+ rev: number;
4
+ value: T;
5
+ };
6
+ type ErrorHandler = (error: NanoStoreIPCError) => void;
7
+ declare class NanoStoreIPCError extends Error {
8
+ code: "STORE_NOT_FOUND" | "RENDERER_WRITE_DISABLED" | "SERIALIZATION_FAILED" | "IPC_FAILED" | "SERVICE_NOT_FOUND" | "SERVICE_METHOD_NOT_FOUND" | "ALREADY_INITIALIZED";
9
+ storeId?: string | undefined;
10
+ originalError?: unknown | undefined;
11
+ constructor(message: string, code: "STORE_NOT_FOUND" | "RENDERER_WRITE_DISABLED" | "SERIALIZATION_FAILED" | "IPC_FAILED" | "SERVICE_NOT_FOUND" | "SERVICE_METHOD_NOT_FOUND" | "ALREADY_INITIALIZED", storeId?: string | undefined, originalError?: unknown | undefined);
12
+ }
13
+ type ServiceHandler = (...args: unknown[]) => Promise<unknown>;
14
+ type ServiceHandlers = Record<string, ServiceHandler>;
15
+ type ServiceHooks = {
16
+ beforeAll?: (methodName: string, args: unknown[]) => void | Promise<void>;
17
+ afterAll?: (methodName: string, result: unknown, duration: number) => void | Promise<void>;
18
+ };
19
+ type ServiceDefinition<T extends ServiceHandlers = ServiceHandlers> = {
20
+ handlers: T;
21
+ beforeAll?: (methodName: string, args: unknown[]) => void | Promise<void>;
22
+ afterAll?: (methodName: string, result: unknown, duration: number) => void | Promise<void>;
23
+ };
24
+
25
+ export { type ErrorHandler as E, NanoStoreIPCError as N, type Snapshot as S, type ServiceHandlers as a, type ServiceDefinition as b, type ServiceHooks as c };
@@ -0,0 +1,124 @@
1
+ import { a as ServiceHandlers, c as ServiceHooks, E as ErrorHandler } from '../types-CyJJt8gf.mjs';
2
+ import * as nanostores from 'nanostores';
3
+ import { WritableAtom } from 'nanostores';
4
+
5
+ interface DefineServiceOptions<THandlers extends ServiceHandlers> {
6
+ /**
7
+ * Service identifier - must be unique across your app
8
+ */
9
+ id: string;
10
+ /**
11
+ * Service method handlers
12
+ * Main: executed locally
13
+ * Renderer: creates RPC proxy
14
+ */
15
+ handlers: THandlers;
16
+ /**
17
+ * Optional middleware hooks for Main process only
18
+ */
19
+ hooks?: ServiceHooks;
20
+ /**
21
+ * Window global name used by preload exposure.
22
+ * Default: "nanostoreIPC"
23
+ */
24
+ globalName?: string;
25
+ }
26
+ type ServiceProxy<THandlers extends ServiceHandlers> = {
27
+ [K in keyof THandlers]: THandlers[K] extends (...args: infer Args) => Promise<infer Return> ? (...args: Args) => Promise<Return> : never;
28
+ } & {
29
+ /**
30
+ * Subscribe to service events (Renderer only)
31
+ */
32
+ on: (eventName: string, callback: (data: unknown) => void) => () => void;
33
+ /**
34
+ * Broadcast an event to all renderer windows (Main only)
35
+ */
36
+ broadcast: (eventName: string, data?: unknown) => void;
37
+ };
38
+ /**
39
+ * Define a service that works in both Main and Renderer processes.
40
+ *
41
+ * **Main Process:**
42
+ * - Registers handlers to be called via IPC
43
+ * - Returns proxy with broadcast() method for sending events
44
+ * - Handlers execute locally
45
+ *
46
+ * **Renderer Process:**
47
+ * - Returns RPC proxy that calls Main process via IPC
48
+ * - Returns proxy with on() method for listening to events
49
+ * - All methods are async and execute remotely
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * // shared/services/todoService.ts
54
+ * export const todoService = defineService({
55
+ * id: 'todos',
56
+ * handlers: {
57
+ * async addTodo(text: string) {
58
+ * const todo = { id: Date.now(), text };
59
+ * todos.push(todo);
60
+ * todoService.broadcast('todoAdded', todo); // Main only
61
+ * return todo;
62
+ * },
63
+ * async getTodos() {
64
+ * return todos;
65
+ * }
66
+ * }
67
+ * });
68
+ *
69
+ * // Main process (electron/main.ts)
70
+ * import './shared/services/todoService'; // Auto-registers
71
+ *
72
+ * // Renderer (React component)
73
+ * const todo = await todoService.addTodo('Buy milk'); // RPC call
74
+ * todoService.on('todoAdded', (todo) => {
75
+ * console.log('New todo:', todo);
76
+ * });
77
+ * ```
78
+ */
79
+ declare function defineService<THandlers extends ServiceHandlers>(options: DefineServiceOptions<THandlers>): ServiceProxy<THandlers>;
80
+
81
+ interface SyncedAtomOptions<T> {
82
+ /**
83
+ * If true, renderer writes are blocked even if main allows writes.
84
+ * Useful to force "actions only" mutability later without breaking API.
85
+ */
86
+ rendererCanSet?: boolean;
87
+ /**
88
+ * Optional: warn when IPC is not available (e.g. during SSR/tests).
89
+ */
90
+ warnIfNoIPC?: boolean;
91
+ /**
92
+ * Optional: channel prefix to match init/expose
93
+ * If you set it here, you must also pass it to expose/init.
94
+ * If omitted, uses unprefixed channels.
95
+ */
96
+ channelPrefix?: string;
97
+ /**
98
+ * Window global name used by preload exposure.
99
+ * Default: "nanostoreIPC"
100
+ */
101
+ globalName?: string;
102
+ /**
103
+ * Error handler called when errors occur in IPC operations.
104
+ */
105
+ onError?: ErrorHandler;
106
+ /**
107
+ * Optional value validator. Return false to reject the value.
108
+ * Can be used for runtime validation (e.g., with Zod).
109
+ */
110
+ validateValue?: (value: T) => boolean | Promise<boolean>;
111
+ }
112
+ /**
113
+ * syncedAtom(id, initial):
114
+ * - In Electron Main: creates a real atom and registers it for IPC broadcast.
115
+ * - In Electron Renderer: creates a proxy atom and syncs it via preload-exposed IPC API.
116
+ * - Outside Electron: behaves as a normal atom(initial).
117
+ *
118
+ * No central "bridge definition" required; ID is the single piece of shared contract.
119
+ */
120
+ declare function syncedAtom<T>(id: string, initial: T, options?: SyncedAtomOptions<T>): (nanostores.PreinitializedWritableAtom<T> & object) | (WritableAtom<T> & {
121
+ destroy: () => void;
122
+ });
123
+
124
+ export { type DefineServiceOptions, type SyncedAtomOptions, defineService, syncedAtom };
@@ -0,0 +1,124 @@
1
+ import { a as ServiceHandlers, c as ServiceHooks, E as ErrorHandler } from '../types-CyJJt8gf.js';
2
+ import * as nanostores from 'nanostores';
3
+ import { WritableAtom } from 'nanostores';
4
+
5
+ interface DefineServiceOptions<THandlers extends ServiceHandlers> {
6
+ /**
7
+ * Service identifier - must be unique across your app
8
+ */
9
+ id: string;
10
+ /**
11
+ * Service method handlers
12
+ * Main: executed locally
13
+ * Renderer: creates RPC proxy
14
+ */
15
+ handlers: THandlers;
16
+ /**
17
+ * Optional middleware hooks for Main process only
18
+ */
19
+ hooks?: ServiceHooks;
20
+ /**
21
+ * Window global name used by preload exposure.
22
+ * Default: "nanostoreIPC"
23
+ */
24
+ globalName?: string;
25
+ }
26
+ type ServiceProxy<THandlers extends ServiceHandlers> = {
27
+ [K in keyof THandlers]: THandlers[K] extends (...args: infer Args) => Promise<infer Return> ? (...args: Args) => Promise<Return> : never;
28
+ } & {
29
+ /**
30
+ * Subscribe to service events (Renderer only)
31
+ */
32
+ on: (eventName: string, callback: (data: unknown) => void) => () => void;
33
+ /**
34
+ * Broadcast an event to all renderer windows (Main only)
35
+ */
36
+ broadcast: (eventName: string, data?: unknown) => void;
37
+ };
38
+ /**
39
+ * Define a service that works in both Main and Renderer processes.
40
+ *
41
+ * **Main Process:**
42
+ * - Registers handlers to be called via IPC
43
+ * - Returns proxy with broadcast() method for sending events
44
+ * - Handlers execute locally
45
+ *
46
+ * **Renderer Process:**
47
+ * - Returns RPC proxy that calls Main process via IPC
48
+ * - Returns proxy with on() method for listening to events
49
+ * - All methods are async and execute remotely
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * // shared/services/todoService.ts
54
+ * export const todoService = defineService({
55
+ * id: 'todos',
56
+ * handlers: {
57
+ * async addTodo(text: string) {
58
+ * const todo = { id: Date.now(), text };
59
+ * todos.push(todo);
60
+ * todoService.broadcast('todoAdded', todo); // Main only
61
+ * return todo;
62
+ * },
63
+ * async getTodos() {
64
+ * return todos;
65
+ * }
66
+ * }
67
+ * });
68
+ *
69
+ * // Main process (electron/main.ts)
70
+ * import './shared/services/todoService'; // Auto-registers
71
+ *
72
+ * // Renderer (React component)
73
+ * const todo = await todoService.addTodo('Buy milk'); // RPC call
74
+ * todoService.on('todoAdded', (todo) => {
75
+ * console.log('New todo:', todo);
76
+ * });
77
+ * ```
78
+ */
79
+ declare function defineService<THandlers extends ServiceHandlers>(options: DefineServiceOptions<THandlers>): ServiceProxy<THandlers>;
80
+
81
+ interface SyncedAtomOptions<T> {
82
+ /**
83
+ * If true, renderer writes are blocked even if main allows writes.
84
+ * Useful to force "actions only" mutability later without breaking API.
85
+ */
86
+ rendererCanSet?: boolean;
87
+ /**
88
+ * Optional: warn when IPC is not available (e.g. during SSR/tests).
89
+ */
90
+ warnIfNoIPC?: boolean;
91
+ /**
92
+ * Optional: channel prefix to match init/expose
93
+ * If you set it here, you must also pass it to expose/init.
94
+ * If omitted, uses unprefixed channels.
95
+ */
96
+ channelPrefix?: string;
97
+ /**
98
+ * Window global name used by preload exposure.
99
+ * Default: "nanostoreIPC"
100
+ */
101
+ globalName?: string;
102
+ /**
103
+ * Error handler called when errors occur in IPC operations.
104
+ */
105
+ onError?: ErrorHandler;
106
+ /**
107
+ * Optional value validator. Return false to reject the value.
108
+ * Can be used for runtime validation (e.g., with Zod).
109
+ */
110
+ validateValue?: (value: T) => boolean | Promise<boolean>;
111
+ }
112
+ /**
113
+ * syncedAtom(id, initial):
114
+ * - In Electron Main: creates a real atom and registers it for IPC broadcast.
115
+ * - In Electron Renderer: creates a proxy atom and syncs it via preload-exposed IPC API.
116
+ * - Outside Electron: behaves as a normal atom(initial).
117
+ *
118
+ * No central "bridge definition" required; ID is the single piece of shared contract.
119
+ */
120
+ declare function syncedAtom<T>(id: string, initial: T, options?: SyncedAtomOptions<T>): (nanostores.PreinitializedWritableAtom<T> & object) | (WritableAtom<T> & {
121
+ destroy: () => void;
122
+ });
123
+
124
+ export { type DefineServiceOptions, type SyncedAtomOptions, defineService, syncedAtom };