IPC Handler Template
Use this template when creating new IPC handlers for Electron communication.
File Structureโ
electron/services/ipc/
โโโ IpcService.ts # Main IPC service
โโโ types.ts # IPC type definitions
โโโ utils.ts # IPC utility functions
โโโ validators.ts # Validation functions
Handler Implementation Templateโ
/**
* IPC handlers for [DOMAIN] operations.
*
* @remarks
* Provides type-safe IPC communication for [DOMAIN] management between the main
* process and renderer process. All handlers use standardized validation and
* error handling patterns.
*
* @public
*/
import type { IpcServiceDependencies } from "../IpcService";
import { logger } from "../../utils/logger";
// Import validation functions from validators.ts
import {
isExampleCreateData,
isExampleUpdateData,
isExampleIdParams,
} from "../validators";
/**
* Registers all [DOMAIN]-related IPC handlers.
*
* @remarks
* This function registers all IPC handlers for [DOMAIN] operations using the
* standardized registration pattern. Each handler includes proper validation
* and error handling.
*
* @example
*
* ```typescript
* registerExampleHandlers(ipcService, {
* exampleManager: managerInstance,
* eventBus: eventBusInstance,
* });
* ```
*
* @param ipcService - The IPC service instance for handler registration
* @param dependencies - Service dependencies needed for operations
*
* @public
*/
export function registerExampleHandlers(
ipcService: any, // Use actual IpcService type
dependencies: IpcServiceDependencies
): void {
const { exampleManager } = dependencies;
// GET operations (no parameters)
ipcService.registerStandardizedIpcHandler(
"example:get-all",
async (): Promise<Example[]> => {
logger.debug("[IPC] Getting all examples");
const examples = await exampleManager.getAllExamples();
return examples;
}
// No validation needed for parameterless operations
);
// GET operations (with parameters)
ipcService.registerStandardizedIpcHandler(
"example:get-by-id",
async (params: ExampleIdParams): Promise<Example | undefined> => {
logger.debug(`[IPC] Getting example by ID: ${params.id}`);
const example = await exampleManager.getExampleById(params.id);
return example;
},
isExampleIdParams
);
// CREATE operations
ipcService.registerStandardizedIpcHandler(
"example:create",
async (params: ExampleCreateData): Promise<Example> => {
logger.debug(`[IPC] Creating example: ${params.name}`);
const example = await exampleManager.createExample(params);
return example;
},
isExampleCreateData
);
// UPDATE operations
ipcService.registerStandardizedIpcHandler(
"example:update",
async (params: ExampleUpdateParams): Promise<void> => {
logger.debug(`[IPC] Updating example: ${params.id}`);
await exampleManager.updateExample(params.id, params.updates);
},
isExampleUpdateParams
);
// DELETE operations
ipcService.registerStandardizedIpcHandler(
"example:delete",
async (params: ExampleIdParams): Promise<void> => {
logger.debug(`[IPC] Deleting example: ${params.id}`);
await exampleManager.deleteExample(params.id);
},
isExampleIdParams
);
// BULK operations
ipcService.registerStandardizedIpcHandler(
"example:bulk-create",
async (params: ExampleBulkCreateData): Promise<Example[]> => {
logger.debug(
`[IPC] Bulk creating ${params.examples.length} examples`
);
const examples = await exampleManager.bulkCreateExamples(
params.examples
);
return examples;
},
isExampleBulkCreateData
);
// UTILITY operations
ipcService.registerStandardizedIpcHandler(
"example:validate-data",
async (params: ExampleValidationData): Promise<ValidationResult> => {
logger.debug("[IPC] Validating example data");
const result = await exampleManager.validateExampleData(params);
return result;
},
isExampleValidationData
);
// EXPORT/IMPORT operations
ipcService.registerStandardizedIpcHandler(
"example:export-data",
async (): Promise<ExportData> => {
logger.debug("[IPC] Exporting example data");
const exportData = await exampleManager.exportExamples();
return exportData;
}
);
ipcService.registerStandardizedIpcHandler(
"example:import-data",
async (params: ExampleImportData): Promise<ImportResult> => {
logger.debug(
`[IPC] Importing example data: ${params.examples.length} items`
);
const result = await exampleManager.importExamples(params.examples);
return result;
},
isExampleImportData
);
logger.info("[IPC] Example handlers registered successfully");
}
Validation Functions Templateโ
Add validation functions to validators.ts
or import them from ../validation/exampleValidation.ts
:
/**
* Validation functions for [DOMAIN] IPC operations.
*
* @remarks
* Provides type-safe validation for all IPC parameters related to [DOMAIN]
* operations. Each validation function ensures data integrity and type safety
* before operations are performed.
*
* @public
*/
/**
* Interface for example ID parameters.
*
* @public
*/
export interface ExampleIdParams {
id: string;
}
/**
* Interface for example creation data.
*
* @public
*/
export interface ExampleCreateData {
name: string;
description?: string;
category: string;
// Add other required fields
}
/**
* Interface for example update parameters.
*
* @public
*/
export interface ExampleUpdateParams {
id: string;
updates: Partial<ExampleCreateData>;
}
/**
* Interface for bulk creation data.
*
* @public
*/
export interface ExampleBulkCreateData {
examples: ExampleCreateData[];
}
/**
* Interface for validation data.
*
* @public
*/
export interface ExampleValidationData {
name: string;
category: string;
// Add validation-specific fields
}
/**
* Interface for import data.
*
* @public
*/
export interface ExampleImportData {
examples: Example[];
options?: {
overwrite?: boolean;
validate?: boolean;
};
}
/**
* Validates example ID parameters.
*
* @example
*
* ```typescript
* if (isExampleIdParams(params)) {
* // params is now typed as ExampleIdParams
* console.log(params.id);
* }
* ```
*
* @param data - Unknown data to validate
*
* @returns Type predicate indicating if data is valid ExampleIdParams
*/
export function isExampleIdParams(data: unknown): data is ExampleIdParams {
return (
typeof data === "object" &&
data !== null &&
"id" in data &&
typeof (data as any).id === "string" &&
(data as any).id.length > 0
);
}
/**
* Validates example creation data.
*
* @param data - Unknown data to validate
*
* @returns Type predicate indicating if data is valid ExampleCreateData
*/
export function isExampleCreateData(data: unknown): data is ExampleCreateData {
if (typeof data !== "object" || data === null) {
return false;
}
const obj = data as any;
return (
// Required fields
"name" in obj &&
typeof obj.name === "string" &&
obj.name.length > 0 &&
"category" in obj &&
typeof obj.category === "string" &&
obj.category.length > 0 &&
// Optional fields
(obj.description === undefined || typeof obj.description === "string")
// Add validation for other fields
);
}
/**
* Validates example update parameters.
*
* @param data - Unknown data to validate
*
* @returns Type predicate indicating if data is valid ExampleUpdateParams
*/
export function isExampleUpdateParams(
data: unknown
): data is ExampleUpdateParams {
if (typeof data !== "object" || data === null) {
return false;
}
const obj = data as any;
return (
// Must have id
"id" in obj &&
typeof obj.id === "string" &&
obj.id.length > 0 &&
// Must have updates object
"updates" in obj &&
typeof obj.updates === "object" &&
obj.updates !== null &&
// Validate updates content (at least one valid field)
Object.keys(obj.updates).length > 0 &&
Object.keys(obj.updates).every((key) => {
switch (key) {
case "name":
return typeof obj.updates[key] === "string";
case "description":
return (
typeof obj.updates[key] === "string" ||
obj.updates[key] === undefined
);
case "category":
return typeof obj.updates[key] === "string";
// Add other updateable fields
default:
return false; // Unknown fields not allowed
}
})
);
}
/**
* Validates bulk creation data.
*
* @param data - Unknown data to validate
*
* @returns Type predicate indicating if data is valid ExampleBulkCreateData
*/
export function isExampleBulkCreateData(
data: unknown
): data is ExampleBulkCreateData {
if (typeof data !== "object" || data === null) {
return false;
}
const obj = data as any;
return (
"examples" in obj &&
Array.isArray(obj.examples) &&
obj.examples.length > 0 &&
obj.examples.every((example: unknown) => isExampleCreateData(example))
);
}
/**
* Validates example validation data.
*
* @param data - Unknown data to validate
*
* @returns Type predicate indicating if data is valid ExampleValidationData
*/
export function isExampleValidationData(
data: unknown
): data is ExampleValidationData {
if (typeof data !== "object" || data === null) {
return false;
}
const obj = data as any;
return (
"name" in obj &&
typeof obj.name === "string" &&
"category" in obj &&
typeof obj.category === "string"
// Add other validation-specific field checks
);
}
/**
* Validates import data.
*
* @param data - Unknown data to validate
*
* @returns Type predicate indicating if data is valid ExampleImportData
*/
export function isExampleImportData(data: unknown): data is ExampleImportData {
if (typeof data !== "object" || data === null) {
return false;
}
const obj = data as any;
const hasValidExamples =
"examples" in obj &&
Array.isArray(obj.examples) &&
obj.examples.length > 0;
// Note: Import might have full Example objects, not just creation data
const hasValidOptions =
obj.options === undefined ||
(typeof obj.options === "object" &&
obj.options !== null &&
(obj.options.overwrite === undefined ||
typeof obj.options.overwrite === "boolean") &&
(obj.options.validate === undefined ||
typeof obj.options.validate === "boolean"));
return hasValidExamples && hasValidOptions;
}
Preload API Extension Templateโ
Add to your preload script:
// In preload.ts, add to electronAPI object
const electronAPI = {
// ... existing API
example: {
// GET operations
getAll: (): Promise<Example[]> => ipcRenderer.invoke("example:get-all"),
getById: (id: string): Promise<Example | undefined> =>
ipcRenderer.invoke("example:get-by-id", { id }),
// CREATE operations
create: (data: ExampleCreateData): Promise<Example> =>
ipcRenderer.invoke("example:create", data),
bulkCreate: (examples: ExampleCreateData[]): Promise<Example[]> =>
ipcRenderer.invoke("example:bulk-create", { examples }),
// UPDATE operations
update: (
id: string,
updates: Partial<ExampleCreateData>
): Promise<void> =>
ipcRenderer.invoke("example:update", { id, updates }),
// DELETE operations
delete: (id: string): Promise<void> =>
ipcRenderer.invoke("example:delete", { id }),
// UTILITY operations
validateData: (
data: ExampleValidationData
): Promise<ValidationResult> =>
ipcRenderer.invoke("example:validate-data", data),
// EXPORT/IMPORT operations
exportData: (): Promise<ExportData> =>
ipcRenderer.invoke("example:export-data"),
importData: (data: ExampleImportData): Promise<ImportResult> =>
ipcRenderer.invoke("example:import-data", data),
},
// ... existing API
};
Type Definitions Templateโ
Add to your types file:
// shared/types/example.ts
/**
* Core example entity interface.
*
* @public
*/
export interface Example {
id: string;
name: string;
description?: string;
category: string;
createdAt: number;
updatedAt: number;
// Add other entity-specific fields
}
/**
* Validation result interface.
*
* @public
*/
export interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
}
/**
* Individual validation error.
*
* @public
*/
export interface ValidationError {
field: string;
message: string;
code: string;
}
/**
* Export data structure.
*
* @public
*/
export interface ExportData {
examples: Example[];
metadata: {
exportedAt: number;
version: string;
count: number;
};
}
/**
* Import result structure.
*
* @public
*/
export interface ImportResult {
success: boolean;
imported: number;
skipped: number;
errors: ImportError[];
}
/**
* Import error details.
*
* @public
*/
export interface ImportError {
item: any;
reason: string;
code: string;
}
Test Templateโ
Create test file: exampleHandlers.test.ts
import { describe, it, expect, beforeEach, vi } from "vitest";
import { registerExampleHandlers } from "../exampleHandlers";
import type { IpcServiceDependencies } from "../../IpcService";
describe("Example IPC Handlers", () => {
let mockIpcService: any;
let mockExampleManager: any;
let dependencies: IpcServiceDependencies;
beforeEach(() => {
mockExampleManager = {
getAllExamples: vi.fn(),
getExampleById: vi.fn(),
createExample: vi.fn(),
updateExample: vi.fn(),
deleteExample: vi.fn(),
bulkCreateExamples: vi.fn(),
validateExampleData: vi.fn(),
exportExamples: vi.fn(),
importExamples: vi.fn(),
};
mockIpcService = {
registerStandardizedIpcHandler: vi.fn(),
};
dependencies = {
exampleManager: mockExampleManager,
// ... other dependencies
} as IpcServiceDependencies;
});
it("should register all example handlers", () => {
registerExampleHandlers(mockIpcService, dependencies);
expect(
mockIpcService.registerStandardizedIpcHandler
).toHaveBeenCalledWith("example:get-all", expect.any(Function));
expect(
mockIpcService.registerStandardizedIpcHandler
).toHaveBeenCalledWith(
"example:create",
expect.any(Function),
expect.any(Function)
);
// Verify all handlers are registered
expect(
mockIpcService.registerStandardizedIpcHandler
).toHaveBeenCalledTimes(8);
});
it("should handle get-all operation", async () => {
const mockExamples = [{ id: "1", name: "Test" }];
mockExampleManager.getAllExamples.mockResolvedValue(mockExamples);
registerExampleHandlers(mockIpcService, dependencies);
// Get the registered handler
const getAllHandler =
mockIpcService.registerStandardizedIpcHandler.mock.calls.find(
(call) => call[0] === "example:get-all"
)[1];
const result = await getAllHandler();
expect(result).toBe(mockExamples);
expect(mockExampleManager.getAllExamples).toHaveBeenCalled();
});
// Add more handler tests...
});
Integration Checklistโ
When creating new IPC handlers:
- [ ] Create handler registration function in domain-specific file
- [ ] Create comprehensive validation functions
- [ ] Add handlers to main IpcService initialization
- [ ] Update preload API with new methods
- [ ] Add TypeScript type definitions
- [ ] Create comprehensive tests
- [ ] Update documentation with examples
- [ ] Test error handling paths
- [ ] Verify type safety works end-to-end
- [ ] Add logging for debugging
Channel Naming Conventionโ
Follow these naming patterns:
- GET operations:
domain:get-all
,domain:get-by-id
- CREATE operations:
domain:create
,domain:bulk-create
- UPDATE operations:
domain:update
,domain:bulk-update
- DELETE operations:
domain:delete
,domain:delete-all
- UTILITY operations:
domain:validate-data
,domain:export-data
- EVENTS:
domain:event-name
(past tense for completed events)
Error Handlingโ
All handlers automatically get:
- Parameter validation
- Error logging
- Consistent error response format
- Type-safe error handling
The registerStandardizedIpcHandler
method handles these concerns automatically.