@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.
@@ -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 the new configuration
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.createSubscriptions(this.streamName, this.consumerName);
232
+ await this.createPullSubscription();
205
233
  }
206
234
  catch (error) {
207
235
  this.connectionState = ConnectionState.DISCONNECTED;
208
- this.subject = null;
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
- this.log("debug", `Unknown status event: ${status.type}`, status.data);
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 createSubscriptions(streamName, consumerName) {
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
- const jc = (0, nats_ws_1.JSONCodec)();
293
- const pullSubject = `$JS.API.CONSUMER.MSG.NEXT.${streamName}.${consumerName}`;
294
- // Create inbox with proper prefix - let the system generate the unique part
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
- // Track state
299
- let pending = 0;
300
- let pulling = false;
301
- const maxMessages = 100;
302
- const threshold = 50;
303
- const pollInterval = 1000; // 1 second poll interval
304
- // Pull messages with proper expiration
305
- const pull = async (batch) => {
306
- if (pulling)
307
- return;
308
- pulling = true;
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
- // Set expires to slightly longer than poll interval to avoid accumulation
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
- finally {
320
- pulling = false;
344
+ catch (error) {
345
+ this.log("error", "Error handling message:", error);
321
346
  }
322
- };
323
- // Initial pull
324
- this.log("debug", `Initial pull request for ${maxMessages} messages`);
325
- pull(maxMessages);
326
- // Set up periodic pulling
327
- let pullTimer = null;
328
- const schedulePull = () => {
329
- if (pullTimer)
330
- clearTimeout(pullTimer);
331
- pullTimer = setTimeout(() => {
332
- if (pending < threshold) {
333
- const pullCount = maxMessages - pending;
334
- this.log("debug", `Scheduled pull: pending=${pending} < threshold=${threshold}, pulling ${pullCount} more`);
335
- pull(pullCount);
336
- }
337
- else {
338
- this.log("debug", `Skipping scheduled pull: pending=${pending} >= threshold=${threshold}`);
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
- }, pollInterval);
341
- };
342
- // Process messages
343
- (async () => {
344
- try {
345
- for await (const msg of sub) {
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
- catch (error) {
445
- if (this.connectionState !== ConnectionState.CLOSING) {
446
- this.log("error", "Handler error:", error);
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
- finally {
450
- if (pullTimer)
451
- clearTimeout(pullTimer);
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
- this.log("info", "Pull subscription created successfully");
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", "Failed to create subscriptions:", 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
- // Only called when we have an active connection that needs resubscription
476
- await this.createSubscriptions(this.streamName, this.consumerName);
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.outputHandler = null;
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
  }