@mochabug/adapt-web 0.0.41 → 0.0.45
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/dist/cjs/genproto/mochabugapis/adapt/graph/vertex_config_pb.js +31 -0
- package/dist/cjs/genproto/mochabugapis/adapt/graph/vertex_config_pb.js.map +1 -0
- package/dist/cjs/index.js +290 -193
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/genproto/mochabugapis/adapt/graph/vertex_config_pb.js +28 -0
- package/dist/esm/genproto/mochabugapis/adapt/graph/vertex_config_pb.js.map +1 -0
- package/dist/esm/index.js +290 -193
- package/dist/esm/index.js.map +1 -1
- package/dist/types/genproto/mochabugapis/adapt/graph/vertex_config_pb.d.ts +108 -0
- package/dist/types/index.d.ts +20 -1
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2023, mochabug AB
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.VertexConfigMetadataSchema = exports.file_mochabugapis_adapt_graph_vertex_config = void 0;
|
|
17
|
+
const codegenv1_1 = require("@bufbuild/protobuf/codegenv1");
|
|
18
|
+
const validate_pb_1 = require("../../../buf/validate/validate_pb");
|
|
19
|
+
const exchange_pb_1 = require("./exchange_pb");
|
|
20
|
+
const receiver_pb_1 = require("./receiver_pb");
|
|
21
|
+
const transmitter_pb_1 = require("./transmitter_pb");
|
|
22
|
+
/**
|
|
23
|
+
* Describes the file mochabugapis/adapt/graph/vertex_config.proto.
|
|
24
|
+
*/
|
|
25
|
+
exports.file_mochabugapis_adapt_graph_vertex_config = (0, codegenv1_1.fileDesc)("Cixtb2NoYWJ1Z2FwaXMvYWRhcHQvZ3JhcGgvdmVydGV4X2NvbmZpZy5wcm90bxIYbW9jaGFidWdhcGlzLmFkYXB0LmdyYXBoIrkGChRWZXJ0ZXhDb25maWdNZXRhZGF0YRIQCghjb21wbGV0ZRgBIAEoCBKwAQoJcmVjZWl2ZXJzGAIgAygLMiIubW9jaGFidWdhcGlzLmFkYXB0LmdyYXBoLlJlY2VpdmVyQnm6SHa6AWwKFnJlY2VpdmVyc191bmlxdWVfbmFtZXMSJkVhY2ggcmVjZWl2ZXIgbXVzdCBoYXZlIGEgdW5pcXVlIG5hbWUuGip0aGlzLm1hcChyZWNlaXZlciwgcmVjZWl2ZXIubmFtZSkudW5pcXVlKCmSAQQIARAyEsIBCgx0cmFuc21pdHRlcnMYAyADKAsyJS5tb2NoYWJ1Z2FwaXMuYWRhcHQuZ3JhcGguVHJhbnNtaXR0ZXJChAG6SIABugF4Chl0cmFuc21pdHRlcnNfdW5pcXVlX25hbWVzEilFYWNoIHRyYW5zbWl0dGVyIG11c3QgaGF2ZSBhIHVuaXF1ZSBuYW1lLhowdGhpcy5tYXAodHJhbnNtaXR0ZXIsIHRyYW5zbWl0dGVyLm5hbWUpLnVuaXF1ZSgpkgECEDISswEKCnByb2NlZHVyZXMYBCADKAsyIi5tb2NoYWJ1Z2FwaXMuYWRhcHQuZ3JhcGguRXhjaGFuZ2VCe7pIeLoBcAoXcHJvY2VkdXJlc191bmlxdWVfbmFtZXMSJ0VhY2ggcHJvY2VkdXJlIG11c3QgaGF2ZSBhIHVuaXF1ZSBuYW1lLhosdGhpcy5tYXAocHJvY2VkdXJlLCBwcm9jZWR1cmUubmFtZSkudW5pcXVlKCmSAQIQChKkAQoHc3RyZWFtcxgFIAMoCzIiLm1vY2hhYnVnYXBpcy5hZGFwdC5ncmFwaC5FeGNoYW5nZUJvukhsugFkChRzdHJlYW1zX3VuaXF1ZV9uYW1lcxIkRWFjaCBzdHJlYW0gbXVzdCBoYXZlIGEgdW5pcXVlIG5hbWUuGiZ0aGlzLm1hcChzdHJlYW0sIHN0cmVhbS5uYW1lKS51bmlxdWUoKZIBAhAKEigKDWNyb25fc2NoZWR1bGUYBiABKAlCDLpICdgBAnIEEAEYZEgAiAEBQhAKDl9jcm9uX3NjaGVkdWxlYgZwcm90bzM", [validate_pb_1.file_buf_validate_validate, exchange_pb_1.file_mochabugapis_adapt_graph_exchange, receiver_pb_1.file_mochabugapis_adapt_graph_receiver, transmitter_pb_1.file_mochabugapis_adapt_graph_transmitter]);
|
|
26
|
+
/**
|
|
27
|
+
* Describes the message mochabugapis.adapt.graph.VertexConfigMetadata.
|
|
28
|
+
* Use `create(VertexConfigMetadataSchema)` to create a new message.
|
|
29
|
+
*/
|
|
30
|
+
exports.VertexConfigMetadataSchema = (0, codegenv1_1.messageDesc)(exports.file_mochabugapis_adapt_graph_vertex_config, 0);
|
|
31
|
+
//# sourceMappingURL=vertex_config_pb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vertex_config_pb.js","sourceRoot":"","sources":["../../../../../../src/genproto/mochabugapis/adapt/graph/vertex_config_pb.ts"],"names":[],"mappings":";AAAA,8BAA8B;AAC9B,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,iDAAiD;AACjD,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;;;AAQjC,4DAAqE;AACrE,mEAA+E;AAE/E,+CAAuE;AAEvE,+CAAuE;AAEvE,qDAA6E;AAG7E;;GAEG;AACU,QAAA,2CAA2C,GACtD,IAAA,oBAAQ,EAAC,6rCAA6rC,EAAE,CAAC,wCAA0B,EAAE,oDAAsC,EAAE,oDAAsC,EAAE,0DAAyC,CAAC,CAAC,CAAC;AA4Gn2C;;;GAGG;AACU,QAAA,0BAA0B,GACrC,IAAA,uBAAW,EAAC,mDAA2C,EAAE,CAAC,CAAC,CAAC"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -11,8 +11,26 @@ const protobuf_1 = require("@bufbuild/protobuf");
|
|
|
11
11
|
const wkt_1 = require("@bufbuild/protobuf/wkt");
|
|
12
12
|
const nats_ws_1 = require("nats.ws");
|
|
13
13
|
const automations_pb_js_1 = require("./genproto/mochabugapis/adapt/automations/v1/automations_pb.js");
|
|
14
|
+
// Constants for JetStream protocol
|
|
15
|
+
const CONSUMER_MSG_NEXT_PREFIX = "$JS.API.CONSUMER.MSG.NEXT.";
|
|
16
|
+
//const ACK_PREFIX = "$JS.ACK.";
|
|
17
|
+
const ACK_POSITIVE = new TextEncoder().encode("+ACK");
|
|
18
|
+
const ACK_NEGATIVE = new TextEncoder().encode("-NAK");
|
|
19
|
+
//const ACK_IN_PROGRESS = new TextEncoder().encode("+WPI");
|
|
20
|
+
//const ACK_TERMINATE = new TextEncoder().encode("+TERM");
|
|
21
|
+
// JetStream Status Codes
|
|
22
|
+
const STATUS_NO_MESSAGES = 404;
|
|
23
|
+
const STATUS_REQUEST_TIMEOUT = 408;
|
|
24
|
+
const STATUS_MAX_ACK_PENDING = 409;
|
|
25
|
+
const STATUS_FLOW_CONTROL = 100;
|
|
26
|
+
// Default configuration
|
|
27
|
+
const DEFAULT_MAX_MESSAGES = 100;
|
|
28
|
+
const DEFAULT_EXPIRES_MS = 30000;
|
|
29
|
+
const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
30
|
+
const DEFAULT_THRESHOLD_RATIO = 0.5;
|
|
14
31
|
const natsUrl = "wss://adapt-dev.mochabugapis.com:443/pubsub";
|
|
15
32
|
const baseUrl = "https://adapt-dev.mochabugapis.com/v1/automations";
|
|
33
|
+
// REST API functions (unchanged from original)
|
|
16
34
|
async function startSession(req, token) {
|
|
17
35
|
const headers = new Headers({
|
|
18
36
|
"Content-Type": "application/json",
|
|
@@ -106,6 +124,7 @@ class RestClientError extends Error {
|
|
|
106
124
|
}
|
|
107
125
|
}
|
|
108
126
|
exports.RestClientError = RestClientError;
|
|
127
|
+
// Connection states
|
|
109
128
|
var ConnectionState;
|
|
110
129
|
(function (ConnectionState) {
|
|
111
130
|
ConnectionState["DISCONNECTED"] = "disconnected";
|
|
@@ -125,15 +144,17 @@ class PubsubClient {
|
|
|
125
144
|
this.urlHandler = null;
|
|
126
145
|
this.isReconnecting = false;
|
|
127
146
|
this.connectionState = ConnectionState.DISCONNECTED;
|
|
147
|
+
// Pull consumer state
|
|
148
|
+
this.pending = 0;
|
|
149
|
+
this.pulling = false;
|
|
150
|
+
this.pullTimer = null;
|
|
151
|
+
this.maxMessages = DEFAULT_MAX_MESSAGES;
|
|
152
|
+
this.expiresMs = DEFAULT_EXPIRES_MS;
|
|
153
|
+
this.pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;
|
|
154
|
+
this.threshold = DEFAULT_MAX_MESSAGES * DEFAULT_THRESHOLD_RATIO;
|
|
155
|
+
this.inbox = null;
|
|
156
|
+
this.jc = (0, nats_ws_1.JSONCodec)();
|
|
128
157
|
this.debug = debug;
|
|
129
|
-
this.subscribe = this.subscribe.bind(this);
|
|
130
|
-
this.unsubscribe = this.unsubscribe.bind(this);
|
|
131
|
-
this.createSubscriptions = this.createSubscriptions.bind(this);
|
|
132
|
-
this.clearSubscriptions = this.clearSubscriptions.bind(this);
|
|
133
|
-
this.resubscribe = this.resubscribe.bind(this);
|
|
134
|
-
this.setupStatusMonitoring = this.setupStatusMonitoring.bind(this);
|
|
135
|
-
this.isConnected = this.isConnected.bind(this);
|
|
136
|
-
this.getConnectionState = this.getConnectionState.bind(this);
|
|
137
158
|
}
|
|
138
159
|
log(level, message, data) {
|
|
139
160
|
if (!this.debug && level === "debug")
|
|
@@ -162,7 +183,9 @@ class PubsubClient {
|
|
|
162
183
|
}
|
|
163
184
|
// If we're already connected to the same subject, just update handlers
|
|
164
185
|
if (this.connectionState === ConnectionState.CONNECTED &&
|
|
165
|
-
this.subject === opts.subject
|
|
186
|
+
this.subject === opts.subject &&
|
|
187
|
+
this.streamName === opts.stream &&
|
|
188
|
+
this.consumerName === opts.consumer) {
|
|
166
189
|
this.log("debug", "Already connected to same subject, updating handlers only");
|
|
167
190
|
this.outputHandler = opts.onOutput || null;
|
|
168
191
|
this.sessionHandler = opts.onSession || null;
|
|
@@ -179,13 +202,18 @@ class PubsubClient {
|
|
|
179
202
|
}
|
|
180
203
|
try {
|
|
181
204
|
this.connectionState = ConnectionState.CONNECTING;
|
|
182
|
-
// Store
|
|
205
|
+
// Store configuration
|
|
183
206
|
this.subject = opts.subject;
|
|
184
207
|
this.streamName = opts.stream;
|
|
185
208
|
this.consumerName = opts.consumer;
|
|
186
209
|
this.outputHandler = opts.onOutput || null;
|
|
187
210
|
this.sessionHandler = opts.onSession || null;
|
|
188
211
|
this.urlHandler = opts.onUrl || null;
|
|
212
|
+
this.maxMessages = opts.maxMessages || DEFAULT_MAX_MESSAGES;
|
|
213
|
+
this.expiresMs = opts.expiresMs || DEFAULT_EXPIRES_MS;
|
|
214
|
+
this.pollIntervalMs = opts.pollIntervalMs || DEFAULT_POLL_INTERVAL_MS;
|
|
215
|
+
this.threshold = Math.floor(this.maxMessages * DEFAULT_THRESHOLD_RATIO);
|
|
216
|
+
// Connect to NATS
|
|
189
217
|
const inboxPrefix = `_INBOX_${opts.consumer}`;
|
|
190
218
|
this.log("debug", `Connecting with inbox prefix: ${inboxPrefix}`);
|
|
191
219
|
this.nc = await (0, nats_ws_1.connect)({
|
|
@@ -201,16 +229,11 @@ class PubsubClient {
|
|
|
201
229
|
this.connectionState = ConnectionState.CONNECTED;
|
|
202
230
|
this.log("info", "Connected successfully");
|
|
203
231
|
this.setupStatusMonitoring();
|
|
204
|
-
await this.
|
|
232
|
+
await this.createPullSubscription();
|
|
205
233
|
}
|
|
206
234
|
catch (error) {
|
|
207
235
|
this.connectionState = ConnectionState.DISCONNECTED;
|
|
208
|
-
this.
|
|
209
|
-
this.streamName = null;
|
|
210
|
-
this.consumerName = null;
|
|
211
|
-
this.outputHandler = null;
|
|
212
|
-
this.sessionHandler = null;
|
|
213
|
-
this.urlHandler = null;
|
|
236
|
+
this.cleanupState();
|
|
214
237
|
if (this.nc) {
|
|
215
238
|
try {
|
|
216
239
|
await this.nc.close();
|
|
@@ -225,6 +248,8 @@ class PubsubClient {
|
|
|
225
248
|
}
|
|
226
249
|
}
|
|
227
250
|
setupStatusMonitoring() {
|
|
251
|
+
if (!this.nc)
|
|
252
|
+
return;
|
|
228
253
|
(async () => {
|
|
229
254
|
try {
|
|
230
255
|
for await (const status of this.nc.status()) {
|
|
@@ -249,32 +274,24 @@ class PubsubClient {
|
|
|
249
274
|
}
|
|
250
275
|
break;
|
|
251
276
|
case "error":
|
|
252
|
-
// Usually permissions errors
|
|
253
277
|
this.log("error", "Connection error:", status.data);
|
|
254
278
|
break;
|
|
255
279
|
case "update":
|
|
256
|
-
// Cluster configuration update
|
|
257
280
|
this.log("debug", "Cluster update received:", status.data);
|
|
258
281
|
break;
|
|
259
282
|
case "ldm":
|
|
260
|
-
// Lame Duck Mode - server is requesting clients to reconnect
|
|
261
283
|
this.log("warn", "Server requesting reconnection (LDM)");
|
|
262
284
|
break;
|
|
263
|
-
// Debug events - usually can be safely ignored but useful for debugging
|
|
264
285
|
case "reconnecting":
|
|
265
286
|
this.log("debug", "Attempting to reconnect...");
|
|
266
287
|
break;
|
|
267
|
-
case "pingTimer":
|
|
268
|
-
// Ping timer events - very verbose, only log if debug is enabled
|
|
269
|
-
if (this.debug) {
|
|
270
|
-
this.log("debug", "Ping timer event");
|
|
271
|
-
}
|
|
272
|
-
break;
|
|
273
288
|
case "staleConnection":
|
|
274
289
|
this.log("warn", "Connection is stale, may reconnect soon");
|
|
275
290
|
break;
|
|
276
291
|
default:
|
|
277
|
-
|
|
292
|
+
if (status.type !== "pingTimer" || this.debug) {
|
|
293
|
+
this.log("debug", `Status event: ${status.type}`, status.data);
|
|
294
|
+
}
|
|
278
295
|
}
|
|
279
296
|
}
|
|
280
297
|
}
|
|
@@ -285,180 +302,248 @@ class PubsubClient {
|
|
|
285
302
|
}
|
|
286
303
|
})();
|
|
287
304
|
}
|
|
288
|
-
async
|
|
305
|
+
async createPullSubscription() {
|
|
306
|
+
if (!this.nc || !this.streamName || !this.consumerName) {
|
|
307
|
+
throw new Error("Not properly initialized");
|
|
308
|
+
}
|
|
289
309
|
await this.clearSubscriptions();
|
|
290
310
|
try {
|
|
291
|
-
this.log("info", `Creating pull subscription for consumer: ${consumerName} on stream: ${streamName}`);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const inbox = (0, nats_ws_1.createInbox)(`_INBOX_${consumerName}`);
|
|
296
|
-
const sub = this.nc.subscribe(inbox);
|
|
311
|
+
this.log("info", `Creating pull subscription for consumer: ${this.consumerName} on stream: ${this.streamName}`);
|
|
312
|
+
// Create inbox for receiving messages
|
|
313
|
+
this.inbox = (0, nats_ws_1.createInbox)(`_INBOX_${this.consumerName}`);
|
|
314
|
+
const sub = this.nc.subscribe(this.inbox);
|
|
297
315
|
this.pullSubscription = sub;
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
316
|
+
// Reset state
|
|
317
|
+
this.pending = 0;
|
|
318
|
+
this.pulling = false;
|
|
319
|
+
// Start message processing loop
|
|
320
|
+
this.processMessages(sub);
|
|
321
|
+
// Initial pull - use no_wait to check for immediate messages
|
|
322
|
+
this.log("debug", `Initial no_wait pull for ${this.maxMessages} messages`);
|
|
323
|
+
await this.pull(this.maxMessages, true);
|
|
324
|
+
// Schedule periodic pulls for long polling
|
|
325
|
+
this.schedulePull();
|
|
326
|
+
this.log("info", "Pull subscription created successfully");
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.log("error", "Failed to create subscriptions:", error);
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async processMessages(sub) {
|
|
334
|
+
try {
|
|
335
|
+
for await (const msg of sub) {
|
|
336
|
+
if (this.connectionState === ConnectionState.CLOSING ||
|
|
337
|
+
this.connectionState === ConnectionState.DISCONNECTED) {
|
|
338
|
+
this.log("info", "Subscription closing");
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
309
341
|
try {
|
|
310
|
-
|
|
311
|
-
const request = {
|
|
312
|
-
batch,
|
|
313
|
-
expires: (pollInterval + 500) * 1000000, // Poll interval + 500ms buffer, in nanoseconds
|
|
314
|
-
};
|
|
315
|
-
this.nc.publish(pullSubject, jc.encode(request), { reply: inbox });
|
|
316
|
-
pending += batch;
|
|
317
|
-
this.log("debug", `Pull request sent: batch=${batch}, pending=${pending}, expires=${request.expires}ns`);
|
|
342
|
+
await this.handleMessage(msg);
|
|
318
343
|
}
|
|
319
|
-
|
|
320
|
-
|
|
344
|
+
catch (error) {
|
|
345
|
+
this.log("error", "Error handling message:", error);
|
|
321
346
|
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
if (this.connectionState !== ConnectionState.CLOSING) {
|
|
351
|
+
this.log("error", "Message processing error:", error);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async handleMessage(msg) {
|
|
356
|
+
// Headers structure: has _code/_description directly, plus nested headers Map
|
|
357
|
+
const status = msg.headers.code;
|
|
358
|
+
const description = msg.headers.description;
|
|
359
|
+
this.log("debug", `Message: status=${status}, hasData=${msg.data?.length > 0}, subject=${msg.subject}`);
|
|
360
|
+
// Handle status messages (404, 408, 409, etc.)
|
|
361
|
+
if (status) {
|
|
362
|
+
await this.handleStatusMessage(status, description, msg);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// Check for flow control
|
|
366
|
+
const flowControl = msg.headers.get("Nats-Msg-Size");
|
|
367
|
+
if (flowControl?.[0] === "FlowControl") {
|
|
368
|
+
this.log("debug", "Received flow control request, responding");
|
|
369
|
+
msg.respond();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
// Check if this is an actual data message
|
|
373
|
+
if (!msg.data || msg.data.length === 0) {
|
|
374
|
+
this.log("debug", `Empty message on ${msg.subject}`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// Get the actual stream subject from headers
|
|
378
|
+
let streamSubject = msg.headers.get("Nats-Subject")?.[0];
|
|
379
|
+
// Fallback: use the message subject itself if it matches our pattern
|
|
380
|
+
if (!streamSubject && this.subject && !msg.subject.startsWith("_INBOX")) {
|
|
381
|
+
streamSubject = msg.subject;
|
|
382
|
+
}
|
|
383
|
+
if (!streamSubject) {
|
|
384
|
+
this.log("debug", "Message without valid stream subject");
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
// We got a real message
|
|
388
|
+
this.pending = Math.max(0, this.pending - 1);
|
|
389
|
+
this.log("debug", `Message received on ${streamSubject}, pending=${this.pending}`);
|
|
390
|
+
// Process the message
|
|
391
|
+
const parts = streamSubject.split(".");
|
|
392
|
+
const action = parts[parts.length - 1];
|
|
393
|
+
this.log("info", `↓ message: ${action} on ${streamSubject}`);
|
|
394
|
+
try {
|
|
395
|
+
switch (action) {
|
|
396
|
+
case "output": {
|
|
397
|
+
if (this.outputHandler) {
|
|
398
|
+
const output = (0, protobuf_1.fromBinary)(automations_pb_js_1.OutputSchema, msg.data);
|
|
399
|
+
this.outputHandler({
|
|
400
|
+
vertex: output.vertex,
|
|
401
|
+
fork: output.fork,
|
|
402
|
+
data: parseOutputData(output.data),
|
|
403
|
+
created: (0, wkt_1.timestampDate)(output.created),
|
|
404
|
+
});
|
|
339
405
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (this.connectionState === ConnectionState.CLOSING ||
|
|
347
|
-
this.connectionState === ConnectionState.DISCONNECTED) {
|
|
348
|
-
this.log("info", "Subscription closing");
|
|
349
|
-
if (pullTimer)
|
|
350
|
-
clearTimeout(pullTimer);
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
// Check status header
|
|
354
|
-
const status = msg.headers?.get("Status");
|
|
355
|
-
if (status === "404") {
|
|
356
|
-
// No messages available
|
|
357
|
-
this.log("debug", "No messages available (404), resetting pending to 0");
|
|
358
|
-
pending = 0;
|
|
359
|
-
schedulePull();
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
if (status === "408") {
|
|
363
|
-
this.log("debug", "Pull request timed out (408), resetting pending and pulling immediately");
|
|
364
|
-
pending = 0;
|
|
365
|
-
pull(maxMessages); // Immediately pull again instead of scheduling
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
if (status === "409") {
|
|
369
|
-
this.log("warn", "Max ack pending reached (409), waiting 5 seconds before retry");
|
|
370
|
-
pending = 0;
|
|
371
|
-
setTimeout(() => pull(maxMessages), 5000);
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
// Skip if not a real message
|
|
375
|
-
if (!msg.data || msg.data.length === 0 || msg.subject === inbox) {
|
|
376
|
-
this.log("debug", `Skipping non-message: data=${msg.data?.length || 0} bytes, subject=${msg.subject}`);
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
// We got a real message
|
|
380
|
-
pending = Math.max(0, pending - 1);
|
|
381
|
-
this.log("debug", `Message received, pending decreased to ${pending}`);
|
|
382
|
-
// Re-pull if below threshold
|
|
383
|
-
if (pending < threshold && !pulling) {
|
|
384
|
-
const pullCount = maxMessages - pending;
|
|
385
|
-
this.log("debug", `Pending ${pending} < threshold ${threshold}, pulling ${pullCount} more messages`);
|
|
386
|
-
pull(pullCount);
|
|
387
|
-
}
|
|
388
|
-
const parts = msg.subject.split(".");
|
|
389
|
-
const action = parts[parts.length - 1];
|
|
390
|
-
this.log("info", `↓ message: ${action} on ${msg.subject}`);
|
|
391
|
-
try {
|
|
392
|
-
switch (action) {
|
|
393
|
-
case "output": {
|
|
394
|
-
if (this.outputHandler) {
|
|
395
|
-
const output = (0, protobuf_1.fromBinary)(automations_pb_js_1.OutputSchema, msg.data);
|
|
396
|
-
this.outputHandler({
|
|
397
|
-
vertex: output.vertex,
|
|
398
|
-
fork: output.fork,
|
|
399
|
-
data: parseOutputData(output.data),
|
|
400
|
-
created: (0, wkt_1.timestampDate)(output.created),
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
case "session": {
|
|
406
|
-
if (this.sessionHandler) {
|
|
407
|
-
const session = (0, protobuf_1.fromBinary)(automations_pb_js_1.SessionSchema, msg.data);
|
|
408
|
-
this.sessionHandler((0, protobuf_1.toJson)(automations_pb_js_1.SessionSchema, session));
|
|
409
|
-
}
|
|
410
|
-
break;
|
|
411
|
-
}
|
|
412
|
-
case "url": {
|
|
413
|
-
if (this.urlHandler) {
|
|
414
|
-
const url = (0, protobuf_1.fromBinary)(automations_pb_js_1.UrlSchema, msg.data);
|
|
415
|
-
this.urlHandler((0, protobuf_1.toJson)(automations_pb_js_1.UrlSchema, url));
|
|
416
|
-
}
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
default:
|
|
420
|
-
this.log("warn", `Unknown message action: ${action}`);
|
|
421
|
-
}
|
|
422
|
-
// ACK the message
|
|
423
|
-
const ackSuccess = msg.respond();
|
|
424
|
-
if (!ackSuccess) {
|
|
425
|
-
this.log("warn", "Failed to ACK message");
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
this.log("debug", `Message ACKed successfully for action: ${action}`);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
catch (error) {
|
|
432
|
-
this.log("error", "Error handling message:", error);
|
|
433
|
-
// NAK the message for redelivery
|
|
434
|
-
const nakSuccess = msg.respond(new TextEncoder().encode("-NAK"));
|
|
435
|
-
if (!nakSuccess) {
|
|
436
|
-
this.log("warn", "Failed to NAK message");
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
this.log("debug", "Message NAKed for redelivery");
|
|
440
|
-
}
|
|
441
|
-
}
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
case "session": {
|
|
409
|
+
if (this.sessionHandler) {
|
|
410
|
+
const session = (0, protobuf_1.fromBinary)(automations_pb_js_1.SessionSchema, msg.data);
|
|
411
|
+
this.sessionHandler((0, protobuf_1.toJson)(automations_pb_js_1.SessionSchema, session));
|
|
442
412
|
}
|
|
413
|
+
break;
|
|
443
414
|
}
|
|
444
|
-
|
|
445
|
-
if (this.
|
|
446
|
-
|
|
415
|
+
case "url": {
|
|
416
|
+
if (this.urlHandler) {
|
|
417
|
+
const url = (0, protobuf_1.fromBinary)(automations_pb_js_1.UrlSchema, msg.data);
|
|
418
|
+
this.urlHandler((0, protobuf_1.toJson)(automations_pb_js_1.UrlSchema, url));
|
|
447
419
|
}
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
default:
|
|
423
|
+
this.log("warn", `Unknown message action: ${action}`);
|
|
424
|
+
}
|
|
425
|
+
// ACK the message
|
|
426
|
+
if (msg.reply) {
|
|
427
|
+
msg.respond(ACK_POSITIVE);
|
|
428
|
+
this.log("debug", `Message ACKed for ${action}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
this.log("error", "Error processing message:", error);
|
|
433
|
+
// NAK the message for redelivery
|
|
434
|
+
if (msg.reply) {
|
|
435
|
+
msg.respond(ACK_NEGATIVE);
|
|
436
|
+
}
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
// Pull more if below threshold
|
|
440
|
+
if (this.pending < this.threshold && !this.pulling) {
|
|
441
|
+
const pullCount = this.maxMessages - this.pending;
|
|
442
|
+
this.log("debug", `Pulling ${pullCount} more messages`);
|
|
443
|
+
this.pull(pullCount, false);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
async handleStatusMessage(status, description, msg) {
|
|
447
|
+
switch (status) {
|
|
448
|
+
case STATUS_NO_MESSAGES:
|
|
449
|
+
this.log("debug", `No messages available: ${description}`);
|
|
450
|
+
this.pending = 0;
|
|
451
|
+
break;
|
|
452
|
+
case STATUS_REQUEST_TIMEOUT:
|
|
453
|
+
this.log("debug", `Request timeout: ${description}`);
|
|
454
|
+
if (this.pending > 0) {
|
|
455
|
+
this.pending--;
|
|
448
456
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
457
|
+
// Check if we have pending messages info
|
|
458
|
+
const pendingMsgs = msg.headers.get("Nats-Pending-Messages")?.[0];
|
|
459
|
+
if (pendingMsgs) {
|
|
460
|
+
this.log("debug", `Server reports ${pendingMsgs} pending messages`);
|
|
452
461
|
}
|
|
453
|
-
|
|
454
|
-
|
|
462
|
+
break;
|
|
463
|
+
case STATUS_MAX_ACK_PENDING:
|
|
464
|
+
this.log("warn", `Max ack pending: ${description}`);
|
|
465
|
+
this.pending = 0;
|
|
466
|
+
// Wait before retry
|
|
467
|
+
setTimeout(() => {
|
|
468
|
+
if (this.connectionState === ConnectionState.CONNECTED) {
|
|
469
|
+
this.pull(this.maxMessages, false);
|
|
470
|
+
}
|
|
471
|
+
}, 5000);
|
|
472
|
+
break;
|
|
473
|
+
case STATUS_FLOW_CONTROL:
|
|
474
|
+
this.log("debug", `Flow control: ${description}`);
|
|
475
|
+
if (msg.reply) {
|
|
476
|
+
msg.respond();
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
default:
|
|
480
|
+
this.log("debug", `Unknown status ${status}: ${description}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
async pull(batch, noWait = false) {
|
|
484
|
+
if (this.pulling || !this.nc || !this.inbox || batch <= 0) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (this.connectionState !== ConnectionState.CONNECTED) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
this.pulling = true;
|
|
491
|
+
try {
|
|
492
|
+
const pullSubject = `${CONSUMER_MSG_NEXT_PREFIX}${this.streamName}.${this.consumerName}`;
|
|
493
|
+
const request = {
|
|
494
|
+
batch: batch,
|
|
495
|
+
};
|
|
496
|
+
if (noWait) {
|
|
497
|
+
request.no_wait = true;
|
|
498
|
+
}
|
|
499
|
+
else if (this.expiresMs > 0) {
|
|
500
|
+
request.expires = this.expiresMs * 1000000; // Convert to nanoseconds
|
|
501
|
+
}
|
|
502
|
+
this.nc.publish(pullSubject, this.jc.encode(request), {
|
|
503
|
+
reply: this.inbox,
|
|
504
|
+
});
|
|
505
|
+
this.pending += batch;
|
|
506
|
+
this.log("debug", `Pull sent: batch=${batch}, pending=${this.pending}, noWait=${noWait}`);
|
|
455
507
|
}
|
|
456
508
|
catch (error) {
|
|
457
|
-
this.log("error", "
|
|
509
|
+
this.log("error", "Error sending pull request:", error);
|
|
510
|
+
this.pending = Math.max(0, this.pending - batch);
|
|
458
511
|
throw error;
|
|
459
512
|
}
|
|
513
|
+
finally {
|
|
514
|
+
this.pulling = false;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
schedulePull() {
|
|
518
|
+
if (this.pullTimer) {
|
|
519
|
+
clearTimeout(this.pullTimer);
|
|
520
|
+
}
|
|
521
|
+
if (this.connectionState !== ConnectionState.CONNECTED) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
this.pullTimer = setTimeout(() => {
|
|
525
|
+
if (this.connectionState !== ConnectionState.CONNECTED) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
// Maintain batch size
|
|
529
|
+
if (this.pending < this.threshold && !this.pulling) {
|
|
530
|
+
const pullCount = this.maxMessages - this.pending;
|
|
531
|
+
this.log("debug", `Scheduled pull: ${pullCount} messages`);
|
|
532
|
+
this.pull(pullCount, false);
|
|
533
|
+
}
|
|
534
|
+
else if (this.pending === 0 && !this.pulling) {
|
|
535
|
+
// No pending at all, pull full batch
|
|
536
|
+
this.log("debug", "No pending, pulling full batch");
|
|
537
|
+
this.pull(this.maxMessages, false);
|
|
538
|
+
}
|
|
539
|
+
this.schedulePull();
|
|
540
|
+
}, this.pollIntervalMs);
|
|
460
541
|
}
|
|
461
542
|
async clearSubscriptions() {
|
|
543
|
+
if (this.pullTimer) {
|
|
544
|
+
clearTimeout(this.pullTimer);
|
|
545
|
+
this.pullTimer = null;
|
|
546
|
+
}
|
|
462
547
|
if (!this.pullSubscription)
|
|
463
548
|
return;
|
|
464
549
|
this.log("debug", "Clearing subscription");
|
|
@@ -472,10 +557,27 @@ class PubsubClient {
|
|
|
472
557
|
this.pullSubscription = null;
|
|
473
558
|
}
|
|
474
559
|
async resubscribe() {
|
|
475
|
-
|
|
476
|
-
|
|
560
|
+
if (!this.streamName || !this.consumerName)
|
|
561
|
+
return;
|
|
562
|
+
this.log("info", "Resubscribing after reconnection");
|
|
563
|
+
// Clear old state
|
|
564
|
+
this.pending = 0;
|
|
565
|
+
this.pulling = false;
|
|
566
|
+
// Recreate subscription
|
|
567
|
+
await this.createPullSubscription();
|
|
477
568
|
this.log("info", "✓ Resubscribed successfully");
|
|
478
569
|
}
|
|
570
|
+
cleanupState() {
|
|
571
|
+
this.subject = null;
|
|
572
|
+
this.streamName = null;
|
|
573
|
+
this.consumerName = null;
|
|
574
|
+
this.outputHandler = null;
|
|
575
|
+
this.sessionHandler = null;
|
|
576
|
+
this.urlHandler = null;
|
|
577
|
+
this.inbox = null;
|
|
578
|
+
this.pending = 0;
|
|
579
|
+
this.pulling = false;
|
|
580
|
+
}
|
|
479
581
|
async unsubscribe() {
|
|
480
582
|
if (this.connectionState === ConnectionState.DISCONNECTED ||
|
|
481
583
|
this.connectionState === ConnectionState.CLOSING) {
|
|
@@ -484,12 +586,7 @@ class PubsubClient {
|
|
|
484
586
|
this.log("info", "Starting unsubscribe process");
|
|
485
587
|
this.connectionState = ConnectionState.CLOSING;
|
|
486
588
|
await this.clearSubscriptions();
|
|
487
|
-
this.
|
|
488
|
-
this.sessionHandler = null;
|
|
489
|
-
this.urlHandler = null;
|
|
490
|
-
this.subject = null;
|
|
491
|
-
this.streamName = null;
|
|
492
|
-
this.consumerName = null;
|
|
589
|
+
this.cleanupState();
|
|
493
590
|
this.isReconnecting = false;
|
|
494
591
|
if (this.nc && !this.nc.isClosed()) {
|
|
495
592
|
try {
|
|
@@ -512,6 +609,7 @@ class PubsubClient {
|
|
|
512
609
|
}
|
|
513
610
|
}
|
|
514
611
|
exports.PubsubClient = PubsubClient;
|
|
612
|
+
// Helper functions
|
|
515
613
|
function validateSubject(subject) {
|
|
516
614
|
if (!subject || subject.includes(" ") || subject.includes("\t")) {
|
|
517
615
|
return false;
|
|
@@ -529,7 +627,6 @@ function parseOutputData(data) {
|
|
|
529
627
|
return [key, JSON.parse(new TextDecoder().decode(value))];
|
|
530
628
|
}
|
|
531
629
|
catch (error) {
|
|
532
|
-
// Note: We don't have access to the log method here, so we keep console.error
|
|
533
630
|
console.error(`Failed to parse data for key ${key}:`, error);
|
|
534
631
|
return [key, null];
|
|
535
632
|
}
|