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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@comate/plugin-shared-internals

v0.9.2

Published

本包`@comate/plugin-schema`提供与插件系统有关的各种共享类型、常量及常用读写能力。

Readme

类型结构

本包@comate/plugin-schema提供与插件系统有关的各种共享类型、常量及常用读写能力。

会话通信

由于整体是多进程的体系,不能像普通函数一样进行直接调用,因此制定了一套通信和会话的能力,以满足以下要求:

  1. 能够使用Promise的形态进行一次调用,插件执行完成前,Engine端可以通过简单的await阻塞自身的逻辑。
  2. 在一次调用过程中发生的其它调用,例如日志,都会与这一次调用相关联。

社区中普遍使用的RPC封装方案如async-call-rpc都只能解决第一个问题,因此我们自己实现了一套方案。

名词解释

  • ChannelImplement:指一个原本已经存在的能够进行通信的对象,这个对象必须有一个message事件和一个send方法。从定义可以看出来,一个进程就是典型的ChannelImplement对象,也可以通过WebSocket等方式来实现这个接口。
  • Channel:对ChannelImplement做一次封装,通过对messagesend的处理,能够管理会话。其本质是使用sessionId关联各种message到同一个会话中。
  • Session:代表一次会话,可以由Channel#startSession主动创建,也可以在一条有全新的sessionId的消息到达时被动创建。所有的发送和接收消息都是在Session对象中处理的,即发送消息的方法、接收消息的事件监听,都是通过继承Session类来做的
             ┌───────┐ ┌───────┐
             │Session│ │Session│
             └───────┘ └───────┘
┌────────────────┬─────────┐         ┌─────────┬────────────────┐
│                │         │         │         │                │
│                │         ├────────►│         │                │
│                │         │         │         │                │
│ Engine Process │ Channel │         │ Channel │ Plugin Process │
│                │         │         │         │                │
│                │         │◄────────┤         │                │
│                │         │         │         │                │
└────────────────┴─────────┘         └─────────┴────────────────┘
                                  ┌───────┐ ┌───────┐
                                  │Session│ │Session│
                                  └───────┘ └───────┘

自定义会话

正常的使用方法是写2个类,一个继承Session并定义一系列的事件监听和发送方法,一个继承Channel并重写createSession方法返回自己的Session子类。

对于Session的子类:

  1. 定义一个类型PayloadMap,它的键是你需要监听的action常量,值是对应的payload的类型。
  2. 定义class extends Session<PayloadMap>
  3. 重写initializeListeners方法,先调用super.initializeListeners(),再用setListener方法监听不同的消息action,TypeScript会自动推导出来payload类型。
  4. 如果这个Session类是被其它功能使用的,那么添加一系列方法,每个方法是对send的调用,用来发送指定类型的消息。
  5. 对处于调用链中间的Session实现,你可以使用forwardMessageToParent方法透传消息到父会话中。
interface GreetingPayload {
    name: string;
    text: string;
}

interface GoodbyePayload {
    name: string;
}

const ACTION_GREETING = 'GREETING';

const ACTION_GOODBYE = 'GOODBYE';

interface PayloadMap {
    [ACTION_GREETING]: GreetingPayload;
    [ACTION_GOODBYE]: GoodbyePayload;
}

class MySession extends Session<PayloadMap> {
    sendGift(price: number) {
        this.send({action: 'SEND_GIFT', payload: {price}});
    }

    protected initializeListeners() {
        super.initializeListeners();

        this.setListener(
            ACTION_GREETING,
            payload => console.log(`Greeting from ${payload.name}: ${payload.text}`)
        );
        this.setListener(
            ACTION_GOODBYE,
            payload => console.log(`${payload.name} leaves`)
        );
    }
}

随后,实现自己的Channel类型,继承时通过泛型指定自己的Session子类,只需要createSession方法即可:

class MyChannel extends Channel<MySession> {
    protected createSession(init: SessionInit) {
        return new MySession(init, this.implement);
    }
}

由于Session只能由Channel创建,因此如果你的Session实现需要很多其它的依赖,就要先在Channel构造函数中获取,再通过createSession传给Session子类。

调用会话

作为主动调用方,使用Channel#startSession可以启动一个会话并发送一个消息过去,这个方法接收一个sessionId字符串(使用UUID即可)或者一个父的Session对象,会返回Promise直到会话结束(收到SESSION_FINISH消息)。

如果调用startSession时传的是一个父Session对象,那么2个会话就会建立父子关系,部分特殊的消息会由子向父的透传。

在调用startSession后,指定的消息被发送到接收端(例如子进程的Channel),在收到第一条有全新的sessionId时,Channel会创建一个Session对象并处理这条消息(对应setListener监听的回调函数)。在处理中可以用sendlog等方法发消息回到调用子(如主进程的Channel)。

所有通过Session对象的logsend发送的消息,都会带上对应的sessionId,以便将所有的信息关联起来。

对于特殊的内置消息类型(当前仅LOG),它们默认会向父会话透传,即子会话的日志最终只在顶层处理,中间层不管理日志。