electron-pinia-sync 1.0.0 → 1.2.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.
@@ -0,0 +1,153 @@
1
+ import { StateTree, Pinia, Store as Store$1 } from 'pinia';
2
+ import Store from 'electron-store';
3
+
4
+ /**
5
+ * Common types shared across Main, Renderer, and Preload processes
6
+ */
7
+
8
+ /**
9
+ * Debug level configuration
10
+ */
11
+ type DebugLevel$1 = boolean | 'verbose' | 'minimal';
12
+ /**
13
+ * Message sent from Main to Renderer when state is updated
14
+ */
15
+ interface StateUpdateMessage {
16
+ /** Store ID */
17
+ storeId: string;
18
+ /** New state */
19
+ state: StateTree;
20
+ /** Transaction ID that caused this update (if any) */
21
+ transactionId?: string;
22
+ }
23
+ /**
24
+ * Options for configuring store persistence
25
+ */
26
+ interface PersistOptions {
27
+ /** Whether to persist this store to disk */
28
+ enabled: boolean;
29
+ /** Optional custom key for electron-store (defaults to storeId) */
30
+ key?: string;
31
+ }
32
+ /**
33
+ * API exposed to Renderer via contextBridge
34
+ */
35
+ interface PiniaSyncAPI {
36
+ /**
37
+ * Pull initial state from Main process
38
+ */
39
+ pullState: (storeId: string) => Promise<StateTree | null>;
40
+ /**
41
+ * Send state patch to Main process
42
+ */
43
+ patchState: (storeId: string, patch: Partial<StateTree>, transactionId: string) => Promise<void>;
44
+ /**
45
+ * Subscribe to state updates from Main process
46
+ */
47
+ onStateUpdate: (callback: (message: StateUpdateMessage) => void) => () => void;
48
+ }
49
+ declare global {
50
+ interface Window {
51
+ piniaSync?: PiniaSyncAPI;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Debug utilities for electron-pinia-sync
57
+ *
58
+ * Enable debug logging programmatically:
59
+ * - { debug: true } - enable debug logging
60
+ * - { debug: 'verbose' } - enable verbose logging with state diffs
61
+ * - { debug: 'minimal' } - only log errors and warnings
62
+ */
63
+ type DebugLevel = boolean | 'verbose' | 'minimal';
64
+ interface DebugLogger {
65
+ log: (message: string, ...args: unknown[]) => void;
66
+ warn: (message: string, ...args: unknown[]) => void;
67
+ error: (message: string, ...args: unknown[]) => void;
68
+ debug: (message: string, ...args: unknown[]) => void;
69
+ verbose: (message: string, ...args: unknown[]) => void;
70
+ }
71
+ /**
72
+ * Create a debug logger for a specific namespace
73
+ */
74
+ declare function createDebugLogger(namespace: string, debugLevel?: DebugLevel, customLogger?: Partial<DebugLogger>): DebugLogger;
75
+ /**
76
+ * Format state for debug output (truncate large objects)
77
+ */
78
+ declare function formatStateForDebug(state: unknown, maxLength?: number): string;
79
+ /**
80
+ * Format patch for debug output
81
+ */
82
+ declare function formatPatchForDebug(patch: unknown): string;
83
+
84
+ interface MainSyncOptions {
85
+ /**
86
+ * electron-store configuration options
87
+ */
88
+ storeOptions?: ConstructorParameters<typeof Store<Record<string, StateTree>>>[0];
89
+ /**
90
+ * Custom Pinia instance (optional, will create one if not provided)
91
+ */
92
+ pinia?: Pinia;
93
+ /**
94
+ * Enable debug logging
95
+ * - true: enable debug logging
96
+ * - 'verbose': enable verbose logging with state diffs
97
+ * - 'minimal': only log errors and warnings
98
+ * - false: disable debug logging (default)
99
+ */
100
+ debug?: DebugLevel$1;
101
+ /**
102
+ * Custom logger (for testing or custom logging)
103
+ */
104
+ logger?: Partial<DebugLogger>;
105
+ }
106
+
107
+ /**
108
+ * Main process synchronization manager
109
+ * Manages Pinia stores in the Main process and handles IPC communication
110
+ */
111
+
112
+ declare class MainSync {
113
+ private pinia;
114
+ private electronStore;
115
+ private storeMetadata;
116
+ private processingTransactions;
117
+ private readonly MAX_TRANSACTION_HISTORY;
118
+ private debug;
119
+ constructor(options?: MainSyncOptions);
120
+ /**
121
+ * Get the Pinia instance managed by this sync manager
122
+ */
123
+ getPinia(): Pinia;
124
+ /**
125
+ * Register a store with the sync manager
126
+ */
127
+ registerStore(storeId: string, store: Store$1, options?: {
128
+ persist?: boolean | PersistOptions;
129
+ }): void;
130
+ /**
131
+ * Normalize persist options to standard format
132
+ */
133
+ private normalizePersistOptions;
134
+ /**
135
+ * Setup IPC handlers for communication with renderers
136
+ */
137
+ private setupIpcHandlers;
138
+ private addTransaction;
139
+ /**
140
+ * Broadcast state update to all renderer processes
141
+ */
142
+ private broadcastStateUpdate;
143
+ /**
144
+ * Cleanup IPC handlers
145
+ */
146
+ destroy(): void;
147
+ }
148
+ /**
149
+ * Create and initialize the Main process sync manager
150
+ */
151
+ declare function createMainSync(options?: MainSyncOptions): MainSync;
152
+
153
+ export { type DebugLevel, type DebugLogger, MainSync, createDebugLogger, createMainSync, formatPatchForDebug, formatStateForDebug };
@@ -1,8 +1,7 @@
1
- import { ipcMain, BrowserWindow } from 'electron';
2
- import { createPinia } from 'pinia';
3
- import Store from 'electron-store';
4
-
5
1
  // src/main/index.ts
2
+ import { BrowserWindow, ipcMain } from "electron";
3
+ import { createPinia } from "pinia";
4
+ import Store from "electron-store";
6
5
 
7
6
  // src/types.ts
8
7
  var IPC_CHANNELS = {
@@ -11,7 +10,10 @@ var IPC_CHANNELS = {
11
10
  /** Renderer sends patch to Main */
12
11
  STATE_PATCH: "pinia-sync:state-patch",
13
12
  /** Main broadcasts state update to all Renderers */
14
- STATE_UPDATED: "pinia-sync:state-updated"};
13
+ STATE_UPDATED: "pinia-sync:state-updated",
14
+ /** Renderer requests full state sync */
15
+ STATE_SYNC: "pinia-sync:state-sync"
16
+ };
15
17
 
16
18
  // src/debug.ts
17
19
  function createDebugLogger(namespace, debugLevel = false, customLogger) {
@@ -216,6 +218,10 @@ var MainSync = class {
216
218
  function createMainSync(options) {
217
219
  return new MainSync(options);
218
220
  }
219
- console.log("main sync plugin loaded");
220
-
221
- export { MainSync, createDebugLogger, createMainSync, formatPatchForDebug, formatStateForDebug };
221
+ export {
222
+ MainSync,
223
+ createDebugLogger,
224
+ createMainSync,
225
+ formatPatchForDebug,
226
+ formatStateForDebug
227
+ };
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ // src/preload/index.ts
4
+ var import_electron = require("electron");
5
+
6
+ // src/types.ts
7
+ var IPC_CHANNELS = {
8
+ /** Renderer requests initial state from Main */
9
+ STATE_PULL: "pinia-sync:state-pull",
10
+ /** Renderer sends patch to Main */
11
+ STATE_PATCH: "pinia-sync:state-patch",
12
+ /** Main broadcasts state update to all Renderers */
13
+ STATE_UPDATED: "pinia-sync:state-updated",
14
+ /** Renderer requests full state sync */
15
+ STATE_SYNC: "pinia-sync:state-sync"
16
+ };
17
+
18
+ // src/preload/index.ts
19
+ var piniaSyncAPI = {
20
+ /**
21
+ * Pull initial state from Main process
22
+ */
23
+ async pullState(storeId) {
24
+ console.log(`[preload] IPC invoke: ${IPC_CHANNELS.STATE_PULL} for store "${storeId}"`);
25
+ const request = { storeId };
26
+ const response = await import_electron.ipcRenderer.invoke(
27
+ IPC_CHANNELS.STATE_PULL,
28
+ request
29
+ );
30
+ console.log(`[preload] IPC response: STATE_PULL for "${storeId}" - state:`, response.state ? "received" : "null");
31
+ return response.state;
32
+ },
33
+ /**
34
+ * Send state patch to Main process
35
+ */
36
+ async patchState(storeId, patch, transactionId) {
37
+ console.log(`[preload] IPC invoke: ${IPC_CHANNELS.STATE_PATCH} for store "${storeId}", transaction: ${transactionId}`);
38
+ const message = {
39
+ storeId,
40
+ patch,
41
+ transactionId
42
+ };
43
+ await import_electron.ipcRenderer.invoke(IPC_CHANNELS.STATE_PATCH, message);
44
+ console.log(`[preload] IPC response: STATE_PATCH for "${storeId}" completed`);
45
+ },
46
+ /**
47
+ * Subscribe to state updates from Main process
48
+ */
49
+ onStateUpdate(callback) {
50
+ console.log(`[preload] IPC listener registered: ${IPC_CHANNELS.STATE_UPDATED}`);
51
+ const listener = (_event, message) => {
52
+ console.log(`[preload] IPC event received: ${IPC_CHANNELS.STATE_UPDATED} for store "${message.storeId}", transaction: ${message.transactionId || "none"}`);
53
+ callback(message);
54
+ };
55
+ import_electron.ipcRenderer.on(IPC_CHANNELS.STATE_UPDATED, listener);
56
+ return () => {
57
+ console.log(`[preload] IPC listener removed: ${IPC_CHANNELS.STATE_UPDATED}`);
58
+ import_electron.ipcRenderer.removeListener(IPC_CHANNELS.STATE_UPDATED, listener);
59
+ };
60
+ }
61
+ };
62
+ import_electron.contextBridge.exposeInMainWorld("piniaSync", piniaSyncAPI);
63
+ console.log("[preload] piniaSync API exposed to window");
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -1,6 +1,5 @@
1
- import { contextBridge, ipcRenderer } from 'electron';
2
-
3
1
  // src/preload/index.ts
2
+ import { contextBridge, ipcRenderer } from "electron";
4
3
 
5
4
  // src/types.ts
6
5
  var IPC_CHANNELS = {
@@ -9,7 +8,10 @@ var IPC_CHANNELS = {
9
8
  /** Renderer sends patch to Main */
10
9
  STATE_PATCH: "pinia-sync:state-patch",
11
10
  /** Main broadcasts state update to all Renderers */
12
- STATE_UPDATED: "pinia-sync:state-updated"};
11
+ STATE_UPDATED: "pinia-sync:state-updated",
12
+ /** Renderer requests full state sync */
13
+ STATE_SYNC: "pinia-sync:state-sync"
14
+ };
13
15
 
14
16
  // src/preload/index.ts
15
17
  var piniaSyncAPI = {
@@ -57,4 +59,3 @@ var piniaSyncAPI = {
57
59
  };
58
60
  contextBridge.exposeInMainWorld("piniaSync", piniaSyncAPI);
59
61
  console.log("[preload] piniaSync API exposed to window");
60
- console.log("[preload] electron-pinia-sync preload script initialized");
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/renderer/index.ts
21
+ var renderer_exports = {};
22
+ __export(renderer_exports, {
23
+ createDebugLogger: () => createDebugLogger,
24
+ createRendererSync: () => createRendererSync,
25
+ formatPatchForDebug: () => formatPatchForDebug,
26
+ formatStateForDebug: () => formatStateForDebug
27
+ });
28
+ module.exports = __toCommonJS(renderer_exports);
29
+
30
+ // node_modules/microdiff/dist/index.js
31
+ var richTypes = { Date: true, RegExp: true, String: true, Number: true };
32
+ function diff(obj, newObj, options = { cyclesFix: true }, _stack = []) {
33
+ let diffs = [];
34
+ const isObjArray = Array.isArray(obj);
35
+ for (const key in obj) {
36
+ const objKey = obj[key];
37
+ const path = isObjArray ? +key : key;
38
+ if (!(key in newObj)) {
39
+ diffs.push({
40
+ type: "REMOVE",
41
+ path: [path],
42
+ oldValue: obj[key]
43
+ });
44
+ continue;
45
+ }
46
+ const newObjKey = newObj[key];
47
+ const areCompatibleObjects = typeof objKey === "object" && typeof newObjKey === "object" && Array.isArray(objKey) === Array.isArray(newObjKey);
48
+ if (objKey && newObjKey && areCompatibleObjects && !richTypes[Object.getPrototypeOf(objKey)?.constructor?.name] && (!options.cyclesFix || !_stack.includes(objKey))) {
49
+ diffs.push.apply(diffs, diff(objKey, newObjKey, options, options.cyclesFix ? _stack.concat([objKey]) : []).map((difference) => {
50
+ difference.path.unshift(path);
51
+ return difference;
52
+ }));
53
+ } else if (objKey !== newObjKey && // treat NaN values as equivalent
54
+ !(Number.isNaN(objKey) && Number.isNaN(newObjKey)) && !(areCompatibleObjects && (isNaN(objKey) ? objKey + "" === newObjKey + "" : +objKey === +newObjKey))) {
55
+ diffs.push({
56
+ path: [path],
57
+ type: "CHANGE",
58
+ value: newObjKey,
59
+ oldValue: objKey
60
+ });
61
+ }
62
+ }
63
+ const isNewObjArray = Array.isArray(newObj);
64
+ for (const key in newObj) {
65
+ if (!(key in obj)) {
66
+ diffs.push({
67
+ type: "CREATE",
68
+ path: [isNewObjArray ? +key : key],
69
+ value: newObj[key]
70
+ });
71
+ }
72
+ }
73
+ return diffs;
74
+ }
75
+
76
+ // src/debug.ts
77
+ function createDebugLogger(namespace, debugLevel = false, customLogger) {
78
+ const isEnabled = debugLevel !== false;
79
+ const isVerbose = debugLevel === "verbose" || debugLevel === true;
80
+ const prefix = `[${namespace}]`;
81
+ const noop = () => {
82
+ };
83
+ const logger = customLogger || console;
84
+ return {
85
+ log: logger.log?.bind(logger, prefix) || console.log.bind(console, prefix),
86
+ warn: logger.warn?.bind(logger, prefix) || console.warn.bind(console, prefix),
87
+ error: logger.error?.bind(logger, prefix) || console.error.bind(console, prefix),
88
+ debug: isEnabled ? logger.log?.bind(logger, prefix) || console.log.bind(console, prefix) : noop,
89
+ verbose: isVerbose ? logger.log?.bind(logger, `${prefix}[VERBOSE]`) || console.log.bind(console, `${prefix}[VERBOSE]`) : noop
90
+ };
91
+ }
92
+ function formatStateForDebug(state, maxLength = 200) {
93
+ try {
94
+ const json = JSON.stringify(state);
95
+ if (json.length > maxLength) {
96
+ return json.substring(0, maxLength) + `... (${json.length} chars total)`;
97
+ }
98
+ return json;
99
+ } catch {
100
+ return "[Circular or non-serializable]";
101
+ }
102
+ }
103
+ function formatPatchForDebug(patch) {
104
+ try {
105
+ const keys = Object.keys(patch);
106
+ if (keys.length === 0) {
107
+ return "{}";
108
+ }
109
+ if (keys.length > 5) {
110
+ return `{ ${keys.slice(0, 5).join(", ")}, ... (${keys.length} keys) }`;
111
+ }
112
+ return JSON.stringify(patch, null, 2);
113
+ } catch {
114
+ return "[Invalid patch]";
115
+ }
116
+ }
117
+
118
+ // src/utils/toRawState.ts
119
+ function toRawState(state) {
120
+ return JSON.parse(JSON.stringify(state));
121
+ }
122
+
123
+ // src/renderer/index.ts
124
+ function generateTransactionId() {
125
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
126
+ }
127
+ function calculatePatch(oldState, newState, mutation) {
128
+ const differences = diff(oldState, newState);
129
+ if (differences.length === 0) {
130
+ return extractPatchFromMutation(mutation, newState);
131
+ }
132
+ const patch = {};
133
+ for (const change of differences) {
134
+ if (change.path.length === 0) {
135
+ return newState;
136
+ }
137
+ const topLevelKey = change.path[0];
138
+ if (typeof topLevelKey === "string" || typeof topLevelKey === "number") {
139
+ patch[topLevelKey] = newState[topLevelKey];
140
+ }
141
+ }
142
+ return Object.keys(patch).length > 0 ? patch : extractPatchFromMutation(mutation, newState);
143
+ }
144
+ function extractPatchFromMutation(mutation, state) {
145
+ if (mutation.type === "patch object") {
146
+ return mutation.payload;
147
+ } else if (mutation.type === "patch function") {
148
+ return state;
149
+ } else if (mutation.type === "direct") {
150
+ const patch = {};
151
+ const events = mutation.events;
152
+ if (events && Array.isArray(events) && events.length > 0) {
153
+ const firstEvent = events[0];
154
+ const key = firstEvent?.key;
155
+ if (typeof key === "string" && key in state) {
156
+ patch[key] = state[key];
157
+ return patch;
158
+ }
159
+ }
160
+ return state;
161
+ } else {
162
+ return state;
163
+ }
164
+ }
165
+ function createRendererSync(options = {}) {
166
+ const debug = createDebugLogger("electron-pinia-sync:renderer", options.debug ?? false, options.logger);
167
+ debug.debug("Initializing RendererSync");
168
+ let api = options.customApi;
169
+ if (!api) {
170
+ if (typeof window !== "undefined" && window.piniaSync) {
171
+ api = window.piniaSync;
172
+ } else if (typeof globalThis !== "undefined" && globalThis.window?.piniaSync) {
173
+ api = globalThis.window.piniaSync;
174
+ }
175
+ }
176
+ if (!api) {
177
+ debug.error("window.piniaSync is not available. Make sure the preload script is loaded correctly.");
178
+ throw new Error("Pinia sync API not available");
179
+ }
180
+ debug.debug("Pinia sync API found");
181
+ const processingTransactions = /* @__PURE__ */ new Set();
182
+ return function rendererSyncPlugin(context) {
183
+ const { store } = context;
184
+ debug.debug(`Initializing sync for store: ${store.$id}`);
185
+ let isApplyingRemoteUpdate = false;
186
+ let previousState = toRawState(store.$state);
187
+ const initializeState = async () => {
188
+ debug.debug(`Pulling initial state for store: ${store.$id}`);
189
+ try {
190
+ const state = await api.pullState(store.$id);
191
+ if (state !== null) {
192
+ debug.verbose(`Received initial state for ${store.$id}:`, formatStateForDebug(state));
193
+ isApplyingRemoteUpdate = true;
194
+ store.$patch(state);
195
+ previousState = toRawState(store.$state);
196
+ isApplyingRemoteUpdate = false;
197
+ debug.debug(`Successfully initialized state for store: ${store.$id}`);
198
+ } else {
199
+ debug.debug(`No initial state available for store: ${store.$id}`);
200
+ }
201
+ } catch (error) {
202
+ debug.error(`Failed to pull initial state for store "${store.$id}":`, error);
203
+ }
204
+ };
205
+ const subscribeToLocalChanges = () => {
206
+ debug.debug(`Subscribing to local changes for store: ${store.$id}`);
207
+ store.$subscribe((mutation, state) => {
208
+ if (isApplyingRemoteUpdate) {
209
+ debug.verbose(`Skipping sync for ${store.$id} (applying remote update)`);
210
+ return;
211
+ }
212
+ debug.verbose(`Local state changed for ${store.$id}, mutation type: ${mutation.type}`);
213
+ const patch = calculatePatch(previousState, state, mutation);
214
+ previousState = toRawState(state);
215
+ if (Object.keys(patch).length === 0) {
216
+ debug.verbose(`No changes detected for ${store.$id}, skipping sync`);
217
+ return;
218
+ }
219
+ debug.verbose(`Calculated patch for ${store.$id}:`, formatPatchForDebug(patch));
220
+ const transactionId = generateTransactionId();
221
+ debug.debug(`Syncing patch to Main (transaction: ${transactionId})`);
222
+ processingTransactions.add(transactionId);
223
+ const rawPatch = toRawState(patch);
224
+ api.patchState(store.$id, rawPatch, transactionId).catch((error) => {
225
+ debug.error(`Failed to sync state for store "${store.$id}":`, error);
226
+ }).finally(() => {
227
+ setTimeout(() => {
228
+ processingTransactions.delete(transactionId);
229
+ }, 100);
230
+ });
231
+ }, { detached: true });
232
+ };
233
+ const subscribeToRemoteUpdates = () => {
234
+ debug.debug(`Subscribing to remote updates for store: ${store.$id}`);
235
+ const unsubscribe = api.onStateUpdate((message) => {
236
+ if (message.storeId !== store.$id) {
237
+ return;
238
+ }
239
+ debug.verbose(`Received remote update for ${store.$id}`, message.transactionId ? `(transaction: ${message.transactionId})` : "");
240
+ if (message.transactionId && processingTransactions.has(message.transactionId)) {
241
+ debug.verbose(`Skipping echo update for ${store.$id} (transaction: ${message.transactionId})`);
242
+ return;
243
+ }
244
+ debug.verbose(`Applying remote state to ${store.$id}:`, formatStateForDebug(message.state));
245
+ isApplyingRemoteUpdate = true;
246
+ store.$patch(message.state);
247
+ previousState = toRawState(store.$state);
248
+ isApplyingRemoteUpdate = false;
249
+ debug.debug(`Successfully applied remote update to store: ${store.$id}`);
250
+ });
251
+ store._piniaSync_unsubscribe = unsubscribe;
252
+ };
253
+ debug.debug(`Initializing store synchronization for: ${store.$id}`);
254
+ initializeState();
255
+ subscribeToLocalChanges();
256
+ subscribeToRemoteUpdates();
257
+ debug.debug(`Store ${store.$id} sync setup complete`);
258
+ const originalDispose = store.$dispose.bind(store);
259
+ store.$dispose = () => {
260
+ debug.debug(`Disposing store: ${store.$id}`);
261
+ const unsubscribe = store._piniaSync_unsubscribe;
262
+ if (unsubscribe) {
263
+ unsubscribe();
264
+ }
265
+ originalDispose();
266
+ };
267
+ };
268
+ }
@@ -0,0 +1,123 @@
1
+ import { StateTree, PiniaPluginContext } from 'pinia';
2
+
3
+ /**
4
+ * Common types shared across Main, Renderer, and Preload processes
5
+ */
6
+
7
+ /**
8
+ * Debug level configuration
9
+ */
10
+ type DebugLevel$1 = boolean | 'verbose' | 'minimal';
11
+ /**
12
+ * Message sent from Main to Renderer when state is updated
13
+ */
14
+ interface StateUpdateMessage {
15
+ /** Store ID */
16
+ storeId: string;
17
+ /** New state */
18
+ state: StateTree;
19
+ /** Transaction ID that caused this update (if any) */
20
+ transactionId?: string;
21
+ }
22
+ /**
23
+ * Options for configuring store persistence
24
+ */
25
+ interface PersistOptions {
26
+ /** Whether to persist this store to disk */
27
+ enabled: boolean;
28
+ /** Optional custom key for electron-store (defaults to storeId) */
29
+ key?: string;
30
+ }
31
+ /**
32
+ * Extended Pinia store options with persistence support
33
+ */
34
+ interface SyncStoreOptions {
35
+ /** Persistence configuration */
36
+ persist?: boolean | PersistOptions;
37
+ }
38
+ /**
39
+ * API exposed to Renderer via contextBridge
40
+ */
41
+ interface PiniaSyncAPI {
42
+ /**
43
+ * Pull initial state from Main process
44
+ */
45
+ pullState: (storeId: string) => Promise<StateTree | null>;
46
+ /**
47
+ * Send state patch to Main process
48
+ */
49
+ patchState: (storeId: string, patch: Partial<StateTree>, transactionId: string) => Promise<void>;
50
+ /**
51
+ * Subscribe to state updates from Main process
52
+ */
53
+ onStateUpdate: (callback: (message: StateUpdateMessage) => void) => () => void;
54
+ }
55
+ declare global {
56
+ interface Window {
57
+ piniaSync?: PiniaSyncAPI;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Debug utilities for electron-pinia-sync
63
+ *
64
+ * Enable debug logging programmatically:
65
+ * - { debug: true } - enable debug logging
66
+ * - { debug: 'verbose' } - enable verbose logging with state diffs
67
+ * - { debug: 'minimal' } - only log errors and warnings
68
+ */
69
+ type DebugLevel = boolean | 'verbose' | 'minimal';
70
+ interface DebugLogger {
71
+ log: (message: string, ...args: unknown[]) => void;
72
+ warn: (message: string, ...args: unknown[]) => void;
73
+ error: (message: string, ...args: unknown[]) => void;
74
+ debug: (message: string, ...args: unknown[]) => void;
75
+ verbose: (message: string, ...args: unknown[]) => void;
76
+ }
77
+ /**
78
+ * Create a debug logger for a specific namespace
79
+ */
80
+ declare function createDebugLogger(namespace: string, debugLevel?: DebugLevel, customLogger?: Partial<DebugLogger>): DebugLogger;
81
+ /**
82
+ * Format state for debug output (truncate large objects)
83
+ */
84
+ declare function formatStateForDebug(state: unknown, maxLength?: number): string;
85
+ /**
86
+ * Format patch for debug output
87
+ */
88
+ declare function formatPatchForDebug(patch: unknown): string;
89
+
90
+ /**
91
+ * Options for the renderer sync plugin
92
+ */
93
+ interface RendererSyncOptions {
94
+ /**
95
+ * Enable debug logging
96
+ * - true: enable debug logging
97
+ * - 'verbose': enable verbose logging with state diffs
98
+ * - 'minimal': only log errors and warnings
99
+ * - false: disable debug logging (default)
100
+ */
101
+ debug?: DebugLevel$1;
102
+ /**
103
+ * Custom logger (default: console)
104
+ */
105
+ logger?: Partial<DebugLogger>;
106
+ /**
107
+ * Custom API implementation (for testing)
108
+ * @internal
109
+ */
110
+ customApi?: PiniaSyncAPI;
111
+ }
112
+
113
+ /**
114
+ * Renderer process Pinia plugin
115
+ * Synchronizes local store changes with Main process and receives updates
116
+ */
117
+
118
+ /**
119
+ * Create the Pinia plugin for renderer process synchronization
120
+ */
121
+ declare function createRendererSync(options?: RendererSyncOptions): (context: PiniaPluginContext) => void;
122
+
123
+ export { type DebugLevel, type DebugLogger, type SyncStoreOptions, createDebugLogger, createRendererSync, formatPatchForDebug, formatStateForDebug };