@nmtjs/ws-client 0.2.2 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,9 @@
1
- ## WebSockets transport for [Neemata](https://github.com/neematajs/neemata)
1
+ # NeemataJS - RPC application server for real-time applications (proof of concept)
2
+
3
+ ### Built with following in mind:
4
+ - transport-agnostic (like WebSockets, WebTransport, .etc)
5
+ - format-agnostic (like JSON, MessagePack, BSON, .etc)
6
+ - binary data streaming and event subscriptions
7
+ - contract-based API
8
+ - end-to-end type safety
9
+ - CPU-intensive task execution on separate workers
package/dist/index.js CHANGED
@@ -1,66 +1,57 @@
1
- import { EventEmitter } from '@nmtjs/protocol/client';
2
- import { concat, decodeNumber, encodeNumber, ServerMessageType } from '@nmtjs/protocol/common';
1
+ import { EventEmitter } from "@nmtjs/protocol/client";
2
+ import { concat, decodeNumber, encodeNumber, ServerMessageType } from "@nmtjs/protocol/common";
3
3
  export class WebSocketClientTransport extends EventEmitter {
4
- options;
5
- #webSocket;
6
- #connecting;
7
- constructor(options){
8
- super(), this.options = options, this.#webSocket = null, this.#connecting = null;
9
- }
10
- connect(auth = undefined, contentType) {
11
- const wsUrl = new URL(this.options.origin);
12
- wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:';
13
- wsUrl.pathname = '/api';
14
- wsUrl.searchParams.set('content-type', contentType);
15
- wsUrl.searchParams.set('accept', contentType);
16
- if (auth) wsUrl.searchParams.set('auth', auth);
17
- const ws = this.options.wsFactory?.(wsUrl) ?? new WebSocket(wsUrl.toString());
18
- ws.binaryType = 'arraybuffer';
19
- ws.addEventListener('message', ({ data })=>{
20
- const buffer = data;
21
- const type = decodeNumber(buffer, 'Uint8');
22
- if (type in ServerMessageType) {
23
- this.emit(`${type}`, buffer.slice(Uint8Array.BYTES_PER_ELEMENT));
24
- }
25
- });
26
- this.#webSocket = ws;
27
- return new Promise((resolve, reject)=>{
28
- ws.addEventListener('open', ()=>{
29
- this.emit('connect');
30
- resolve();
31
- }, {
32
- once: true
33
- });
34
- ws.addEventListener('error', (event)=>reject(new Error('WebSocket error', {
35
- cause: event
36
- })), {
37
- once: true
38
- });
39
- ws.addEventListener('close', (event)=>{
40
- this.emit('disconnect');
41
- this.#webSocket = null;
42
- if (this.options.autoreconnect === true) {
43
- setTimeout(()=>this.connect(auth, contentType), 1000);
44
- }
45
- }, {
46
- once: true
47
- });
48
- });
49
- }
50
- async disconnect() {
51
- if (this.#webSocket === null) return;
52
- this.#webSocket.close();
53
- return _once(this.#webSocket, 'close');
54
- }
55
- async send(messageType, buffer) {
56
- if (this.#connecting) await this.#connecting;
57
- this.#webSocket.send(concat(encodeNumber(messageType, 'Uint8'), buffer));
58
- }
4
+ #webSocket = null;
5
+ #connecting = null;
6
+ constructor(options) {
7
+ super();
8
+ this.options = options;
9
+ }
10
+ connect(auth = undefined, contentType) {
11
+ const wsUrl = new URL(this.options.origin);
12
+ wsUrl.protocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
13
+ wsUrl.pathname = "/api";
14
+ wsUrl.searchParams.set("content-type", contentType);
15
+ wsUrl.searchParams.set("accept", contentType);
16
+ if (auth) wsUrl.searchParams.set("auth", auth);
17
+ const ws = this.options.wsFactory?.(wsUrl) ?? new WebSocket(wsUrl.toString());
18
+ ws.binaryType = "arraybuffer";
19
+ ws.addEventListener("message", ({ data }) => {
20
+ const buffer = data;
21
+ const type = decodeNumber(buffer, "Uint8");
22
+ if (type in ServerMessageType) {
23
+ this.emit(`${type}`, buffer.slice(Uint8Array.BYTES_PER_ELEMENT));
24
+ }
25
+ });
26
+ this.#webSocket = ws;
27
+ this.#connecting = new Promise((resolve, reject) => {
28
+ ws.addEventListener("open", () => {
29
+ this.emit("connected");
30
+ resolve();
31
+ }, { once: true });
32
+ ws.addEventListener("error", (event) => reject(new Error("WebSocket error", { cause: event })), { once: true });
33
+ ws.addEventListener("close", (event) => {
34
+ this.emit("disconnected");
35
+ this.#webSocket = null;
36
+ if (this.options.autoreconnect === true) {
37
+ setTimeout(() => this.connect(auth, contentType), 1e3);
38
+ }
39
+ }, { once: true });
40
+ });
41
+ return this.#connecting;
42
+ }
43
+ async disconnect() {
44
+ if (this.#webSocket === null) return;
45
+ this.#webSocket.close();
46
+ return _once(this.#webSocket, "close");
47
+ }
48
+ async send(messageType, buffer) {
49
+ if (this.#connecting) await this.#connecting;
50
+ this.#webSocket.send(concat(encodeNumber(messageType, "Uint8"), buffer));
51
+ }
59
52
  }
60
53
  function _once(target, event) {
61
- return new Promise((resolve)=>{
62
- target.addEventListener(event, ()=>resolve(), {
63
- once: true
64
- });
65
- });
54
+ return new Promise((resolve) => {
55
+ target.addEventListener(event, () => resolve(), { once: true });
56
+ });
66
57
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../index.ts"],"sourcesContent":["import {\n type BaseClientFormat,\n EventEmitter,\n type ProtocolTransport,\n} from '@nmtjs/protocol/client'\nimport {\n type ClientMessageType,\n concat,\n decodeNumber,\n encodeNumber,\n ServerMessageType,\n} from '@nmtjs/protocol/common'\n\nexport type WebSocketClientTransportOptions = {\n /**\n * The origin of the server\n * @example 'http://localhost:3000'\n */\n origin: string\n /**\n * Whether to autoreconnect on close\n * @default true\n */\n autoreconnect?: boolean\n /**\n * Custom WebSocket class\n * @default globalThis.WebSocket\n */\n wsFactory?: (url: URL) => WebSocket\n\n debug?: boolean\n}\n\nexport class WebSocketClientTransport\n extends EventEmitter\n implements ProtocolTransport\n{\n #webSocket: WebSocket | null = null\n #connecting: Promise<void> | null = null\n\n constructor(private readonly options: WebSocketClientTransportOptions) {\n super()\n }\n\n connect(\n auth: string | undefined = undefined,\n contentType: BaseClientFormat['contentType'],\n ): Promise<void> {\n const wsUrl = new URL(this.options.origin)\n wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:'\n wsUrl.pathname = '/api'\n wsUrl.searchParams.set('content-type', contentType)\n wsUrl.searchParams.set('accept', contentType)\n if (auth) wsUrl.searchParams.set('auth', auth)\n\n const ws =\n this.options.wsFactory?.(wsUrl) ?? new WebSocket(wsUrl.toString())\n\n ws.binaryType = 'arraybuffer'\n\n ws.addEventListener('message', ({ data }) => {\n const buffer: ArrayBuffer = data\n const type = decodeNumber(buffer, 'Uint8')\n if (type in ServerMessageType) {\n this.emit(`${type}`, buffer.slice(Uint8Array.BYTES_PER_ELEMENT))\n }\n })\n\n this.#webSocket = ws\n\n return new Promise((resolve, reject) => {\n ws.addEventListener(\n 'open',\n () => {\n this.emit('connect')\n resolve()\n },\n { once: true },\n )\n\n ws.addEventListener(\n 'error',\n (event) => reject(new Error('WebSocket error', { cause: event })),\n { once: true },\n )\n\n ws.addEventListener(\n 'close',\n (event) => {\n this.emit('disconnect')\n this.#webSocket = null\n if (this.options.autoreconnect === true) {\n setTimeout(() => this.connect(auth, contentType), 1000)\n }\n },\n { once: true },\n )\n })\n }\n\n async disconnect(): Promise<void> {\n if (this.#webSocket === null) return\n this.#webSocket!.close()\n return _once(this.#webSocket, 'close')\n }\n\n async send(\n messageType: ClientMessageType,\n buffer: ArrayBuffer,\n ): Promise<void> {\n if (this.#connecting) await this.#connecting\n this.#webSocket!.send(concat(encodeNumber(messageType, 'Uint8'), buffer))\n }\n}\n\nfunction _once(target: EventTarget, event: string) {\n return new Promise<void>((resolve) => {\n target.addEventListener(event, () => resolve(), { once: true })\n })\n}\n"],"names":["EventEmitter","concat","decodeNumber","encodeNumber","ServerMessageType","WebSocketClientTransport","constructor","options","connect","auth","undefined","contentType","wsUrl","URL","origin","protocol","pathname","searchParams","set","ws","wsFactory","WebSocket","toString","binaryType","addEventListener","data","buffer","type","emit","slice","Uint8Array","BYTES_PER_ELEMENT","Promise","resolve","reject","once","event","Error","cause","autoreconnect","setTimeout","disconnect","close","_once","send","messageType","target"],"mappings":"AAAA,SAEEA,YAAY,QAEP,yBAAwB;AAC/B,SAEEC,MAAM,EACNC,YAAY,EACZC,YAAY,EACZC,iBAAiB,QACZ,yBAAwB;AAsB/B,OAAO,MAAMC,iCACHL;;IAGR,CAAA,SAAU,CAAyB;IACnC,CAAA,UAAW,CAA6B;IAExCM,YAAY,AAAiBC,OAAwC,CAAE;QACrE,KAAK,SADsBA,UAAAA,cAH7B,CAAA,SAAU,GAAqB,WAC/B,CAAA,UAAW,GAAyB;IAIpC;IAEAC,QACEC,OAA2BC,SAAS,EACpCC,WAA4C,EAC7B;QACf,MAAMC,QAAQ,IAAIC,IAAI,IAAI,CAACN,OAAO,CAACO,MAAM;QACzCF,MAAMG,QAAQ,GAAGH,MAAMG,QAAQ,KAAK,WAAW,SAAS;QACxDH,MAAMI,QAAQ,GAAG;QACjBJ,MAAMK,YAAY,CAACC,GAAG,CAAC,gBAAgBP;QACvCC,MAAMK,YAAY,CAACC,GAAG,CAAC,UAAUP;QACjC,IAAIF,MAAMG,MAAMK,YAAY,CAACC,GAAG,CAAC,QAAQT;QAEzC,MAAMU,KACJ,IAAI,CAACZ,OAAO,CAACa,SAAS,GAAGR,UAAU,IAAIS,UAAUT,MAAMU,QAAQ;QAEjEH,GAAGI,UAAU,GAAG;QAEhBJ,GAAGK,gBAAgB,CAAC,WAAW,CAAC,EAAEC,IAAI,EAAE;YACtC,MAAMC,SAAsBD;YAC5B,MAAME,OAAOzB,aAAawB,QAAQ;YAClC,IAAIC,QAAQvB,mBAAmB;gBAC7B,IAAI,CAACwB,IAAI,CAAC,CAAC,EAAED,KAAK,CAAC,EAAED,OAAOG,KAAK,CAACC,WAAWC,iBAAiB;YAChE;QACF;QAEA,IAAI,CAAC,CAAA,SAAU,GAAGZ;QAElB,OAAO,IAAIa,QAAQ,CAACC,SAASC;YAC3Bf,GAAGK,gBAAgB,CACjB,QACA;gBACE,IAAI,CAACI,IAAI,CAAC;gBACVK;YACF,GACA;gBAAEE,MAAM;YAAK;YAGfhB,GAAGK,gBAAgB,CACjB,SACA,CAACY,QAAUF,OAAO,IAAIG,MAAM,mBAAmB;oBAAEC,OAAOF;gBAAM,KAC9D;gBAAED,MAAM;YAAK;YAGfhB,GAAGK,gBAAgB,CACjB,SACA,CAACY;gBACC,IAAI,CAACR,IAAI,CAAC;gBACV,IAAI,CAAC,CAAA,SAAU,GAAG;gBAClB,IAAI,IAAI,CAACrB,OAAO,CAACgC,aAAa,KAAK,MAAM;oBACvCC,WAAW,IAAM,IAAI,CAAChC,OAAO,CAACC,MAAME,cAAc;gBACpD;YACF,GACA;gBAAEwB,MAAM;YAAK;QAEjB;IACF;IAEA,MAAMM,aAA4B;QAChC,IAAI,IAAI,CAAC,CAAA,SAAU,KAAK,MAAM;QAC9B,IAAI,CAAC,CAAA,SAAU,CAAEC,KAAK;QACtB,OAAOC,MAAM,IAAI,CAAC,CAAA,SAAU,EAAE;IAChC;IAEA,MAAMC,KACJC,WAA8B,EAC9BnB,MAAmB,EACJ;QACf,IAAI,IAAI,CAAC,CAAA,UAAW,EAAE,MAAM,IAAI,CAAC,CAAA,UAAW;QAC5C,IAAI,CAAC,CAAA,SAAU,CAAEkB,IAAI,CAAC3C,OAAOE,aAAa0C,aAAa,UAAUnB;IACnE;AACF;AAEA,SAASiB,MAAMG,MAAmB,EAAEV,KAAa;IAC/C,OAAO,IAAIJ,QAAc,CAACC;QACxBa,OAAOtB,gBAAgB,CAACY,OAAO,IAAMH,WAAW;YAAEE,MAAM;QAAK;IAC/D;AACF"}
1
+ {"mappings":"AAAA,SAEE,oBAEK,wBAAwB;AAC/B,SAEE,QACA,cACA,cACA,yBACK,wBAAwB;AAsB/B,OAAO,MAAM,iCACH,aAEV;CACE,aAA+B;CAC/B,cAAoC;CAEpC,YAA6BA,SAA0C;AACrE,SAAO;OADoB;CAE5B;CAED,QACEC,OAA2B,WAC3BC,aACe;EACf,MAAM,QAAQ,IAAI,IAAI,KAAK,QAAQ;AACnC,QAAM,WAAW,MAAM,aAAa,WAAW,SAAS;AACxD,QAAM,WAAW;AACjB,QAAM,aAAa,IAAI,gBAAgB,YAAY;AACnD,QAAM,aAAa,IAAI,UAAU,YAAY;AAC7C,MAAI,KAAM,OAAM,aAAa,IAAI,QAAQ,KAAK;EAE9C,MAAM,KACJ,KAAK,QAAQ,YAAY,MAAM,IAAI,IAAI,UAAU,MAAM,UAAU;AAEnE,KAAG,aAAa;AAEhB,KAAG,iBAAiB,WAAW,CAAC,EAAE,MAAM,KAAK;GAC3C,MAAMC,SAAsB;GAC5B,MAAM,OAAO,aAAa,QAAQ,QAAQ;AAC1C,OAAI,QAAQ,mBAAmB;AAC7B,SAAK,MAAM,EAAE,KAAK,GAAG,OAAO,MAAM,WAAW,kBAAkB,CAAC;GACjE;EACF,EAAC;AAEF,OAAKC,aAAa;AAElB,OAAKC,cAAc,IAAI,QAAQ,CAAC,SAAS,WAAW;AAClD,MAAG,iBACD,QACA,MAAM;AACJ,SAAK,KAAK,YAAY;AACtB,aAAS;GACV,GACD,EAAE,MAAM,KAAM,EACf;AAED,MAAG,iBACD,SACA,CAAC,UAAU,OAAO,IAAI,MAAM,mBAAmB,EAAE,OAAO,MAAO,GAAE,EACjE,EAAE,MAAM,KAAM,EACf;AAED,MAAG,iBACD,SACA,CAAC,UAAU;AACT,SAAK,KAAK,eAAe;AACzB,SAAKD,aAAa;AAClB,QAAI,KAAK,QAAQ,kBAAkB,MAAM;AACvC,gBAAW,MAAM,KAAK,QAAQ,MAAM,YAAY,EAAE,IAAK;IACxD;GACF,GACD,EAAE,MAAM,KAAM,EACf;EACF;AAED,SAAO,KAAKC;CACb;CAED,MAAM,aAA4B;AAChC,MAAI,KAAKD,eAAe,KAAM;AAC9B,OAAKA,WAAY,OAAO;AACxB,SAAO,MAAM,KAAKA,YAAY,QAAQ;CACvC;CAED,MAAM,KACJE,aACAH,QACe;AACf,MAAI,KAAKE,YAAa,OAAM,KAAKA;AACjC,OAAKD,WAAY,KAAK,OAAO,aAAa,aAAa,QAAQ,EAAE,OAAO,CAAC;CAC1E;AACF;AAED,SAAS,MAAMG,QAAqBC,OAAe;AACjD,QAAO,IAAI,QAAc,CAAC,YAAY;AACpC,SAAO,iBAAiB,OAAO,MAAM,SAAS,EAAE,EAAE,MAAM,KAAM,EAAC;CAChE;AACF","names":["options: WebSocketClientTransportOptions","auth: string | undefined","contentType: BaseClientFormat['contentType']","buffer: ArrayBuffer","#webSocket","#connecting","messageType: ClientMessageType","target: EventTarget","event: string"],"sources":["src/index.ts"],"sourcesContent":["import {\n type BaseClientFormat,\n EventEmitter,\n type ProtocolTransport,\n} from '@nmtjs/protocol/client'\nimport {\n type ClientMessageType,\n concat,\n decodeNumber,\n encodeNumber,\n ServerMessageType,\n} from '@nmtjs/protocol/common'\n\nexport type WebSocketClientTransportOptions = {\n /**\n * The origin of the server\n * @example 'http://localhost:3000'\n */\n origin: string\n /**\n * Whether to autoreconnect on close\n * @default true\n */\n autoreconnect?: boolean\n /**\n * Custom WebSocket class\n * @default globalThis.WebSocket\n */\n wsFactory?: (url: URL) => WebSocket\n\n debug?: boolean\n}\n\nexport class WebSocketClientTransport\n extends EventEmitter\n implements ProtocolTransport\n{\n #webSocket: WebSocket | null = null\n #connecting: Promise<void> | null = null\n\n constructor(private readonly options: WebSocketClientTransportOptions) {\n super()\n }\n\n connect(\n auth: string | undefined = undefined,\n contentType: BaseClientFormat['contentType'],\n ): Promise<void> {\n const wsUrl = new URL(this.options.origin)\n wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:'\n wsUrl.pathname = '/api'\n wsUrl.searchParams.set('content-type', contentType)\n wsUrl.searchParams.set('accept', contentType)\n if (auth) wsUrl.searchParams.set('auth', auth)\n\n const ws =\n this.options.wsFactory?.(wsUrl) ?? new WebSocket(wsUrl.toString())\n\n ws.binaryType = 'arraybuffer'\n\n ws.addEventListener('message', ({ data }) => {\n const buffer: ArrayBuffer = data\n const type = decodeNumber(buffer, 'Uint8')\n if (type in ServerMessageType) {\n this.emit(`${type}`, buffer.slice(Uint8Array.BYTES_PER_ELEMENT))\n }\n })\n\n this.#webSocket = ws\n\n this.#connecting = new Promise((resolve, reject) => {\n ws.addEventListener(\n 'open',\n () => {\n this.emit('connected')\n resolve()\n },\n { once: true },\n )\n\n ws.addEventListener(\n 'error',\n (event) => reject(new Error('WebSocket error', { cause: event })),\n { once: true },\n )\n\n ws.addEventListener(\n 'close',\n (event) => {\n this.emit('disconnected')\n this.#webSocket = null\n if (this.options.autoreconnect === true) {\n setTimeout(() => this.connect(auth, contentType), 1000)\n }\n },\n { once: true },\n )\n })\n\n return this.#connecting\n }\n\n async disconnect(): Promise<void> {\n if (this.#webSocket === null) return\n this.#webSocket!.close()\n return _once(this.#webSocket, 'close')\n }\n\n async send(\n messageType: ClientMessageType,\n buffer: ArrayBuffer,\n ): Promise<void> {\n if (this.#connecting) await this.#connecting\n this.#webSocket!.send(concat(encodeNumber(messageType, 'Uint8'), buffer))\n }\n}\n\nfunction _once(target: EventTarget, event: string) {\n return new Promise<void>((resolve) => {\n target.addEventListener(event, () => resolve(), { once: true })\n })\n}\n"],"version":3}
package/package.json CHANGED
@@ -3,24 +3,24 @@
3
3
  "type": "module",
4
4
  "exports": {
5
5
  ".": {
6
- "import": "./dist/index.js",
7
- "types": "./index.ts"
6
+ "types": "./src/index.ts",
7
+ "import": "./dist/index.js"
8
8
  }
9
9
  },
10
10
  "dependencies": {
11
- "@nmtjs/client": "^0.6.3",
12
- "@nmtjs/common": "^0.6.3",
13
- "@nmtjs/protocol": "^0.6.3"
11
+ "@nmtjs/client": "0.7.1",
12
+ "@nmtjs/common": "0.7.1",
13
+ "@nmtjs/protocol": "0.7.1"
14
14
  },
15
15
  "files": [
16
- "index.ts",
16
+ "src",
17
17
  "dist",
18
18
  "LICENSE.md",
19
19
  "README.md"
20
20
  ],
21
- "version": "0.2.2",
21
+ "version": "0.7.1",
22
22
  "scripts": {
23
- "build": "neemata-build -p neutral ./index.ts",
23
+ "build": "neemata-build --root=./src './*.ts'",
24
24
  "type-check": "tsc --noEmit"
25
25
  }
26
26
  }
@@ -68,11 +68,11 @@ export class WebSocketClientTransport
68
68
 
69
69
  this.#webSocket = ws
70
70
 
71
- return new Promise((resolve, reject) => {
71
+ this.#connecting = new Promise((resolve, reject) => {
72
72
  ws.addEventListener(
73
73
  'open',
74
74
  () => {
75
- this.emit('connect')
75
+ this.emit('connected')
76
76
  resolve()
77
77
  },
78
78
  { once: true },
@@ -87,7 +87,7 @@ export class WebSocketClientTransport
87
87
  ws.addEventListener(
88
88
  'close',
89
89
  (event) => {
90
- this.emit('disconnect')
90
+ this.emit('disconnected')
91
91
  this.#webSocket = null
92
92
  if (this.options.autoreconnect === true) {
93
93
  setTimeout(() => this.connect(auth, contentType), 1000)
@@ -96,6 +96,8 @@ export class WebSocketClientTransport
96
96
  { once: true },
97
97
  )
98
98
  })
99
+
100
+ return this.#connecting
99
101
  }
100
102
 
101
103
  async disconnect(): Promise<void> {