@digta/network 0.1.0
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 +86 -0
- package/api.js +107 -0
- package/connection.js +154 -0
- package/index.d.ts +96 -0
- package/index.js +11 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @digta/network
|
|
2
|
+
|
|
3
|
+
Network toolkit for Node: a **TCP / WebSocket** client with **autoreconnect**, plus a tiny **HTTP API** client with retries. Same package, one import.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @digta/network
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Connection — TCP / WebSocket + autoreconnect
|
|
10
|
+
|
|
11
|
+
One API for both transports. Pick it with the URL scheme: `tcp://`, `ws://`, or `wss://`.
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { createConnection } from "@digta/network";
|
|
15
|
+
// or: const { createConnection } = require("@digta/network");
|
|
16
|
+
|
|
17
|
+
const conn = createConnection({
|
|
18
|
+
url: "tcp://192.168.1.10:502", // or "ws://host:8080/path"
|
|
19
|
+
reconnect: true, // on by default
|
|
20
|
+
backoff: { min: 500, max: 30000, factor: 2 },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
conn.on("connect", () => console.log("connected"));
|
|
24
|
+
conn.on("message", (data) => console.log("in:", data));
|
|
25
|
+
conn.on("reconnecting", (attempt, delay) => console.log(`retry #${attempt} in ${delay}ms`));
|
|
26
|
+
conn.on("close", () => console.log("closed"));
|
|
27
|
+
conn.on("error", (err) => console.error(err));
|
|
28
|
+
|
|
29
|
+
conn.connect();
|
|
30
|
+
conn.send("hello");
|
|
31
|
+
// conn.close(); // intentional — stops reconnecting
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Autoreconnect** uses exponential backoff with jitter and resets on success. Call `close()` to stop for good.
|
|
35
|
+
|
|
36
|
+
### Heartbeat & latency (optional)
|
|
37
|
+
|
|
38
|
+
Set `heartbeat` (ms) to ping and measure round-trip time:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const conn = createConnection({ url: "ws://host:8080", heartbeat: 5000 });
|
|
42
|
+
conn.on("latency", (ms) => console.log(`${ms}ms`));
|
|
43
|
+
conn.on("timeout", () => console.log("no pong — dropping + reconnecting"));
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **WebSocket**: works out of the box (native ping/pong frames).
|
|
47
|
+
- **Raw TCP**: has no ping frame. Provide your protocol's `ping` bytes and an `isPong(data)` matcher, or rely on OS keepalive (always on) for dead-link detection.
|
|
48
|
+
|
|
49
|
+
### Events
|
|
50
|
+
|
|
51
|
+
`connect` · `message(data)` · `close` · `error(err)` · `reconnecting(attempt, delay)` · `reconnect_failed(attempts)` · `latency(ms)` · `timeout`
|
|
52
|
+
|
|
53
|
+
> TCP is a raw byte stream — it has **no message framing**. `message` fires per chunk; handle your own boundaries. WebSocket messages are framed for you.
|
|
54
|
+
|
|
55
|
+
## API — HTTP client with retries
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import { createApi } from "@digta/network";
|
|
59
|
+
|
|
60
|
+
const api = createApi({
|
|
61
|
+
baseURL: "https://api.example.com",
|
|
62
|
+
headers: { authorization: "Bearer …" },
|
|
63
|
+
timeout: 10000,
|
|
64
|
+
retries: 3, // retries network errors, timeouts, 429, and 5xx (exp backoff)
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const { data, status } = await api.get("/users", { query: { page: 2 } });
|
|
68
|
+
await api.post("/users", { name: "digta" }); // JSON body auto-serialized
|
|
69
|
+
await api.get("/thing", { retries: 0, timeout: 2000 }); // per-request overrides
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- JSON bodies are serialized and `Content-Type` set automatically; JSON responses are parsed.
|
|
73
|
+
- `4xx`/`5xx` **reject** with an error carrying `.status` and `.response`.
|
|
74
|
+
- Follows redirects (up to 5 hops).
|
|
75
|
+
- Zero HTTP dependency — built on Node's `http`/`https`, works on Node 14+.
|
|
76
|
+
|
|
77
|
+
For one-off absolute-URL calls, use the ready `api`:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { api } from "@digta/network";
|
|
81
|
+
const res = await api.get("https://example.com/health");
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/api.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const http = require('http')
|
|
4
|
+
const https = require('https')
|
|
5
|
+
|
|
6
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
7
|
+
|
|
8
|
+
function buildUrl(base, path, query) {
|
|
9
|
+
let url = /^https?:\/\//i.test(path)
|
|
10
|
+
? path
|
|
11
|
+
: base
|
|
12
|
+
? base.replace(/\/+$/, '') + '/' + String(path).replace(/^\/+/, '')
|
|
13
|
+
: path
|
|
14
|
+
if (query && Object.keys(query).length) {
|
|
15
|
+
const qs = new URLSearchParams(query).toString()
|
|
16
|
+
url += (url.includes('?') ? '&' : '?') + qs
|
|
17
|
+
}
|
|
18
|
+
return url
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function retriable(err) {
|
|
22
|
+
if (err.status) return err.status >= 500 || err.status === 429
|
|
23
|
+
return true // network error or timeout
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// One request, following up to 5 redirects (301/302/303 → GET, 307/308 keep method+body).
|
|
27
|
+
function once(method, url, payload, headers, timeout, hops = 0) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const u = new URL(url)
|
|
30
|
+
const lib = u.protocol === 'https:' ? https : http
|
|
31
|
+
const req = lib.request(u, { method, headers }, (res) => {
|
|
32
|
+
const { statusCode: status, headers: resHeaders } = res
|
|
33
|
+
if (resHeaders.location && status >= 300 && status < 400 && hops < 5) {
|
|
34
|
+
res.resume()
|
|
35
|
+
const next = new URL(resHeaders.location, url).toString()
|
|
36
|
+
let m = method, p = payload, h = headers
|
|
37
|
+
if (status === 303 || ((status === 301 || status === 302) && method === 'POST')) {
|
|
38
|
+
m = 'GET'; p = null
|
|
39
|
+
h = { ...headers }; delete h['content-length']; delete h['content-type']
|
|
40
|
+
}
|
|
41
|
+
return resolve(once(m, next, p, h, timeout, hops + 1))
|
|
42
|
+
}
|
|
43
|
+
const chunks = []
|
|
44
|
+
res.on('data', (c) => chunks.push(c))
|
|
45
|
+
res.on('end', () => {
|
|
46
|
+
const text = Buffer.concat(chunks).toString('utf8')
|
|
47
|
+
const ct = resHeaders['content-type'] || ''
|
|
48
|
+
let data = text
|
|
49
|
+
if (ct.includes('application/json') && text) { try { data = JSON.parse(text) } catch { /* keep text */ } }
|
|
50
|
+
const result = { status, headers: resHeaders, data }
|
|
51
|
+
if (status >= 400) {
|
|
52
|
+
const e = new Error(`HTTP ${status} ${method} ${url}`)
|
|
53
|
+
e.status = status; e.response = result
|
|
54
|
+
return reject(e)
|
|
55
|
+
}
|
|
56
|
+
resolve(result)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
req.on('error', reject)
|
|
60
|
+
req.setTimeout(timeout, () => req.destroy(new Error(`@digta/network: request timed out after ${timeout}ms`)))
|
|
61
|
+
if (payload != null) req.write(payload)
|
|
62
|
+
req.end()
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createApi(opts = {}) {
|
|
67
|
+
const base = opts.baseURL || opts.baseUrl || ''
|
|
68
|
+
const cfg = {
|
|
69
|
+
timeout: opts.timeout ?? 30000,
|
|
70
|
+
retries: opts.retries ?? 0,
|
|
71
|
+
retryDelay: opts.retryDelay ?? 500,
|
|
72
|
+
headers: opts.headers || {},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function request(method, path, { body, headers, query, timeout, retries } = {}) {
|
|
76
|
+
const url = buildUrl(base, path, query)
|
|
77
|
+
const isJsonBody = body != null && typeof body === 'object' && !Buffer.isBuffer(body)
|
|
78
|
+
const payload = body == null ? null : isJsonBody ? JSON.stringify(body) : body
|
|
79
|
+
const finalHeaders = {
|
|
80
|
+
...cfg.headers,
|
|
81
|
+
...headers,
|
|
82
|
+
...(isJsonBody ? { 'content-type': 'application/json' } : {}),
|
|
83
|
+
...(payload != null ? { 'content-length': Buffer.byteLength(payload) } : {}),
|
|
84
|
+
}
|
|
85
|
+
const maxRetries = retries ?? cfg.retries
|
|
86
|
+
const to = timeout ?? cfg.timeout
|
|
87
|
+
|
|
88
|
+
const attempt = (n) =>
|
|
89
|
+
once(method, url, payload, finalHeaders, to).catch((err) => {
|
|
90
|
+
if (n < maxRetries && retriable(err)) return sleep(cfg.retryDelay * 2 ** n).then(() => attempt(n + 1))
|
|
91
|
+
throw err
|
|
92
|
+
})
|
|
93
|
+
return attempt(0)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
request,
|
|
98
|
+
get: (p, o) => request('GET', p, o),
|
|
99
|
+
delete: (p, o) => request('DELETE', p, o),
|
|
100
|
+
head: (p, o) => request('HEAD', p, o),
|
|
101
|
+
post: (p, body, o) => request('POST', p, { ...o, body }),
|
|
102
|
+
put: (p, body, o) => request('PUT', p, { ...o, body }),
|
|
103
|
+
patch: (p, body, o) => request('PATCH', p, { ...o, body }),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { createApi }
|
package/connection.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const EventEmitter = require('events')
|
|
4
|
+
|
|
5
|
+
let _WS
|
|
6
|
+
function getWs() {
|
|
7
|
+
if (_WS) return _WS
|
|
8
|
+
try { _WS = require('ws') }
|
|
9
|
+
catch { throw new Error('@digta/network: WebSocket support needs ws — run `npm i ws`') }
|
|
10
|
+
return _WS
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function openTcp(conn) {
|
|
14
|
+
const net = require('net')
|
|
15
|
+
const socket = net.connect({ host: conn.host, port: conn.port })
|
|
16
|
+
socket.setKeepAlive(true, conn.opts.heartbeat || 10000) // OS-level dead-link detection
|
|
17
|
+
socket.on('connect', () => conn._onOpen())
|
|
18
|
+
socket.on('data', (d) => conn._onData(d))
|
|
19
|
+
socket.on('close', () => conn._onClose())
|
|
20
|
+
socket.on('error', (e) => conn._onError(e))
|
|
21
|
+
return {
|
|
22
|
+
supportsPing: false, // raw TCP has no ping frame — see _onData / opts.ping
|
|
23
|
+
send: (data) => socket.write(data),
|
|
24
|
+
ping: (payload) => { if (payload) socket.write(payload) },
|
|
25
|
+
close: () => socket.destroy(),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function openWs(conn) {
|
|
30
|
+
const WebSocket = getWs()
|
|
31
|
+
const ws = new WebSocket(conn.url)
|
|
32
|
+
ws.on('open', () => conn._onOpen())
|
|
33
|
+
ws.on('message', (d) => conn._onData(d))
|
|
34
|
+
ws.on('pong', () => conn._onPong())
|
|
35
|
+
ws.on('close', () => conn._onClose())
|
|
36
|
+
ws.on('error', (e) => conn._onError(e))
|
|
37
|
+
return {
|
|
38
|
+
supportsPing: true, // native ping/pong frames → heartbeat + latency for free
|
|
39
|
+
send: (data) => ws.send(data),
|
|
40
|
+
ping: () => ws.ping(),
|
|
41
|
+
close: () => ws.close(),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class Connection extends EventEmitter {
|
|
46
|
+
constructor(opts = {}) {
|
|
47
|
+
super()
|
|
48
|
+
this.opts = { heartbeat: 0, heartbeatTimeout: 10000, reconnect: true, maxRetries: Infinity, ...opts }
|
|
49
|
+
this.backoff = { min: 500, max: 30000, factor: 2, ...(opts.backoff || {}) }
|
|
50
|
+
|
|
51
|
+
if (opts.url) {
|
|
52
|
+
const u = new URL(opts.url)
|
|
53
|
+
this.transport = u.protocol === 'tcp:' ? 'tcp' : 'ws'
|
|
54
|
+
this.host = u.hostname
|
|
55
|
+
this.port = Number(u.port)
|
|
56
|
+
this.url = opts.url
|
|
57
|
+
} else {
|
|
58
|
+
this.transport = opts.transport || 'tcp'
|
|
59
|
+
this.host = opts.host
|
|
60
|
+
this.port = opts.port
|
|
61
|
+
if (this.transport === 'ws') this.url = opts.url || `ws://${this.host}:${this.port}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.connected = false
|
|
65
|
+
this.latency = null
|
|
66
|
+
this._attempt = 0
|
|
67
|
+
this._intentional = false
|
|
68
|
+
this._raw = null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
connect() {
|
|
72
|
+
if (this.connected) return this
|
|
73
|
+
this._intentional = false
|
|
74
|
+
clearTimeout(this._reconnectTimer)
|
|
75
|
+
this._raw = this.transport === 'ws' ? openWs(this) : openTcp(this)
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
send(data) {
|
|
80
|
+
if (!this.connected) throw new Error('@digta/network: not connected')
|
|
81
|
+
this._raw.send(data)
|
|
82
|
+
return this
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
close() {
|
|
86
|
+
this._intentional = true
|
|
87
|
+
clearTimeout(this._reconnectTimer)
|
|
88
|
+
this._stopHeartbeat()
|
|
89
|
+
if (this._raw) this._raw.close()
|
|
90
|
+
this.connected = false
|
|
91
|
+
return this
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_onOpen() {
|
|
95
|
+
this.connected = true
|
|
96
|
+
this._attempt = 0
|
|
97
|
+
this.emit('connect')
|
|
98
|
+
this._startHeartbeat()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_onData(data) {
|
|
102
|
+
// TCP has no ping frame: if the caller taught us to recognize a pong, use it for latency.
|
|
103
|
+
if (!this._raw.supportsPing && this.opts.isPong && this.opts.isPong(data)) return this._onPong()
|
|
104
|
+
this.emit('message', data)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_onClose() {
|
|
108
|
+
this._stopHeartbeat()
|
|
109
|
+
this.connected = false
|
|
110
|
+
this.emit('close')
|
|
111
|
+
if (!this._intentional) this._scheduleReconnect()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_onError(err) {
|
|
115
|
+
// Swallow when unheard: reconnect recovers, and an unhandled 'error' would crash the process.
|
|
116
|
+
if (this.listenerCount('error')) this.emit('error', err)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_scheduleReconnect() {
|
|
120
|
+
if (!this.opts.reconnect) return
|
|
121
|
+
if (this._attempt >= this.opts.maxRetries) return this.emit('reconnect_failed', this._attempt)
|
|
122
|
+
const { min, max, factor } = this.backoff
|
|
123
|
+
const base = Math.min(max, min * Math.pow(factor, this._attempt))
|
|
124
|
+
const delay = Math.round(base / 2 + Math.random() * (base / 2)) // exponential backoff + full jitter
|
|
125
|
+
this._attempt++
|
|
126
|
+
this.emit('reconnecting', this._attempt, delay)
|
|
127
|
+
this._reconnectTimer = setTimeout(() => this.connect(), delay)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_startHeartbeat() {
|
|
131
|
+
if (!this.opts.heartbeat) return
|
|
132
|
+
if (!this._raw.supportsPing && !this.opts.ping) return // TCP heartbeat needs a ping payload
|
|
133
|
+
this._hbTimer = setInterval(() => this._ping(), this.opts.heartbeat)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_ping() {
|
|
137
|
+
if (!this.connected) return
|
|
138
|
+
this._pingAt = Date.now()
|
|
139
|
+
try { this._raw.ping(this.opts.ping) } catch { return }
|
|
140
|
+
this._pongTimer = setTimeout(() => { this.emit('timeout'); this._raw.close() }, this.opts.heartbeatTimeout)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_onPong() {
|
|
144
|
+
clearTimeout(this._pongTimer)
|
|
145
|
+
if (this._pingAt) { this.latency = Date.now() - this._pingAt; this.emit('latency', this.latency) }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
_stopHeartbeat() {
|
|
149
|
+
clearInterval(this._hbTimer)
|
|
150
|
+
clearTimeout(this._pongTimer)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = { Connection, createConnection: (opts) => new Connection(opts) }
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { EventEmitter } from 'events'
|
|
2
|
+
|
|
3
|
+
// ── Connection (TCP / WebSocket + autoreconnect) ──────────────────────────
|
|
4
|
+
|
|
5
|
+
export interface BackoffOptions {
|
|
6
|
+
/** First retry delay in ms. Default 500. */
|
|
7
|
+
min?: number
|
|
8
|
+
/** Cap on retry delay in ms. Default 30000. */
|
|
9
|
+
max?: number
|
|
10
|
+
/** Multiplier per attempt. Default 2. */
|
|
11
|
+
factor?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ConnectionOptions {
|
|
15
|
+
/** `tcp://host:port`, `ws://host:port/path`, or `wss://…`. Sets transport + address. */
|
|
16
|
+
url?: string
|
|
17
|
+
/** Used instead of `url`. */
|
|
18
|
+
transport?: 'tcp' | 'ws'
|
|
19
|
+
host?: string
|
|
20
|
+
port?: number
|
|
21
|
+
/** Auto-reconnect on drop. Default true. */
|
|
22
|
+
reconnect?: boolean
|
|
23
|
+
/** Max reconnect attempts. Default Infinity. */
|
|
24
|
+
maxRetries?: number
|
|
25
|
+
/** Reconnect backoff schedule. */
|
|
26
|
+
backoff?: BackoffOptions
|
|
27
|
+
/** Ping interval in ms (0 = off). WS uses native ping/pong; TCP needs `ping`+`isPong`. */
|
|
28
|
+
heartbeat?: number
|
|
29
|
+
/** Drop + reconnect if no pong within this many ms. Default 10000. */
|
|
30
|
+
heartbeatTimeout?: number
|
|
31
|
+
/** TCP only: bytes to send as a heartbeat ping. */
|
|
32
|
+
ping?: string | Buffer
|
|
33
|
+
/** TCP only: returns true if the received data is the pong to our ping. */
|
|
34
|
+
isPong?: (data: Buffer) => boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Events: `connect`, `message(data)`, `close`, `error(err)`,
|
|
39
|
+
* `reconnecting(attempt, delayMs)`, `reconnect_failed(attempts)`,
|
|
40
|
+
* `latency(ms)`, `timeout`.
|
|
41
|
+
*/
|
|
42
|
+
export class Connection extends EventEmitter {
|
|
43
|
+
constructor(options?: ConnectionOptions)
|
|
44
|
+
readonly connected: boolean
|
|
45
|
+
/** Last measured round-trip in ms (heartbeat), or null. */
|
|
46
|
+
readonly latency: number | null
|
|
47
|
+
readonly transport: 'tcp' | 'ws'
|
|
48
|
+
connect(): this
|
|
49
|
+
send(data: string | Buffer | ArrayBufferView): this
|
|
50
|
+
/** Close intentionally — no reconnect. */
|
|
51
|
+
close(): this
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createConnection(options?: ConnectionOptions): Connection
|
|
55
|
+
|
|
56
|
+
// ── API (HTTP client with retries) ────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export interface ApiOptions {
|
|
59
|
+
baseURL?: string
|
|
60
|
+
headers?: Record<string, string>
|
|
61
|
+
/** Per-request timeout in ms. Default 30000. */
|
|
62
|
+
timeout?: number
|
|
63
|
+
/** Retry count for network errors, timeouts, 429, and 5xx. Default 0. */
|
|
64
|
+
retries?: number
|
|
65
|
+
/** Base retry delay in ms (doubles each attempt). Default 500. */
|
|
66
|
+
retryDelay?: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface RequestOptions {
|
|
70
|
+
body?: unknown
|
|
71
|
+
headers?: Record<string, string>
|
|
72
|
+
query?: Record<string, string | number | boolean>
|
|
73
|
+
timeout?: number
|
|
74
|
+
retries?: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface Response<T = any> {
|
|
78
|
+
status: number
|
|
79
|
+
headers: Record<string, string | string[] | undefined>
|
|
80
|
+
data: T
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface Api {
|
|
84
|
+
request<T = any>(method: string, path: string, options?: RequestOptions): Promise<Response<T>>
|
|
85
|
+
get<T = any>(path: string, options?: RequestOptions): Promise<Response<T>>
|
|
86
|
+
delete<T = any>(path: string, options?: RequestOptions): Promise<Response<T>>
|
|
87
|
+
head<T = any>(path: string, options?: RequestOptions): Promise<Response<T>>
|
|
88
|
+
post<T = any>(path: string, body?: unknown, options?: RequestOptions): Promise<Response<T>>
|
|
89
|
+
put<T = any>(path: string, body?: unknown, options?: RequestOptions): Promise<Response<T>>
|
|
90
|
+
patch<T = any>(path: string, body?: unknown, options?: RequestOptions): Promise<Response<T>>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createApi(options?: ApiOptions): Api
|
|
94
|
+
|
|
95
|
+
/** Ready-to-use client for absolute URLs. */
|
|
96
|
+
export const api: Api
|
package/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Connection, createConnection } = require('./connection')
|
|
4
|
+
const { createApi } = require('./api')
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
Connection,
|
|
8
|
+
createConnection, // TCP / WebSocket client with autoreconnect
|
|
9
|
+
createApi, // HTTP client with retries
|
|
10
|
+
api: createApi(), // ready-to-use HTTP client for absolute URLs
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@digta/network",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TCP + WebSocket client with autoreconnect, plus a tiny HTTP API client with retries. One toolkit.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js",
|
|
16
|
+
"connection.js",
|
|
17
|
+
"api.js",
|
|
18
|
+
"index.d.ts"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node test.js"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"tcp",
|
|
25
|
+
"websocket",
|
|
26
|
+
"ws",
|
|
27
|
+
"reconnect",
|
|
28
|
+
"autoreconnect",
|
|
29
|
+
"http",
|
|
30
|
+
"api",
|
|
31
|
+
"client",
|
|
32
|
+
"retry"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"ws": "^8.18.0"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=14"
|
|
43
|
+
}
|
|
44
|
+
}
|