@hcengineering/hulypulse-client 0.7.423
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 +152 -0
- package/lib/client.js +334 -0
- package/lib/client.js.map +7 -0
- package/lib/index.js +19 -0
- package/lib/index.js.map +7 -0
- package/package.json +55 -0
- package/tsconfig.json +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# HulypulseClient
|
|
2
|
+
|
|
3
|
+
A TypeScript/Node.js client for the Hulypulse WebSocket server.
|
|
4
|
+
Supports automatic reconnection, request–response correlation, `get` / `put` / `delete`, and subscriptions.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
### Main Methods
|
|
9
|
+
|
|
10
|
+
## put(key: string, data: string, TTL?: number): Promise<boolean>
|
|
11
|
+
|
|
12
|
+
Stores a value under a key.
|
|
13
|
+
|
|
14
|
+
TTL (optional) — time-to-live in seconds.
|
|
15
|
+
|
|
16
|
+
Resolves with true if the operation succeeded.
|
|
17
|
+
|
|
18
|
+
await client.put("workspace/users/123", "Alice", 60) → true
|
|
19
|
+
|
|
20
|
+
## get(key: string): Promise<any | false>
|
|
21
|
+
|
|
22
|
+
Retrieves the value for a key.
|
|
23
|
+
|
|
24
|
+
Resolves with the value if found.
|
|
25
|
+
Resolves with false if the key does not exist.
|
|
26
|
+
|
|
27
|
+
const value = await client.get("workspace/users/123")
|
|
28
|
+
if (value) {
|
|
29
|
+
console.log("User data:", value)
|
|
30
|
+
} else {
|
|
31
|
+
console.log("User not found")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
## get_full(key: string): Promise<{data, etag, expires_at} | false>
|
|
35
|
+
|
|
36
|
+
Retrieves the full record:
|
|
37
|
+
|
|
38
|
+
data — stored value,
|
|
39
|
+
etag — data identifier,
|
|
40
|
+
expires_at — expiration in seconds.
|
|
41
|
+
|
|
42
|
+
const full = await client.get_full("workspace/users/123")
|
|
43
|
+
if (full) {
|
|
44
|
+
console.log(full.data, full.etag, full.expires_at)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
## delete(key: string): Promise<boolean>
|
|
48
|
+
|
|
49
|
+
Deletes a key.
|
|
50
|
+
|
|
51
|
+
Resolves with true if the key was deleted.
|
|
52
|
+
Resolves with false if the key was not found.
|
|
53
|
+
|
|
54
|
+
const deleted = await client.delete("workspace/users/123")
|
|
55
|
+
console.log(deleted ? "Deleted" : "Not found")
|
|
56
|
+
|
|
57
|
+
## subscribe(key: string, callback: (msg, key, index) => void): Promise<boolean>
|
|
58
|
+
|
|
59
|
+
Subscribes to updates for a key (or prefix).
|
|
60
|
+
|
|
61
|
+
The callback is invoked on every event: Set, Del, Expired
|
|
62
|
+
|
|
63
|
+
Resolves with true if a new subscription was created.
|
|
64
|
+
Resolves with false if the callback was already subscribed.
|
|
65
|
+
|
|
66
|
+
const cb = (msg, key, index) => {
|
|
67
|
+
if( msg.message === 'Expired' ) console.log(`${msg.key} was expired`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await client.subscribe("workspace/users/", cb)
|
|
71
|
+
// Now cb will be called when any key starting with "workspace/users/" changes
|
|
72
|
+
|
|
73
|
+
## unsubscribe(key: string, callback: Callback): Promise<boolean>
|
|
74
|
+
|
|
75
|
+
Unsubscribes a specific callback.
|
|
76
|
+
|
|
77
|
+
Resolves with true if the callback was removed (and if it was the last one, the server gets an unsub message).
|
|
78
|
+
Resolves with false if the callback was not found.
|
|
79
|
+
|
|
80
|
+
await client.unsubscribe("workspace/users/", cb)
|
|
81
|
+
|
|
82
|
+
## send(message: any): Promise<any>
|
|
83
|
+
|
|
84
|
+
Low-level method to send a raw message.
|
|
85
|
+
|
|
86
|
+
Automatically attaches a correlation id.
|
|
87
|
+
Resolves when a response with the same correlation is received.
|
|
88
|
+
|
|
89
|
+
const reply = await client.send({ type: "get", key: "workspace/users/123" })
|
|
90
|
+
console.log("Raw reply:", reply)
|
|
91
|
+
|
|
92
|
+
## Reconnection
|
|
93
|
+
|
|
94
|
+
If the connection drops, the client automatically reconnects.
|
|
95
|
+
All active subscriptions are re-sent to the server after reconnect.
|
|
96
|
+
|
|
97
|
+
## Closing
|
|
98
|
+
|
|
99
|
+
The client supports both manual closing and the new using syntax (TypeScript 5.2+).
|
|
100
|
+
|
|
101
|
+
client[Symbol.dispose]() // closes the connection
|
|
102
|
+
|
|
103
|
+
or, if needed internally:
|
|
104
|
+
|
|
105
|
+
(client as any).close()
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Usage Example
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { HulypulseClient } from "./hulypulse_client.js"
|
|
113
|
+
|
|
114
|
+
async function main() {
|
|
115
|
+
// connect
|
|
116
|
+
const client = await HulypulseClient.connect("wss://hulypulse_mem.lleo.me/ws")
|
|
117
|
+
|
|
118
|
+
// subscribe to updates
|
|
119
|
+
const cb = (msg, key, index) => {
|
|
120
|
+
console.log("Update for", key, ":", msg)
|
|
121
|
+
}
|
|
122
|
+
await client.subscribe("workspace/users/", cb)
|
|
123
|
+
|
|
124
|
+
// put value
|
|
125
|
+
await client.put("workspace/users/123", JSON.stringify({ name: "Alice" }), 5)
|
|
126
|
+
|
|
127
|
+
// get value
|
|
128
|
+
const value = await client.get("workspace/users/123")
|
|
129
|
+
console.log("Fetched:", value)
|
|
130
|
+
|
|
131
|
+
// get full record
|
|
132
|
+
const full = await client.get_full("workspace/users/123")
|
|
133
|
+
if (full) {
|
|
134
|
+
console.log(full.data, full.etag, full.expires_at)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// delete key
|
|
138
|
+
const deleted = await client.delete("workspace/users/123")
|
|
139
|
+
console.log(deleted ? "Deleted" : "Not found")
|
|
140
|
+
|
|
141
|
+
// unsubscribe
|
|
142
|
+
await client.unsubscribe("workspace/users/", cb)
|
|
143
|
+
|
|
144
|
+
// low-level send
|
|
145
|
+
const reply = await client.send({ type: "sublist" })
|
|
146
|
+
console.log("My sublists:", reply)
|
|
147
|
+
|
|
148
|
+
// dispose
|
|
149
|
+
client[Symbol.dispose]()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main()
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var client_exports = {};
|
|
21
|
+
__export(client_exports, {
|
|
22
|
+
HulypulseClient: () => HulypulseClient,
|
|
23
|
+
escapeString: () => escapeString
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(client_exports);
|
|
26
|
+
const WS_CLOSE_NORMAL = 1e3;
|
|
27
|
+
class HulypulseClient {
|
|
28
|
+
constructor(url) {
|
|
29
|
+
this.url = url;
|
|
30
|
+
}
|
|
31
|
+
static {
|
|
32
|
+
__name(this, "HulypulseClient");
|
|
33
|
+
}
|
|
34
|
+
ws = null;
|
|
35
|
+
closed_manually = false;
|
|
36
|
+
reconnectTimeout;
|
|
37
|
+
RECONNECT_INTERVAL_MS = 1e3;
|
|
38
|
+
subscribes = /* @__PURE__ */ new Map();
|
|
39
|
+
pingTimeout;
|
|
40
|
+
pingInterval;
|
|
41
|
+
PING_INTERVAL_MS = 30 * 1e3;
|
|
42
|
+
PING_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
43
|
+
SEND_TIMEOUT_MS = 3e3;
|
|
44
|
+
correlationId = 1;
|
|
45
|
+
pending = /* @__PURE__ */ new Map();
|
|
46
|
+
/** Matches errors from Pulse when Redis/backend TCP is dead while WS still looks open. */
|
|
47
|
+
static isConnectionLikeError(err) {
|
|
48
|
+
const s = err.toLowerCase();
|
|
49
|
+
return s.includes("broken pipe") || s.includes("connection reset") || s.includes("connection refused") || s.includes("connection aborted") || s.includes("unexpected eof") || s.includes("io error");
|
|
50
|
+
}
|
|
51
|
+
async connect() {
|
|
52
|
+
await new Promise((resolve, reject) => {
|
|
53
|
+
const ws = new WebSocket(this.url.toString());
|
|
54
|
+
this.ws = ws;
|
|
55
|
+
ws.onopen = () => {
|
|
56
|
+
this.resubscribe();
|
|
57
|
+
this.startPing();
|
|
58
|
+
resolve(void 0);
|
|
59
|
+
};
|
|
60
|
+
ws.onerror = (event) => {
|
|
61
|
+
this.reconnect();
|
|
62
|
+
};
|
|
63
|
+
ws.onclose = (event) => {
|
|
64
|
+
this.reconnect();
|
|
65
|
+
};
|
|
66
|
+
ws.onmessage = (event) => {
|
|
67
|
+
try {
|
|
68
|
+
if (event.data === "ping") {
|
|
69
|
+
this.ws?.send("pong");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (event.data === "pong") {
|
|
73
|
+
if (this.pingTimeout !== void 0) {
|
|
74
|
+
clearTimeout(this.pingTimeout);
|
|
75
|
+
this.pingTimeout = void 0;
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const msg = JSON.parse(event.data.toString());
|
|
80
|
+
if ("message" in msg) {
|
|
81
|
+
for (const [key, callbacks] of this.subscribes) {
|
|
82
|
+
if (msg.key.startsWith(key)) {
|
|
83
|
+
callbacks.forEach((cb, index) => {
|
|
84
|
+
try {
|
|
85
|
+
const value = msg.message === "Set" && msg.value !== void 0 ? JSON.parse(msg.value) : void 0;
|
|
86
|
+
cb(msg.key, value);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error(`Error in callback #${index} with key "${key}":`, err);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} else if ("correlation" in msg) {
|
|
94
|
+
const id = msg.correlation;
|
|
95
|
+
if (id !== void 0 && this.pending.has(id)) {
|
|
96
|
+
const pending = this.pending.get(id);
|
|
97
|
+
if (pending !== void 0) {
|
|
98
|
+
clearTimeout(pending.send_timeout);
|
|
99
|
+
this.pending.delete(id);
|
|
100
|
+
if ("error" in msg) {
|
|
101
|
+
if (typeof msg.error === "string" && HulypulseClient.isConnectionLikeError(msg.error)) {
|
|
102
|
+
console.warn("Pulse server reported connection-like error; reconnecting");
|
|
103
|
+
this.reconnect();
|
|
104
|
+
}
|
|
105
|
+
pending.reject(new Error(msg.error));
|
|
106
|
+
} else {
|
|
107
|
+
pending.resolve(msg);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
console.warn("Unknown message format:", msg);
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.error("Failed to parse message", e);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
resubscribe() {
|
|
121
|
+
for (const [key] of this.subscribes) {
|
|
122
|
+
this.send({ type: "sub", key }).catch((error) => {
|
|
123
|
+
console.error(`Resubscription failed for key=${key}:`, error);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
startPing() {
|
|
128
|
+
this.stopPing();
|
|
129
|
+
this.pingInterval = setInterval(() => {
|
|
130
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
131
|
+
this.ws.send("ping");
|
|
132
|
+
}
|
|
133
|
+
if (this.pingTimeout !== void 0) {
|
|
134
|
+
clearTimeout(this.pingTimeout);
|
|
135
|
+
}
|
|
136
|
+
this.pingTimeout = setTimeout(() => {
|
|
137
|
+
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
138
|
+
console.warn("WS-server not responding to ping, closing connection");
|
|
139
|
+
clearInterval(this.pingInterval);
|
|
140
|
+
this.ws?.close(WS_CLOSE_NORMAL);
|
|
141
|
+
}
|
|
142
|
+
}, this.PING_TIMEOUT_MS);
|
|
143
|
+
}, this.PING_INTERVAL_MS);
|
|
144
|
+
}
|
|
145
|
+
stopPing() {
|
|
146
|
+
if (this.pingInterval !== void 0) {
|
|
147
|
+
clearInterval(this.pingInterval);
|
|
148
|
+
this.pingInterval = void 0;
|
|
149
|
+
}
|
|
150
|
+
if (this.pingTimeout !== void 0) {
|
|
151
|
+
clearTimeout(this.pingTimeout);
|
|
152
|
+
this.pingTimeout = void 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
[Symbol.dispose]() {
|
|
156
|
+
this.close();
|
|
157
|
+
}
|
|
158
|
+
reconnect() {
|
|
159
|
+
if (this.reconnectTimeout !== void 0) {
|
|
160
|
+
clearTimeout(this.reconnectTimeout);
|
|
161
|
+
this.reconnectTimeout = void 0;
|
|
162
|
+
}
|
|
163
|
+
this.stopPing();
|
|
164
|
+
if (!this.closed_manually) {
|
|
165
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
166
|
+
void this.connect();
|
|
167
|
+
}, this.RECONNECT_INTERVAL_MS);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
close() {
|
|
171
|
+
this.closed_manually = true;
|
|
172
|
+
if (this.reconnectTimeout !== void 0) {
|
|
173
|
+
clearTimeout(this.reconnectTimeout);
|
|
174
|
+
}
|
|
175
|
+
this.reconnectTimeout = void 0;
|
|
176
|
+
this.stopPing();
|
|
177
|
+
this.ws?.close();
|
|
178
|
+
}
|
|
179
|
+
static async connect(url) {
|
|
180
|
+
const client = new HulypulseClient(url);
|
|
181
|
+
await client.connect();
|
|
182
|
+
return client;
|
|
183
|
+
}
|
|
184
|
+
async info() {
|
|
185
|
+
const reply = await this.send({ type: "info" });
|
|
186
|
+
if (reply.error !== void 0) {
|
|
187
|
+
throw new Error(reply.error);
|
|
188
|
+
}
|
|
189
|
+
return reply.result ?? "";
|
|
190
|
+
}
|
|
191
|
+
async list() {
|
|
192
|
+
const reply = await this.send({ type: "list" });
|
|
193
|
+
if (reply.error !== void 0) {
|
|
194
|
+
throw new Error(reply.error);
|
|
195
|
+
}
|
|
196
|
+
return reply.result ?? "";
|
|
197
|
+
}
|
|
198
|
+
async subscribe(key, callback) {
|
|
199
|
+
let list = this.subscribes.get(key);
|
|
200
|
+
if (list === void 0) {
|
|
201
|
+
list = [];
|
|
202
|
+
this.subscribes.set(key, list);
|
|
203
|
+
}
|
|
204
|
+
if (!list.includes(callback)) {
|
|
205
|
+
list.push(callback);
|
|
206
|
+
if (list.length === 1) {
|
|
207
|
+
void await this.send({ type: "sub", key });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const prevlist = await this.send({ type: "list", key });
|
|
211
|
+
if (prevlist?.error === void 0 && Array.isArray(prevlist?.result)) {
|
|
212
|
+
for (const item of prevlist.result) {
|
|
213
|
+
try {
|
|
214
|
+
const value = item.data !== void 0 ? JSON.parse(item.data) : void 0;
|
|
215
|
+
if ((item.ttl ?? item.expires_at ?? 0) <= 1 || // TODO: remove expires_at after upgrade server
|
|
216
|
+
value === void 0) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
callback(item.key, value);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error("Error in initial callback", err);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return async () => {
|
|
226
|
+
return await this.unsubscribe(key, callback);
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
async unsubscribe(key, callback) {
|
|
230
|
+
const list = this.subscribes.get(key);
|
|
231
|
+
if (list === void 0 || !list.includes(callback)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const newList = list.filter((cb) => cb !== callback);
|
|
235
|
+
if (newList.length === 0) {
|
|
236
|
+
this.subscribes.delete(key);
|
|
237
|
+
void await this.send({ type: "unsub", key });
|
|
238
|
+
} else {
|
|
239
|
+
this.subscribes.set(key, newList);
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
async put(key, data, third) {
|
|
244
|
+
const message = {
|
|
245
|
+
type: "put",
|
|
246
|
+
key,
|
|
247
|
+
data: JSON.stringify(data),
|
|
248
|
+
...typeof third === "number" ? { TTL: third } : third
|
|
249
|
+
};
|
|
250
|
+
void await this.send(message);
|
|
251
|
+
}
|
|
252
|
+
async put_full(key, data, third) {
|
|
253
|
+
const message = {
|
|
254
|
+
type: "put",
|
|
255
|
+
key,
|
|
256
|
+
data: JSON.stringify(data),
|
|
257
|
+
...typeof third === "number" ? { TTL: third } : third
|
|
258
|
+
};
|
|
259
|
+
const reply = await this.send(message);
|
|
260
|
+
if (reply.error !== void 0) {
|
|
261
|
+
throw new Error(reply.error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async get(key) {
|
|
265
|
+
const reply = await this.send({ type: "get", key });
|
|
266
|
+
if (reply.error !== void 0) {
|
|
267
|
+
if (reply.error === "not found") {
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
throw new Error(reply.error);
|
|
271
|
+
}
|
|
272
|
+
return reply.result?.data;
|
|
273
|
+
}
|
|
274
|
+
async get_full(key) {
|
|
275
|
+
const reply = await this.send({ type: "get", key });
|
|
276
|
+
if (reply.error !== void 0) {
|
|
277
|
+
if (reply.error === "not found") {
|
|
278
|
+
return void 0;
|
|
279
|
+
}
|
|
280
|
+
throw new Error(reply.error);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
data: reply.result.data,
|
|
284
|
+
etag: reply.result.etag,
|
|
285
|
+
expiresAt: reply.result.expiresAt
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async delete(key, options) {
|
|
289
|
+
const message = { type: "delete", key, ...options };
|
|
290
|
+
const reply = await this.send(message);
|
|
291
|
+
if (reply.error !== void 0) {
|
|
292
|
+
if (reply.error === "not found") {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
throw new Error(reply.error);
|
|
296
|
+
}
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
async send(msg) {
|
|
300
|
+
const id = String(this.correlationId++);
|
|
301
|
+
const message = { ...msg, correlation: id.toString() };
|
|
302
|
+
if (this.closed_manually) {
|
|
303
|
+
if (msg.type === "unsub") {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
this.closed_manually = false;
|
|
307
|
+
if (this.ws?.readyState === WebSocket.CONNECTING || this.ws?.readyState === WebSocket.OPEN) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
await this.connect();
|
|
311
|
+
}
|
|
312
|
+
return await new Promise((resolve, reject) => {
|
|
313
|
+
if (this.closed_manually || this.ws?.readyState !== WebSocket.OPEN) {
|
|
314
|
+
resolve({ error: "WebSocket is not open." });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const sendTimeout = setTimeout(() => {
|
|
318
|
+
const pending = this.pending.get(id);
|
|
319
|
+
if (pending !== void 0) {
|
|
320
|
+
pending.resolve({ error: "Timeout waiting for response" });
|
|
321
|
+
this.pending.delete(id);
|
|
322
|
+
}
|
|
323
|
+
}, this.SEND_TIMEOUT_MS);
|
|
324
|
+
this.pending.set(id, { resolve, reject, send_timeout: sendTimeout });
|
|
325
|
+
this.ws.send(JSON.stringify(message));
|
|
326
|
+
this.startPing();
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function escapeString(str) {
|
|
331
|
+
return str.replace(/[*?\[\]\\\x00-\x1F\x7F"']/g, "_");
|
|
332
|
+
}
|
|
333
|
+
__name(escapeString, "escapeString");
|
|
334
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/client.ts"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright \u00A9 2025 Hardcore Engineering Inc.\n//\n// Licensed under the Eclipse Public License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License. You may\n// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\nconst WS_CLOSE_NORMAL = 1000\n\nexport type UnsubscribeCallback = () => Promise<boolean>\n\nexport type Callback<T> = (key: string, data: T | undefined) => void\n\ninterface GetFullResult<T> {\n data: T\n etag: string\n expiresAt: number\n}\n\ntype Command = 'sub' | 'unsub' | 'put' | 'get' | 'delete' | 'list' | 'sublist' | 'info'\n\ninterface SubscribedMessage {\n message: 'Set' | 'Del' | 'Unlink' | 'Expired'\n key: string\n value?: string\n}\n\ninterface CommandMessage {\n command: Command\n correlation: string\n result: any\n}\n\ninterface ErrorCommandMessage {\n error: string\n command: Command\n correlation: string\n}\n\ntype PulseIncomingMessage = SubscribedMessage | CommandMessage | ErrorCommandMessage\n\ninterface GetMessage {\n type: 'get'\n key: string\n}\n\ninterface PutMessage {\n type: 'put'\n key: string\n data: string\n TTL?: number\n expiresAt?: number\n ifMatch?: string\n ifNoneMatch?: string\n}\n\ninterface DeleteMessage {\n type: 'delete'\n key: string\n ifMatch?: string\n}\n\ninterface SubscribeMessages {\n type: 'sub'\n key: string\n}\n\ninterface UnsubscribeMessages {\n type: 'unsub'\n key: string\n}\n\ninterface SubscribesList {\n type: 'list'\n}\n\ninterface InfoMessage {\n type: 'info'\n}\n\ntype ProtocolMessage =\n | GetMessage\n | PutMessage\n | DeleteMessage\n | SubscribeMessages\n | UnsubscribeMessages\n | SubscribesList\n | InfoMessage\n\nexport class HulypulseClient implements Disposable {\n private ws: WebSocket | null = null\n private closed_manually = false\n private reconnectTimeout: any | undefined\n private readonly RECONNECT_INTERVAL_MS = 1000\n\n private readonly subscribes = new Map<string, Callback<any>[]>()\n\n private pingTimeout: ReturnType<typeof setTimeout> | undefined\n private pingInterval: ReturnType<typeof setInterval> | undefined\n private readonly PING_INTERVAL_MS = 30 * 1000\n private readonly PING_TIMEOUT_MS = 5 * 60 * 1000\n private readonly SEND_TIMEOUT_MS = 3000\n\n private correlationId = 1\n\n private readonly pending = new Map<\n string,\n {\n resolve: (v: any) => void\n reject: (e: any) => void\n send_timeout: ReturnType<typeof setTimeout>\n }\n >()\n\n private constructor (private readonly url: string | URL) {}\n\n /** Matches errors from Pulse when Redis/backend TCP is dead while WS still looks open. */\n private static isConnectionLikeError (err: string): boolean {\n const s = err.toLowerCase()\n return (\n s.includes('broken pipe') ||\n s.includes('connection reset') ||\n s.includes('connection refused') ||\n s.includes('connection aborted') ||\n s.includes('unexpected eof') ||\n s.includes('io error')\n )\n }\n\n private async connect (): Promise<void> {\n await new Promise((resolve, reject) => {\n const ws = new WebSocket(this.url.toString())\n this.ws = ws\n\n ws.onopen = () => {\n this.resubscribe()\n this.startPing()\n resolve(undefined)\n }\n\n ws.onerror = (event) => {\n this.reconnect()\n }\n\n ws.onclose = (event) => {\n this.reconnect()\n }\n\n ws.onmessage = (event) => {\n try {\n if (event.data === 'ping') {\n this.ws?.send('pong')\n return\n }\n if (event.data === 'pong') {\n if (this.pingTimeout !== undefined) {\n clearTimeout(this.pingTimeout)\n this.pingTimeout = undefined\n }\n return\n }\n\n const msg: PulseIncomingMessage = JSON.parse(event.data.toString())\n\n // Handle incoming messages (Set, Expired, Del)\n if ('message' in msg) {\n for (const [key, callbacks] of this.subscribes) {\n if (msg.key.startsWith(key)) {\n callbacks.forEach((cb, index) => {\n try {\n const value = msg.message === 'Set' && msg.value !== undefined ? JSON.parse(msg.value) : undefined\n cb(msg.key, value)\n } catch (err) {\n console.error(`Error in callback #${index} with key \"${key}\":`, err)\n }\n })\n }\n }\n } else if ('correlation' in msg) {\n const id = msg.correlation\n if (id !== undefined && this.pending.has(id)) {\n const pending = this.pending.get(id)\n if (pending !== undefined) {\n clearTimeout(pending.send_timeout)\n this.pending.delete(id)\n if ('error' in msg) {\n if (typeof msg.error === 'string' && HulypulseClient.isConnectionLikeError(msg.error)) {\n console.warn('Pulse server reported connection-like error; reconnecting')\n this.reconnect()\n }\n pending.reject(new Error(msg.error))\n } else {\n pending.resolve(msg)\n }\n }\n }\n } else {\n console.warn('Unknown message format:', msg)\n }\n } catch (e) {\n console.error('Failed to parse message', e)\n }\n }\n })\n }\n\n private resubscribe (): void {\n for (const [key] of this.subscribes) {\n this.send({ type: 'sub', key }).catch((error) => {\n console.error(`Resubscription failed for key=${key}:`, error)\n // throw new Error(`Resubscription failed for key=${key}: ${error.message ?? error}`)\n })\n }\n }\n\n private startPing (): void {\n this.stopPing()\n this.pingInterval = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send('ping')\n }\n if (this.pingTimeout !== undefined) {\n clearTimeout(this.pingTimeout)\n }\n this.pingTimeout = setTimeout(() => {\n if (this.ws?.readyState !== WebSocket.OPEN) {\n console.warn('WS-server not responding to ping, closing connection')\n clearInterval(this.pingInterval)\n this.ws?.close(WS_CLOSE_NORMAL)\n }\n }, this.PING_TIMEOUT_MS)\n }, this.PING_INTERVAL_MS)\n }\n\n private stopPing (): void {\n if (this.pingInterval !== undefined) {\n clearInterval(this.pingInterval)\n this.pingInterval = undefined\n }\n if (this.pingTimeout !== undefined) {\n clearTimeout(this.pingTimeout)\n this.pingTimeout = undefined\n }\n }\n\n [Symbol.dispose] (): void {\n this.close()\n }\n\n private reconnect (): void {\n if (this.reconnectTimeout !== undefined) {\n clearTimeout(this.reconnectTimeout)\n this.reconnectTimeout = undefined\n }\n this.stopPing()\n\n if (!this.closed_manually) {\n this.reconnectTimeout = setTimeout(() => {\n void this.connect()\n }, this.RECONNECT_INTERVAL_MS)\n }\n }\n\n public close (): void {\n this.closed_manually = true\n if (this.reconnectTimeout !== undefined) {\n clearTimeout(this.reconnectTimeout)\n }\n this.reconnectTimeout = undefined\n this.stopPing()\n this.ws?.close()\n }\n\n static async connect (url: string | URL): Promise<HulypulseClient> {\n const client = new HulypulseClient(url)\n await client.connect()\n return client\n }\n\n public async info (): Promise<string> {\n const reply = await this.send({ type: 'info' })\n if (reply.error !== undefined) {\n throw new Error(reply.error)\n }\n return reply.result ?? ''\n }\n\n public async list (): Promise<string> {\n const reply = await this.send({ type: 'list' })\n if (reply.error !== undefined) {\n throw new Error(reply.error)\n }\n return reply.result ?? ''\n }\n\n public async subscribe (key: string, callback: Callback<any>): Promise<UnsubscribeCallback> {\n let list = this.subscribes.get(key)\n if (list === undefined) {\n list = []\n this.subscribes.set(key, list)\n }\n\n if (!list.includes(callback)) {\n // Already subscribed?\n list.push(callback)\n if (list.length === 1) {\n void (await this.send({ type: 'sub', key }))\n }\n }\n\n // callback for every old item (ttl > 1 sec for atomicity)\n const prevlist = await this.send({ type: 'list', key })\n if (prevlist?.error === undefined && Array.isArray(prevlist?.result)) {\n for (const item of prevlist.result) {\n try {\n const value = item.data !== undefined ? JSON.parse(item.data) : undefined\n if (\n (item.ttl ?? item.expires_at ?? 0) <= 1 || // TODO: remove expires_at after upgrade server\n value === undefined\n ) {\n continue\n }\n callback(item.key, value)\n } catch (err) {\n // throw new Error(err)\n console.error('Error in initial callback', err)\n }\n }\n }\n\n return async () => {\n return await this.unsubscribe(key, callback)\n }\n }\n\n public async unsubscribe (key: string, callback: Callback<any>): Promise<boolean> {\n const list = this.subscribes.get(key)\n if (list === undefined || !list.includes(callback)) {\n return false\n }\n const newList = list.filter((cb) => cb !== callback)\n if (newList.length === 0) {\n this.subscribes.delete(key)\n void (await this.send({ type: 'unsub', key }))\n } else {\n this.subscribes.set(key, newList)\n }\n return true\n }\n\n public async put (key: string, data: any, ttl: number): Promise<void>\n public async put (\n key: string,\n data: any,\n options?: Pick<PutMessage, 'ifMatch' | 'ifNoneMatch' | 'TTL' | 'expiresAt'>\n ): Promise<void>\n public async put (\n key: string,\n data: any,\n third?: number | Pick<PutMessage, 'ifMatch' | 'ifNoneMatch' | 'TTL' | 'expiresAt'>\n ): Promise<void> {\n const message: Omit<PutMessage, 'correlation'> = {\n type: 'put',\n key,\n data: JSON.stringify(data),\n ...(typeof third === 'number' ? { TTL: third } : third)\n }\n void (await this.send(message))\n }\n\n public async put_full (key: string, data: any, ttl: number): Promise<any>\n public async put_full (\n key: string,\n data: any,\n options?: Pick<PutMessage, 'ifMatch' | 'ifNoneMatch' | 'TTL' | 'expiresAt'>\n ): Promise<void>\n public async put_full (\n key: string,\n data: any,\n third?: number | Pick<PutMessage, 'ifMatch' | 'ifNoneMatch' | 'TTL' | 'expiresAt'>\n ): Promise<void> {\n const message: Omit<PutMessage, 'correlation'> = {\n type: 'put',\n key,\n data: JSON.stringify(data),\n ...(typeof third === 'number' ? { TTL: third } : third)\n }\n const reply = await this.send(message)\n if (reply.error !== undefined) {\n throw new Error(reply.error)\n }\n }\n\n public async get<T>(key: string): Promise<T | undefined> {\n const reply = await this.send({ type: 'get', key })\n if (reply.error !== undefined) {\n if (reply.error === 'not found') {\n return undefined\n }\n throw new Error(reply.error)\n }\n return reply.result?.data\n }\n\n public async get_full<T>(key: string): Promise<GetFullResult<T> | undefined> {\n const reply = await this.send({ type: 'get', key })\n if (reply.error !== undefined) {\n if (reply.error === 'not found') {\n return undefined\n }\n throw new Error(reply.error)\n }\n return {\n data: reply.result.data,\n etag: reply.result.etag,\n expiresAt: reply.result.expiresAt\n }\n }\n\n public async delete (key: string, options?: Pick<DeleteMessage, 'ifMatch'>): Promise<boolean> {\n const message: Omit<DeleteMessage, 'correlation'> = { type: 'delete', key, ...options }\n const reply = await this.send(message)\n if (reply.error !== undefined) {\n if (reply.error === 'not found') {\n return false\n }\n throw new Error(reply.error)\n }\n return true\n }\n\n private async send<M extends Omit<ProtocolMessage, 'correlation'>>(msg: M): Promise<any> {\n const id = String(this.correlationId++)\n const message = { ...msg, correlation: id.toString() } satisfies M\n\n // Reconnect if need before\n if (this.closed_manually) {\n if (msg.type === 'unsub') {\n // don't need to do anything\n return\n }\n this.closed_manually = false\n if (this.ws?.readyState === WebSocket.CONNECTING || this.ws?.readyState === WebSocket.OPEN) {\n return\n }\n await this.connect()\n }\n\n return await new Promise((resolve, reject) => {\n if (this.closed_manually || this.ws?.readyState !== WebSocket.OPEN) {\n resolve({ error: 'WebSocket is not open.' })\n return\n }\n const sendTimeout = setTimeout(() => {\n const pending = this.pending.get(id)\n if (pending !== undefined) {\n pending.resolve({ error: 'Timeout waiting for response' })\n this.pending.delete(id)\n }\n }, this.SEND_TIMEOUT_MS)\n this.pending.set(id, { resolve, reject, send_timeout: sendTimeout })\n this.ws.send(JSON.stringify(message))\n this.startPing() // reset ping timer on any send\n })\n }\n}\n\nexport function escapeString (str: string): string {\n // eslint-disable-next-line no-control-regex, no-useless-escape\n return str.replace(/[*?\\[\\]\\\\\\x00-\\x1F\\x7F\"']/g, '_')\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
15
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
16
|
+
var index_exports = {};
|
|
17
|
+
module.exports = __toCommonJS(index_exports);
|
|
18
|
+
__reExport(index_exports, require("./client"), module.exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright \u00A9 2025 Hardcore Engineering Inc.\n//\n// Licensed under the Eclipse Public License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License. You may\n// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\nexport * from './client'\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;AAAA;AAAA;AAeA,0BAAc,qBAfd;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hcengineering/hulypulse-client",
|
|
3
|
+
"version": "0.7.423",
|
|
4
|
+
"main": "lib/index.js",
|
|
5
|
+
"svelte": "src/index.ts",
|
|
6
|
+
"types": "types/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib/**/*",
|
|
9
|
+
"types/**/*",
|
|
10
|
+
"tsconfig.json"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "compile",
|
|
14
|
+
"build:watch": "compile",
|
|
15
|
+
"format": "format src",
|
|
16
|
+
"test": "jest --passWithNoTests --silent",
|
|
17
|
+
"_phase:build": "compile transpile src",
|
|
18
|
+
"_phase:test": "jest --passWithNoTests --silent",
|
|
19
|
+
"_phase:format": "format src",
|
|
20
|
+
"_phase:validate": "compile validate"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"cross-env": "~7.0.3",
|
|
24
|
+
"@hcengineering/platform-rig": "^0.7.423",
|
|
25
|
+
"@types/node": "^22.18.1",
|
|
26
|
+
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
27
|
+
"eslint-plugin-import": "^2.26.0",
|
|
28
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
29
|
+
"eslint-plugin-n": "^15.4.0",
|
|
30
|
+
"eslint": "^8.54.0",
|
|
31
|
+
"esbuild": "^0.25.10",
|
|
32
|
+
"@typescript-eslint/parser": "^6.21.0",
|
|
33
|
+
"eslint-config-standard-with-typescript": "^40.0.0",
|
|
34
|
+
"prettier": "^3.6.2",
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"jest": "^29.7.0",
|
|
37
|
+
"jest-fetch-mock": "^3.0.3",
|
|
38
|
+
"ts-jest": "^29.1.1",
|
|
39
|
+
"@types/jest": "^29.5.5"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@hcengineering/core": "^0.7.423",
|
|
43
|
+
"@hcengineering/platform": "^0.7.423"
|
|
44
|
+
},
|
|
45
|
+
"exports": {
|
|
46
|
+
".": {
|
|
47
|
+
"types": "./types/index.d.ts",
|
|
48
|
+
"require": "./lib/index.js",
|
|
49
|
+
"import": "./lib/index.js"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
|
3
|
+
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "./lib",
|
|
7
|
+
"declarationDir": "./types",
|
|
8
|
+
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
|
9
|
+
},
|
|
10
|
+
"include": ["src/**/*"],
|
|
11
|
+
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
|
|
12
|
+
}
|