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 🙏

© 2026 – Pkg Stats / Ryan Hefner

jigsaw.js

v5.2.0

Published

Jigsaw的中文是拼图的意思,该项目最早来源于一个思考:

Downloads

207

Readme

Jigsaw.js 文档

1.1 简介

Jigsaw的中文是拼图的意思,该项目最早来源于一个思考:

将一个服务拆分成多个微服务,最重要的是什么。

服务模块之间的解耦要靠一个消息组件实现,于是Jigsaw就诞生了,专门解决独立服务之间互相调用的问题。


1.1.1 特性

①Jigsaw和一般的RPC框架一样,有生产者消费者存在,但并不像普通的RPC框架一样繁琐,因为每一个Jigsaw实例既是生产者也是消费者。

②Jigsaw想做到调用另一个进程的函数就像是同个作用域的函数一样简单,于是在用法上做了点有趣的设计。

③Jigsaw的一个设计的要求点是可靠性,于是从Node.js的数据报(即UDP协议)开始重新封装一层Jigsaw专用的通信协议。该协议具有丢包自动重发、大包拆分再发、每包必有回应的特性,是一种可靠的协议。于是无需关注内部实现,直接使用它稳定可靠的通信即可。

④Jigsaw存在一个域名服务器,该域名服务器是Jigsaw自己的内部实现,用于映射Jigsaw的名字到具体的网络地址。使用域名服务器可以在互联网上组成一个虚拟的Jigsaw网络。网络内的任何一个Jigsaw实例都可以通过名字访问另一个Jigsaw实例。

⑤Jigsaw自动管理连接,保证连接之间的健壮性,即使某个计算机发生崩溃导致Jigsaw实例被关闭,也会在下次打开的时候无缝的重新接入Jigsaw网络。

1.2 安装

在npm项目下执行命令npm install jigsaw.js --save

1.3 简单实例

1.3.1


human.js

let {jigsaw}=require("jigsaw.js")("127.0.0.1","127.0.0.1");

let human=new jigsaw("human");//这是一个jigsaw实例

human.send("gun:shoot",{bullets:10});

gun.js

let {jigsaw}=require("jigsaw.js")("127.0.0.1","127.0.0.1");

let gun=new jigsaw("gun");

gun.port("shoot",({bullets})=>{

    for(let i=0;i<bullets;i++)
        console.log("shoot!");
        
return {msg:`我已经发射了${bullets}颗子弹`};
})

index.js

let {domainserver,webserver}=require("jigsaw.js")("127.0.0.1","127.0.0.1");
//第一个参数指的是需要绑定到的网卡IP地址,一般默认为127.0.0.1表示绑定到本机IP地址,这样的情况下,所有Jigsaw实例只能在本机内任意通信。
//第二个参数指的是domainserver域名服务器所在主机的IP地址。默认是本机。

//这两个参数决定了Jigsaw实例的环境,一般来说,可以互相访问的Jigsaw实例,这两个参数全部都是一样的。

let {fork}=require("child_process");

domainserver();//一个Jigsaw网络内至少要启动一个域名服务器,所有Jigsaw实例都可以使用该域名服务器

webserver(1793);//本行可以不写,本行可以启动一个访问Jigsaw网络的Web服务器。

fork("human.js");
fork("gun.js");

然后node index.js就可以运行该实例了。
此处只是一个最简单的用法,对于Web应用的复杂用法可以参考分布式架构的设计,之后会在文档中详细说明。
关于物联网相关的用法,文档之后也会补充。

1.3.2 简单的 Web应用 接口服务器

该文件在examples/simpleaccount.js

let {jigsaw,domainserver,webserver}=require("../index.js")("127.0.0.1","127.0.0.1");
 domainserver();
 webserver(80);


 let accounts=[];

let jg=new jigsaw("account");

jg.port("register",({username,password})=>{
 	let userid=accounts.length;
	accounts[userid]={userid,username,password,token:""};

	return {error:false,msg:`注册成功!你的账号是:${userid}`}

});

jg.port("login",({userid,password})=>{
	if(!accounts[userid])return {error:true,msg:"系统中不存在该账户"};


	if(accounts[userid].password == password){
		let token=Math.random()+"";
		accounts[userid].token=token;

		return {error:false,msg:`登录成功!欢迎你,${accounts[userid].username},你的登录态令牌:${token}`}
	}else
		return {error:true,msg:"登录失败,密码错误!"};

})

node simpleaccount.js启动后。

可以通过
http://127.0.0.1/account/register?username=testuser&password=123
http://127.0.0.1/account/login?userid=0&password=123
进行测试


1.4 API 接口

1.4.1 jigsaw.prototype.constructor(jgname)

jigsaw类的构造器,第一个参数是jigsaw的名字,jigsaw的名字是很重要的,可以用来对实例进行命名空间分配,也可以通过名字访问其它jigsaw实例。

let jg = new jigsaw("myjigsaw");

1.4.2 jigsaw.prototype.port(name,func)

为jigsaw实例声明一个接口,第一个参数是接口名,第二个参数是该接口被调用后触发的函数,该函数可以是一个异步函数(async function)。

jg.port("add",(a,b)=>{
return a+b;
})

