# @comate/plugin-shared-internals

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

Latest version **0.9.2** (published 2024-03-11) · MIT license · 0 weekly downloads

## Install

```sh
npm install @comate/plugin-shared-internals
pnpm add @comate/plugin-shared-internals
yarn add @comate/plugin-shared-internals
bun add @comate/plugin-shared-internals
```

## Health

**Score 25/100 (F)** — status: abandoned.

Positive: has types; esm support; no vulnerabilities.

Warnings: low downloads; pre 1.0.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.9.2 |
| Published | 2024-03-11 |
| First published | 2024-03-11 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Node | >=20.10.0 |
| Dependencies | 3 |
| Unpacked size | 69.5 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | otakustay |

## Links

- npm: https://www.npmjs.com/package/@comate/plugin-shared-internals
- npm.io page: https://npm.io/package/@comate/plugin-shared-internals

## Dependencies (3)

- [globby](https://npm.io/package/globby.md) 13.2.2
- [anymatch](https://npm.io/package/anymatch.md) ^3.1.3
- [vscode-uri](https://npm.io/package/vscode-uri.md) ^3.0.8

## Recent versions

- 0.9.2 (latest) — 2024-03-11
- 0.9.1 — 2024-03-11
- 0.9.0 — 2024-03-11

## README

# 类型结构

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

## 会话通信

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

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

社区中普遍使用的RPC封装方案如[async-call-rpc](https://www.npmjs.com/package/async-call-rpc)都只能解决第一个问题，因此我们自己实现了一套方案。

### 名词解释

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

```text
             ┌───────┐ ┌───────┐
             │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`方法透传消息到父会话中。

```ts
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`方法即可：

```ts
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`监听的回调函数）。在处理中可以用`send`、`log`等方法发消息回到调用子（如主进程的`Channel`）。

所有通过`Session`对象的`log`和`send`发送的消息，都会带上对应的`sessionId`，以便将所有的信息关联起来。

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

---
_Source: https://npm.io/package/@comate/plugin-shared-internals · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
