npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

electron-controllers

v1.1.2

Published

Crud IPC Controllers for electron applications

Downloads

10

Readme

electron-controllers

npm version build status Dependency Status

Simplified controller based ipc architecture for electron applications using typescript.

⤴️ Motivation

  • Simplify. Remove the need for managing channels manually, naming things is hard - so this package tries to do as much of the work for you as possible.

  • Familiarity. MVC is a well known design pattern among developers which can help provide better structure to your application as it grows. This package provide the C - controllers part of the MVC pattern to your electron application.

💿 Installation

$ npm install --save electron-controllers
# or
$ yarn add electron-controllers

📖 Usage

electron-controllers was designed with typescript users in mind and exposes a very minimal api ( 7 types and two functions ).

RequestChannel and ResponseChannel

RequestChannel and ResponseChannel are simple type aliases created using the flavoring technique for creating flexible nominal types. Using these aliases in your application instead of strings not only helps by adding extra type safety, but also as a form of documentation.

// Represents a channel for ipcRenderer to send requests on.
type RequestChannel = Flavor<string, 'IPC_REQUEST_CHANNEL'>

// Represents a channel for ipcMain to send responses on.
type ResponseChannel = Flavor<string, 'IPC_RESPONSE_CHANNEL'>

IpcRequest

IpcRequest represents a single request sent from ipcRenderer to ipcMain. The full type signature is

interface IpcRequest<T = unknown> {
  responseChannel?: ResponseChannel
  payload: T
}

Where responseChannel is the channel that ipcMain will use to send a response to this request and payload is any data sent along with the request.

Example Usage

const request: IpcRequest<{ name: string }> = { responseChannel:  
 'testChannel', payload: { name: 'testing' } }

IpcHandler

IpcHandler is an abstract class that contains all the logic needed for handling communication between ipcRenderer and ipcMain for a single RequestChannel so you don't have to do it manually. The relevant bits of it's type signature are listed below.

abstract class IpcHandler<TRequest, TResponse> {
  abstract channel: RequestChannel
  
  abstract makeResponse(request: IpcRequest<TRequest>): Promise<TResponse>
}

To use the class simply extend from it and provide a channel to listen for requests on and override the method makeResponse take requests with a payload of type TRequest and returns a response of type Promise<TResponse>.

Example Usage

import { IpcHandler, RequestChannel } from 'electron-controllers'

class MyHandler extends IpcHandler<number, { id: number; name: string }> {
  channel: RequestChannel = 'myChannel'

  async makeResponse(request: IpcRequest<number>): Promise<{ id: number; name: string }> {
    return  getDataFromSomeWhereFunc(request.payload)
  }
} 

IpcAction

IpcAction is nothing more than a type alias for a function that takes a request of type TRequest and returns a Promise<TResponse>. It's main use case is simplifying the creation of IpcHandlers by removing the need to extract the payload from IpcRequests using the createHandler function which will be discussed later.

type IpcAction<TRequest, TResponse> = (request: TRequest) => Promise<TResponse>

CrudChannel

The CrudChannel class handles routing for a IpcController instances by creating a set of routes (channels) that map to controller methods, it can also be used inside ipcRenderer for sending requests. Each instance of a CrudChannel contains 5 different channels that can be accessed using methods on the crud channel instance. To create an instance of CrudChannel, either use the static create(basePath?: RequestChannel): CrudChannel method on the CrudChannel class or the exported crudChannel function which has the same signature. If provided, the optional basePath?: RequestChannel parameter will be used as the root part of the request channels created by the CrudChannel instance, which can be useful for logging amongst other things, if not provided it will be created for using a short random string.

Example usage.

import { CrudChannel, crudChannel } from 'electron-controllers'
const channel: CrudChannel = crudChannel() // using the crudChannel function without a basePath

const channel2: CrudChannel = CrudChannel.create('myBasePath') // using the static create method with a basePath

console.log(channel.basePath) // prints randomly created basePath
console.log(channel2.basePath) // prints 'myBasePath'

const addChannel = channel.add() // channel used for creating new entities
const listChannel = channel.list() // channel used for listing entities
const updateChannel = channel.update() // channel used for updating entities
const removeChannel = channel.remove() // channel used for deleting entities
const findByIdChannel = channel.findById() // channel used for finding an entity by id.

IpcController