jg.port("addasync",async(a,b)=>{
let sleep=(t)=>new Promise((r)=>setTimeout(r,t));
await sleep(1000);
return a+b;
})

1.4.3 jigsaw.prototype.send(path,data) : [Promise]

对指定的路径的jigsaw实例的一个接口进行一次远程调用。
其中第一个参数是路径,第二个参数是传递的数据,该参数必须是一个对象。
若该参数是undefined或者不填,则相当于是传递了空对象{}

返回值是一个Promise,会直接返回远程调用的结果。(如果该结果为空,则一定是null)

路径的格式由 jigsaw名 + 冒号 + 接口名 组成。

jg.send("gun:shoot",{bullets:10});

jg.send("app.database:select",{sheet:"users",id:233});

1.4.4 jigsaw.prototype.handle(obj)

该方法可以批量定义接口。就像这样:

jg.handle({
    shoot(){
    
    },
    async reload(){
    
    }
    
})

还可以这样:

jg.handle((portname,data)=>{

console.log(`${portname}接口收到了数据`,data);

})

1.4.5 jigsaw.prototype.dighole(targetjigsaw) : [Promise]

向目标的jigsaw打一个"洞",目标jigsaw通过该"洞"可以稳定的访问本jigsaw。  

你可以像这样向"洞"发送数据,就和jigsaw的远程调用方法是一样的  

对于内网的jigsaw实例访问外网的jigsaw实例,若希望通信可以双向畅通无阻,  
那么应当使用该方法打出一个"洞",使用该"洞"作为jigsaw的名字进行远程调用。  

若jigsaw实例同在一个内网或者互联网上,则完全不需要使用本方法。  

//下面的代码的执行环境可以是可以访问互联网的局域网

	let lan = new jigsaw("LAN");
	lan.port("call",()=>{
			console.log("我被调用了");
		});
	
	lan.dighole("INTERNET");


//下面的代码的执行环境可以是互联网

	let internet = new jigsaw("INTERNET");
	internet.send(`LAN:call`);

1.4.6 jigsaw.setoption(jgname,option) : [Promise]

对特定的jigsaw名进行配置选项,该配置会一直保存在域名服务器上。  

该选项对象中有如下属性

①jgcount

若一个jigsaw名被设置了jgcount选项,那么下一次从域名服务器查询该jigsaw实例的网络地址时, 会分别从好几个jigsaw实例中随机选择一个网络地址并返回。

该功能用途主要是负载均衡。

例如一个实例的名字是ticket,并设置了jgcount属性为4

那么下次任何实例访问该实例的时候,域名服务器都会从以下这4个名字中随机选择一个地址返回

ticket
ticket@1
ticket@2
ticket@3

这样流量被分流到了4个jigsaw实例上,由他们共同分担并承受压力。配合数据库可以实现解决了c10k问题的web应用。

该方法是一个静态方法,用法应当是

await jigsaw.setoption("ticket",{jgcount:4});

1.4.7 jigsaw.prototype.close()

直接关闭 jigsaw 实例,jigsaw内部的套接字实例、保持连接的域名客户端也会因此被关闭。


1.5 测试

本项目可以通过 mocha 框架进行测试,
在项目目录下直接运行

npm test

即可开始测试
另外,你还可以检查测试用例的覆盖率
在项目目录下直接运行

npm run cov

即可得到覆盖率报告

2.1 负载均衡 与 网络IO

一般来说一个分布式系统的负载均衡的实现应当在RPC框架上。所以jigsaw实现了基本的负载均衡。

jigsaw实例可以存在在进程上,那么如果启动多个进程,使用了jigsaw.setoption方法使得域名服务器开始对请求分流。各个进程就可以等量的处理流量。

因为要保证数据的唯一性,传统的负载均衡应用实现都要依赖数据库。

理想的分布式系统是没有系统之间进行数据交换的成本的,也就是说,RPC框架之间进行通信的需要的时间和空间几乎为0. 那么理论上任何系统都可以无限拓展,不受性能的约束。

但是现实并不是这样的,RPC框架进行一次数据交换,一般靠的是操作系统提供的套接字接口。那么网络IO就需要占用一定的时间。任何分布式系统的设计都应当考虑进去这一点。对于一个方法需要大量远程调用的请求应当重新考虑设计。

由于Jigsaw基于node.js,并且场景为IO密集型,并且是异步IO,所以网络IO的性能是较好的,这也是选择在node.js下开发一款RPC框架的原因。

2.2 推荐的命名规范

一般来说,对Jigsaw实例的命名(即构造器的第一个参数)可以参考这样的规范。

①使用命名空间

可以按照功能从命名上对Jigsaw实例进行分组。  

例如这样使用点符号作为命名空间  
app.auth //应用的认证层入口,用于鉴权等

app.database //应用的数据库中间件层
app.ticket //应用的订单层
app.account //应用的账户管理层

app.interface.ticket //应用的订单接口
app.interface.register //应用的注册接口
Jigsaw实例的命名并不会影响到Jigsaw的使用,事实上,大部分字符都可以作为名字。  

②适当的时候可以匿名

let jg=new jigsaw();

像这样创建一个jigsaw实例,那么该jigsaw实例被称作匿名jigsaw实例。

一般这种实例并不设置接受远程调用的接口,当然要设置也是可以的。
但只能通过打洞的方式进行访问。