The IpcController abstract class is used to create controllers which map routes (channels) on the crudChannel member to methods on IpcController implementation instances. There are 5 abstract methods on the IpcController abstract class, when implemented these methods are used to handle incoming requests on the crudChannel instance's routes with matching name, E.G. crudChannel.add() route will be handled by the controller.add() method and so on. The relevant bits of the type signature are listed below. Each method on an IpcController instance fits the signature of IpcAction type, which is used behind the scenes by the IpcController class to create IpcHandler instances out of each method using the createHandler function.

abstract class IpcController {
  abstract crudChannel: CrudChannel // a crudChannel instance used to map channels to methods.
  abstract add(entities: any): Promise<any> // handles request made to the crudChannel.add() route
  abstract list(filter: any): Promise<any> // handles request made to the crudChannel.list() route
  abstract findById(id: any): Promise<any> // handles request made to the crudChannel.findById() route
  abstract remove(entities: any): Promise<any> // handles request made to the crudChannel.remove() route
  abstract update(entities: any): Promise<any> // handles request made to the crudChannel.update() route
}

Example Usage

import { IpcController, crudChannel } 'electron-controllers'

export class MyController extends IpcController {
  crudChannel: CrudChannel = crudChannel()

  public db: { [key: number]: string } = { 1: 'typescript', 2: 
    'javascript', 3: 'golang', 4: 'dart', 5: 'C#' }
  
  async add(entity: { name: string }): Promise<number> {
    this.db[Object.keys(this.db).length + 1] = entity.name
    return Object.keys(this.db).length
  }

  async findById(id: number): Promise<string> {
    return this.db[id]
  }

  async list(filter: { limit: number }): Promise<string[]> {
    return Object.values(this.db).slice(0, filter.limit)
  }

  async remove(id: number): Promise<number> {
    delete this.db[id]
    return Object.keys(this.db).length
  }

  async update(entity: { id: number; name: string }): Promise<string> {
    this.db[entity.id] = entity.name
    return this.db[entity.id]
  }
}

createHandler

In some cases, an entire controller is not needed and it would be overkill to use one to handle requests being made on a single channel. In these cases you could of course create an instance of an IpcHandler, but an argument could be made that it's still a tad bit tedious to have to create a class and extend from another class just to implement a single method in order to handle incoming requests on a single channel. It's these types of use cases that the createHandler function was created for. createHandler takes a RequestChannel and an IpcAction function as parameters and returns an instance of IpcHandler, the full type signature is listed below.

const createHandler = <TRequest, TResponse>(
  channel: RequestChannel = '',
  action: IpcAction<TRequest, TResponse>,
): IpcHandler<TRequest, TResponse>

Example Usage

import { createHandler, IpcAction, IpcRequest } from 'electron-controllers'

const myAction: IpcAction<number, { id: number, name: string}> =  
 async ( id: number) => { 
    const data = await getDataFromSomewhereUsingId(id)
    return data
}

const myHandler = createHandler(myAction)
const request: IpcRequest<number> = { responseChannel: 'someChannel', payload: 2389 }

// makeResponse is rarely, if ever invoked explicitly as shown below in real applications.

const response: { id: number, name: string} = await myHandler.makeResponse(request)

registerHandlers

Once you have defined all of the IpcHandlers and IpcControllers that your application will use, you need to let electron know how to use them in your application somehow, this is where the registerHandlers function comes into play. In the entry point of your electron application ( the main.js or main.ts file ) you call the registerHandlers function, passing in an optional list of all the IpcControllers that your application has defined and an optional list of all of the IpcHandlers, the registerHandlers function will take care of the rest. What is does under the hood is quite simple, it extracts all of the channels from your controllers and handlers, and then call ipcMain.on for each channel, passing in a single method from IpcHandler ( IpcHandler.handle() ) abstract class which takes care of sending a response to ipcRenderer on your behalf. The full type signature for the function is shown below.

const registerHandlers = (controllers?: IpcController[], handlers?: IpcHandler<any, any>[]): void
const controllers = [new MyController1(), new MyController2, new MyController3()]

const handlers = [createHandler('someChannel', async (id: number) => await getDataFromDb(id)),   
new MyHandler() ]

// somewhere inside main.ts
registerHandlers(controllers, handlers)

Contributing

All contributions are welcome and greatly appreciated. :thumbsup:

Bug Reports or Feature Requests

Please use GitHub Issues.