@naylence/runtime 0.3.5-test.954 → 0.3.5-test.956

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.
@@ -96,12 +96,12 @@ installProcessEnvShim();
96
96
  // --- END ENV SHIM ---
97
97
 
98
98
  // This file is auto-generated during build - do not edit manually
99
- // Generated from package.json version: 0.3.5-test.954
99
+ // Generated from package.json version: 0.3.5-test.956
100
100
  /**
101
101
  * The package version, injected at build time.
102
102
  * @internal
103
103
  */
104
- const VERSION = '0.3.5-test.954';
104
+ const VERSION = '0.3.5-test.956';
105
105
 
106
106
  /**
107
107
  * Fame protocol specific error classes with WebSocket close codes and proper inheritance.
@@ -919,7 +919,7 @@ class TaskCancelledError extends Error {
919
919
  * Provides functionality similar to Python's asyncio TaskSpawner with proper
920
920
  * error handling, cancellation, and graceful shutdown capabilities.
921
921
  */
922
- const logger$1b = getLogger('naylence.fame.util.task_spawner');
922
+ const logger$1c = getLogger('naylence.fame.util.task_spawner');
923
923
  function firstDefined(source, keys) {
924
924
  for (const key of keys) {
925
925
  if (Object.prototype.hasOwnProperty.call(source, key)) {
@@ -1080,7 +1080,7 @@ class TaskSpawner {
1080
1080
  const taskId = `task-${++this._taskCounter}`;
1081
1081
  const taskName = normalizedOptions.name || `unnamed-${taskId}`;
1082
1082
  const timeout = normalizedOptions.timeout ?? this._config.defaultTimeout;
1083
- logger$1b.debug('starting_background_task', {
1083
+ logger$1c.debug('starting_background_task', {
1084
1084
  task_name: taskName,
1085
1085
  task_id: taskId,
1086
1086
  });
@@ -1097,7 +1097,7 @@ class TaskSpawner {
1097
1097
  task.promise
1098
1098
  .then(() => {
1099
1099
  if (!this._suppressCompletionLogging) {
1100
- logger$1b.debug('task_completed_successfully', {
1100
+ logger$1c.debug('task_completed_successfully', {
1101
1101
  task_name: taskName,
1102
1102
  task_id: taskId,
1103
1103
  duration_ms: Date.now() - task.startTime,
@@ -1151,7 +1151,7 @@ class TaskSpawner {
1151
1151
  error.name === 'AbortError' ||
1152
1152
  error.message === 'Task cancelled' ||
1153
1153
  error.message === 'Aborted') {
1154
- logger$1b.debug('task_cancelled', {
1154
+ logger$1c.debug('task_cancelled', {
1155
1155
  task_name: taskName,
1156
1156
  note: 'Task cancelled as requested',
1157
1157
  });
@@ -1159,7 +1159,7 @@ class TaskSpawner {
1159
1159
  }
1160
1160
  // Handle timeout
1161
1161
  if (error instanceof TaskTimeoutError) {
1162
- logger$1b.warning('task_timed_out', {
1162
+ logger$1c.warning('task_timed_out', {
1163
1163
  task_name: taskName,
1164
1164
  error: error.message,
1165
1165
  });
@@ -1171,7 +1171,7 @@ class TaskSpawner {
1171
1171
  // Handle known WebSocket shutdown race condition (similar to Python version)
1172
1172
  if (error.message.includes("await wasn't used with future") ||
1173
1173
  error.message.includes('WebSocket closed during receive')) {
1174
- logger$1b.debug('task_shutdown_race_condition_handled', {
1174
+ logger$1c.debug('task_shutdown_race_condition_handled', {
1175
1175
  task_name: taskName,
1176
1176
  note: 'Normal WebSocket close timing during shutdown - not an error',
1177
1177
  });
@@ -1181,7 +1181,7 @@ class TaskSpawner {
1181
1181
  if (error.name === 'FameTransportClose' ||
1182
1182
  error.message.includes('normal closure') ||
1183
1183
  error.message.includes('Connection closed')) {
1184
- logger$1b.debug('task_shutdown_completed_normally', {
1184
+ logger$1c.debug('task_shutdown_completed_normally', {
1185
1185
  task_name: taskName,
1186
1186
  note: 'Task closed normally during shutdown',
1187
1187
  });
@@ -1194,14 +1194,14 @@ class TaskSpawner {
1194
1194
  // Log retriable errors as warnings (they'll be retried by upstream logic)
1195
1195
  // Log non-retriable errors as errors (fatal failures)
1196
1196
  if (isRetriableError) {
1197
- logger$1b.warning('background_task_failed', {
1197
+ logger$1c.warning('background_task_failed', {
1198
1198
  task_name: taskName,
1199
1199
  error: error.message,
1200
1200
  retriable: true,
1201
1201
  });
1202
1202
  }
1203
1203
  else {
1204
- logger$1b.error('background_task_failed', {
1204
+ logger$1c.error('background_task_failed', {
1205
1205
  task_name: taskName,
1206
1206
  error: error.message,
1207
1207
  stack: error.stack,
@@ -1220,11 +1220,11 @@ class TaskSpawner {
1220
1220
  async shutdownTasks(options = {}) {
1221
1221
  const { gracePeriod, cancelHanging, joinTimeout } = normalizeShutdownOptions(options);
1222
1222
  if (this._tasks.size === 0) {
1223
- logger$1b.debug('shutdown_tasks_no_tasks_to_shutdown');
1223
+ logger$1c.debug('shutdown_tasks_no_tasks_to_shutdown');
1224
1224
  return;
1225
1225
  }
1226
1226
  this._suppressCompletionLogging = true;
1227
- logger$1b.debug('shutting_down_tasks', {
1227
+ logger$1c.debug('shutting_down_tasks', {
1228
1228
  task_count: this._tasks.size,
1229
1229
  task_names: Array.from(this._tasks.values()).map((t) => t.name),
1230
1230
  grace_period_ms: gracePeriod,
@@ -1239,7 +1239,7 @@ class TaskSpawner {
1239
1239
  if (cancelHanging) {
1240
1240
  const stillRunning = tasks.filter((task) => task.getState() === TaskState.RUNNING && !completed.has(task));
1241
1241
  if (stillRunning.length > 0) {
1242
- logger$1b.debug('tasks_did_not_complete_within_grace_period', {
1242
+ logger$1c.debug('tasks_did_not_complete_within_grace_period', {
1243
1243
  hanging_count: stillRunning.length,
1244
1244
  });
1245
1245
  // Wait for them to finish with individual timeouts
@@ -1249,7 +1249,7 @@ class TaskSpawner {
1249
1249
  }
1250
1250
  catch (error) {
1251
1251
  if (error instanceof TaskTimeoutError) {
1252
- logger$1b.warning('task_did_not_shutdown', {
1252
+ logger$1c.warning('task_did_not_shutdown', {
1253
1253
  task_name: task.name || task.id,
1254
1254
  join_timeout_ms: joinTimeout,
1255
1255
  });
@@ -1260,7 +1260,7 @@ class TaskSpawner {
1260
1260
  }
1261
1261
  else if (!(error instanceof TaskCancelledError)) {
1262
1262
  /* istanbul ignore next - unreachable defensive branch */
1263
- logger$1b.error('task_raised_during_cancellation', {
1263
+ logger$1c.error('task_raised_during_cancellation', {
1264
1264
  task_name: task.name || task.id,
1265
1265
  error: error instanceof Error ? error.message : String(error),
1266
1266
  });
@@ -2353,6 +2353,7 @@ function validateKeyCorrelationTtlSec(ttlSec) {
2353
2353
  * condition/promise and ensure at most one notifier coroutine exists for a
2354
2354
  * flow at any time.
2355
2355
  */
2356
+ const logger$1b = getLogger('naylence.fame.flow.flow_controller');
2356
2357
  /**
2357
2358
  * Simple condition variable implementation for TypeScript/Node.js
2358
2359
  * Similar to Python's asyncio.Condition
@@ -2478,8 +2479,17 @@ class FlowController {
2478
2479
  // clamp into [0, initialWindow]
2479
2480
  const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
2480
2481
  this.credits.set(flowId, newBalance);
2482
+ const crossedZero = prev <= 0 && newBalance > 0;
2483
+ logger$1b.debug('flow_controller_add_credits', {
2484
+ flow_id: flowId,
2485
+ delta,
2486
+ prev_balance: prev,
2487
+ new_balance: newBalance,
2488
+ initial_window: this.initialWindow,
2489
+ crossed_zero: crossedZero,
2490
+ });
2481
2491
  // wake waiters only if we crossed the zero boundary
2482
- if (prev <= 0 && newBalance > 0) {
2492
+ if (crossedZero) {
2483
2493
  this.wakeWaiters(flowId);
2484
2494
  }
2485
2495
  return newBalance;
@@ -2490,11 +2500,27 @@ class FlowController {
2490
2500
  async acquire(flowId) {
2491
2501
  this.ensureFlow(flowId);
2492
2502
  const condition = this.conditions.get(flowId);
2503
+ logger$1b.debug('flow_controller_acquire_attempt', {
2504
+ flow_id: flowId,
2505
+ current_balance: this.credits.get(flowId),
2506
+ });
2493
2507
  while (this.credits.get(flowId) <= 0) {
2508
+ logger$1b.debug('flow_controller_waiting_for_credit', {
2509
+ flow_id: flowId,
2510
+ });
2494
2511
  await condition.wait();
2512
+ logger$1b.debug('flow_controller_woke_with_credit', {
2513
+ flow_id: flowId,
2514
+ balance_after_wake: this.credits.get(flowId),
2515
+ });
2495
2516
  }
2496
2517
  const current = this.credits.get(flowId);
2497
2518
  this.credits.set(flowId, current - 1);
2519
+ logger$1b.debug('flow_controller_credit_consumed', {
2520
+ flow_id: flowId,
2521
+ prev_balance: current,
2522
+ remaining_balance: current - 1,
2523
+ });
2498
2524
  }
2499
2525
  /**
2500
2526
  * Consume *credits* immediately (non-blocking).
@@ -2514,6 +2540,12 @@ class FlowController {
2514
2540
  const current = this.credits.get(flowId);
2515
2541
  const remaining = Math.max(current - credits, 0);
2516
2542
  this.credits.set(flowId, remaining);
2543
+ logger$1b.debug('flow_controller_consume', {
2544
+ flow_id: flowId,
2545
+ requested: credits,
2546
+ prev_balance: current,
2547
+ remaining_balance: remaining,
2548
+ });
2517
2549
  return remaining;
2518
2550
  }
2519
2551
  /**
@@ -2534,6 +2566,10 @@ class FlowController {
2534
2566
  this.windowIds.delete(flowId);
2535
2567
  this.credits.set(flowId, this.initialWindow);
2536
2568
  this.wakeWaiters(flowId);
2569
+ logger$1b.debug('flow_controller_flow_reset', {
2570
+ flow_id: flowId,
2571
+ reset_balance: this.initialWindow,
2572
+ });
2537
2573
  }
2538
2574
  /**
2539
2575
  * Return `[windowId, flags]` for the next outbound envelope.
@@ -9381,10 +9417,12 @@ class BaseAsyncConnector extends TaskSpawner {
9381
9417
  throw new FameTransportClose('Connection closed', 1006);
9382
9418
  }
9383
9419
  // Apply flow control if enabled and not a credit update
9384
- if (this._fcEnabled &&
9385
- !(envelope.frame &&
9386
- 'flow_id' in envelope.frame &&
9387
- 'credits' in envelope.frame)) {
9420
+ const isCreditUpdateFrame = Boolean(envelope.frame &&
9421
+ (envelope.frame.type === 'CreditUpdate' ||
9422
+ envelope.frame.type === 'credit_update' ||
9423
+ ('flowId' in envelope.frame && 'credits' in envelope.frame) ||
9424
+ ('flow_id' in envelope.frame && 'credits' in envelope.frame)));
9425
+ if (this._fcEnabled && !isCreditUpdateFrame) {
9388
9426
  const flowId = envelope.flowId || this._connectorFlowId;
9389
9427
  envelope.flowId = flowId;
9390
9428
  const t0 = this._metrics ? performance.now() : 0;
@@ -9879,6 +9917,7 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
9879
9917
  ? Math.floor(config.inboxCapacity)
9880
9918
  : DEFAULT_INBOX_CAPACITY$7;
9881
9919
  this.inbox = new BoundedAsyncQueue(preferredCapacity);
9920
+ this.inboxCapacity = preferredCapacity;
9882
9921
  this.connectorId = BroadcastChannelConnector.generateConnectorId();
9883
9922
  this.channel = new BroadcastChannel(this.channelName);
9884
9923
  logger$_.debug('broadcast_channel_connector_created', {
@@ -9938,15 +9977,27 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
9938
9977
  if (typeof this.inbox.tryEnqueue === 'function') {
9939
9978
  const accepted = this.inbox.tryEnqueue(payload);
9940
9979
  if (accepted) {
9980
+ this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
9981
+ source: 'listener',
9982
+ enqueue_strategy: 'try',
9983
+ payload_length: payload.byteLength,
9984
+ });
9941
9985
  return;
9942
9986
  }
9943
9987
  }
9944
9988
  this.inbox.enqueue(payload);
9989
+ this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
9990
+ source: 'listener',
9991
+ enqueue_strategy: 'enqueue',
9992
+ payload_length: payload.byteLength,
9993
+ });
9945
9994
  }
9946
9995
  catch (error) {
9947
9996
  if (error instanceof QueueFullError) {
9948
9997
  logger$_.warning('broadcast_channel_receive_queue_full', {
9949
9998
  channel: this.channelName,
9999
+ inbox_capacity: this.inboxCapacity,
10000
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
9950
10001
  });
9951
10002
  }
9952
10003
  else {
@@ -9957,8 +10008,10 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
9957
10008
  }
9958
10009
  }
9959
10010
  };
9960
- this.channel.addEventListener('message', this.onMsg);
9961
- this.listenerRegistered = true;
10011
+ if (!config.passive) {
10012
+ this.channel.addEventListener('message', this.onMsg);
10013
+ this.listenerRegistered = true;
10014
+ }
9962
10015
  // Setup visibility change monitoring
9963
10016
  this.visibilityChangeHandler = () => {
9964
10017
  const isHidden = document.hidden;
@@ -10030,15 +10083,25 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
10030
10083
  if (typeof this.inbox.tryEnqueue === 'function') {
10031
10084
  const accepted = this.inbox.tryEnqueue(item);
10032
10085
  if (accepted) {
10086
+ this.logInboxSnapshot('broadcast_channel_push_enqueued', {
10087
+ enqueue_strategy: 'try',
10088
+ item_type: this._describeInboxItem(item),
10089
+ });
10033
10090
  return;
10034
10091
  }
10035
10092
  }
10036
10093
  this.inbox.enqueue(item);
10094
+ this.logInboxSnapshot('broadcast_channel_push_enqueued', {
10095
+ enqueue_strategy: 'enqueue',
10096
+ item_type: this._describeInboxItem(item),
10097
+ });
10037
10098
  }
10038
10099
  catch (error) {
10039
10100
  if (error instanceof QueueFullError) {
10040
10101
  logger$_.warning('broadcast_channel_push_queue_full', {
10041
10102
  channel: this.channelName,
10103
+ inbox_capacity: this.inboxCapacity,
10104
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
10042
10105
  });
10043
10106
  throw error;
10044
10107
  }
@@ -10061,7 +10124,11 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
10061
10124
  });
10062
10125
  }
10063
10126
  async _transportReceive() {
10064
- return await this.inbox.dequeue();
10127
+ const item = await this.inbox.dequeue();
10128
+ this.logInboxSnapshot('broadcast_channel_inbox_dequeued', {
10129
+ item_type: this._describeInboxItem(item),
10130
+ });
10131
+ return item;
10065
10132
  }
10066
10133
  async _transportClose(code, reason) {
10067
10134
  logger$_.debug('broadcast_channel_transport_closing', {
@@ -10115,6 +10182,28 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
10115
10182
  }
10116
10183
  return rawOrEnvelope;
10117
10184
  }
10185
+ _describeInboxItem(item) {
10186
+ if (item instanceof Uint8Array) {
10187
+ return 'bytes';
10188
+ }
10189
+ if (item.envelope) {
10190
+ return 'channel_message';
10191
+ }
10192
+ if (item.frame) {
10193
+ return 'envelope';
10194
+ }
10195
+ return 'unknown';
10196
+ }
10197
+ logInboxSnapshot(event, extra = {}) {
10198
+ logger$_.debug(event, {
10199
+ channel: this.channelName,
10200
+ connector_id: this.connectorId,
10201
+ connector_state: this.state,
10202
+ inbox_capacity: this.inboxCapacity,
10203
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
10204
+ ...extra,
10205
+ });
10206
+ }
10118
10207
  _shouldSkipDuplicateAck(senderId, payload) {
10119
10208
  const dedupKey = this._extractAckDedupKey(payload);
10120
10209
  if (!dedupKey) {
@@ -29941,6 +30030,7 @@ class BroadcastChannelListener extends TransportListener {
29941
30030
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
29942
30031
  channelName: this._channelName,
29943
30032
  inboxCapacity: this._inboxCapacity,
30033
+ passive: true,
29944
30034
  };
29945
30035
  }
29946
30036
  try {
@@ -30001,6 +30091,7 @@ class BroadcastChannelListener extends TransportListener {
30001
30091
  type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
30002
30092
  channelName: this._channelName,
30003
30093
  inboxCapacity: this._inboxCapacity,
30094
+ passive: true,
30004
30095
  };
30005
30096
  const channelCandidate = candidate.channelName ?? candidate['channel_name'];
30006
30097
  if (typeof channelCandidate === 'string' && channelCandidate.trim().length > 0) {
@@ -25,6 +25,8 @@
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.FlowController = void 0;
27
27
  const core_1 = require("@naylence/core");
28
+ const logging_js_1 = require("../util/logging.js");
29
+ const logger = (0, logging_js_1.getLogger)('naylence.fame.flow.flow_controller');
28
30
  /**
29
31
  * Simple condition variable implementation for TypeScript/Node.js
30
32
  * Similar to Python's asyncio.Condition
@@ -150,8 +152,17 @@ class FlowController {
150
152
  // clamp into [0, initialWindow]
151
153
  const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
152
154
  this.credits.set(flowId, newBalance);
155
+ const crossedZero = prev <= 0 && newBalance > 0;
156
+ logger.debug('flow_controller_add_credits', {
157
+ flow_id: flowId,
158
+ delta,
159
+ prev_balance: prev,
160
+ new_balance: newBalance,
161
+ initial_window: this.initialWindow,
162
+ crossed_zero: crossedZero,
163
+ });
153
164
  // wake waiters only if we crossed the zero boundary
154
- if (prev <= 0 && newBalance > 0) {
165
+ if (crossedZero) {
155
166
  this.wakeWaiters(flowId);
156
167
  }
157
168
  return newBalance;
@@ -162,11 +173,27 @@ class FlowController {
162
173
  async acquire(flowId) {
163
174
  this.ensureFlow(flowId);
164
175
  const condition = this.conditions.get(flowId);
176
+ logger.debug('flow_controller_acquire_attempt', {
177
+ flow_id: flowId,
178
+ current_balance: this.credits.get(flowId),
179
+ });
165
180
  while (this.credits.get(flowId) <= 0) {
181
+ logger.debug('flow_controller_waiting_for_credit', {
182
+ flow_id: flowId,
183
+ });
166
184
  await condition.wait();
185
+ logger.debug('flow_controller_woke_with_credit', {
186
+ flow_id: flowId,
187
+ balance_after_wake: this.credits.get(flowId),
188
+ });
167
189
  }
168
190
  const current = this.credits.get(flowId);
169
191
  this.credits.set(flowId, current - 1);
192
+ logger.debug('flow_controller_credit_consumed', {
193
+ flow_id: flowId,
194
+ prev_balance: current,
195
+ remaining_balance: current - 1,
196
+ });
170
197
  }
171
198
  /**
172
199
  * Consume *credits* immediately (non-blocking).
@@ -186,6 +213,12 @@ class FlowController {
186
213
  const current = this.credits.get(flowId);
187
214
  const remaining = Math.max(current - credits, 0);
188
215
  this.credits.set(flowId, remaining);
216
+ logger.debug('flow_controller_consume', {
217
+ flow_id: flowId,
218
+ requested: credits,
219
+ prev_balance: current,
220
+ remaining_balance: remaining,
221
+ });
189
222
  return remaining;
190
223
  }
191
224
  /**
@@ -206,6 +239,10 @@ class FlowController {
206
239
  this.windowIds.delete(flowId);
207
240
  this.credits.set(flowId, this.initialWindow);
208
241
  this.wakeWaiters(flowId);
242
+ logger.debug('flow_controller_flow_reset', {
243
+ flow_id: flowId,
244
+ reset_balance: this.initialWindow,
245
+ });
209
246
  }
210
247
  /**
211
248
  * Return `[windowId, flags]` for the next outbound envelope.
@@ -261,10 +261,12 @@ class BaseAsyncConnector extends task_spawner_js_1.TaskSpawner {
261
261
  throw new errors_js_1.FameTransportClose('Connection closed', 1006);
262
262
  }
263
263
  // Apply flow control if enabled and not a credit update
264
- if (this._fcEnabled &&
265
- !(envelope.frame &&
266
- 'flow_id' in envelope.frame &&
267
- 'credits' in envelope.frame)) {
264
+ const isCreditUpdateFrame = Boolean(envelope.frame &&
265
+ (envelope.frame.type === 'CreditUpdate' ||
266
+ envelope.frame.type === 'credit_update' ||
267
+ ('flowId' in envelope.frame && 'credits' in envelope.frame) ||
268
+ ('flow_id' in envelope.frame && 'credits' in envelope.frame)));
269
+ if (this._fcEnabled && !isCreditUpdateFrame) {
268
270
  const flowId = envelope.flowId || this._connectorFlowId;
269
271
  envelope.flowId = flowId;
270
272
  const t0 = this._metrics ? performance.now() : 0;
@@ -69,6 +69,7 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
69
69
  ? Math.floor(config.inboxCapacity)
70
70
  : DEFAULT_INBOX_CAPACITY;
71
71
  this.inbox = new bounded_async_queue_js_1.BoundedAsyncQueue(preferredCapacity);
72
+ this.inboxCapacity = preferredCapacity;
72
73
  this.connectorId = BroadcastChannelConnector.generateConnectorId();
73
74
  this.channel = new BroadcastChannel(this.channelName);
74
75
  logger.debug('broadcast_channel_connector_created', {
@@ -128,15 +129,27 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
128
129
  if (typeof this.inbox.tryEnqueue === 'function') {
129
130
  const accepted = this.inbox.tryEnqueue(payload);
130
131
  if (accepted) {
132
+ this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
133
+ source: 'listener',
134
+ enqueue_strategy: 'try',
135
+ payload_length: payload.byteLength,
136
+ });
131
137
  return;
132
138
  }
133
139
  }
134
140
  this.inbox.enqueue(payload);
141
+ this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
142
+ source: 'listener',
143
+ enqueue_strategy: 'enqueue',
144
+ payload_length: payload.byteLength,
145
+ });
135
146
  }
136
147
  catch (error) {
137
148
  if (error instanceof bounded_async_queue_js_1.QueueFullError) {
138
149
  logger.warning('broadcast_channel_receive_queue_full', {
139
150
  channel: this.channelName,
151
+ inbox_capacity: this.inboxCapacity,
152
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
140
153
  });
141
154
  }
142
155
  else {
@@ -147,8 +160,10 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
147
160
  }
148
161
  }
149
162
  };
150
- this.channel.addEventListener('message', this.onMsg);
151
- this.listenerRegistered = true;
163
+ if (!config.passive) {
164
+ this.channel.addEventListener('message', this.onMsg);
165
+ this.listenerRegistered = true;
166
+ }
152
167
  // Setup visibility change monitoring
153
168
  this.visibilityChangeHandler = () => {
154
169
  const isHidden = document.hidden;
@@ -220,15 +235,25 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
220
235
  if (typeof this.inbox.tryEnqueue === 'function') {
221
236
  const accepted = this.inbox.tryEnqueue(item);
222
237
  if (accepted) {
238
+ this.logInboxSnapshot('broadcast_channel_push_enqueued', {
239
+ enqueue_strategy: 'try',
240
+ item_type: this._describeInboxItem(item),
241
+ });
223
242
  return;
224
243
  }
225
244
  }
226
245
  this.inbox.enqueue(item);
246
+ this.logInboxSnapshot('broadcast_channel_push_enqueued', {
247
+ enqueue_strategy: 'enqueue',
248
+ item_type: this._describeInboxItem(item),
249
+ });
227
250
  }
228
251
  catch (error) {
229
252
  if (error instanceof bounded_async_queue_js_1.QueueFullError) {
230
253
  logger.warning('broadcast_channel_push_queue_full', {
231
254
  channel: this.channelName,
255
+ inbox_capacity: this.inboxCapacity,
256
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
232
257
  });
233
258
  throw error;
234
259
  }
@@ -251,7 +276,11 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
251
276
  });
252
277
  }
253
278
  async _transportReceive() {
254
- return await this.inbox.dequeue();
279
+ const item = await this.inbox.dequeue();
280
+ this.logInboxSnapshot('broadcast_channel_inbox_dequeued', {
281
+ item_type: this._describeInboxItem(item),
282
+ });
283
+ return item;
255
284
  }
256
285
  async _transportClose(code, reason) {
257
286
  logger.debug('broadcast_channel_transport_closing', {
@@ -305,6 +334,28 @@ class BroadcastChannelConnector extends base_async_connector_js_1.BaseAsyncConne
305
334
  }
306
335
  return rawOrEnvelope;
307
336
  }
337
+ _describeInboxItem(item) {
338
+ if (item instanceof Uint8Array) {
339
+ return 'bytes';
340
+ }
341
+ if (item.envelope) {
342
+ return 'channel_message';
343
+ }
344
+ if (item.frame) {
345
+ return 'envelope';
346
+ }
347
+ return 'unknown';
348
+ }
349
+ logInboxSnapshot(event, extra = {}) {
350
+ logger.debug(event, {
351
+ channel: this.channelName,
352
+ connector_id: this.connectorId,
353
+ connector_state: this.state,
354
+ inbox_capacity: this.inboxCapacity,
355
+ inbox_remaining_capacity: this.inbox.remainingCapacity,
356
+ ...extra,
357
+ });
358
+ }
308
359
  _shouldSkipDuplicateAck(senderId, payload) {
309
360
  const dedupKey = this._extractAckDedupKey(payload);
310
361
  if (!dedupKey) {
@@ -342,6 +342,7 @@ class BroadcastChannelListener extends transport_listener_js_1.TransportListener
342
342
  type: broadcast_channel_connector_js_1.BROADCAST_CHANNEL_CONNECTOR_TYPE,
343
343
  channelName: this._channelName,
344
344
  inboxCapacity: this._inboxCapacity,
345
+ passive: true,
345
346
  };
346
347
  }
347
348
  try {
@@ -402,6 +403,7 @@ class BroadcastChannelListener extends transport_listener_js_1.TransportListener
402
403
  type: broadcast_channel_connector_js_1.BROADCAST_CHANNEL_CONNECTOR_TYPE,
403
404
  channelName: this._channelName,
404
405
  inboxCapacity: this._inboxCapacity,
406
+ passive: true,
405
407
  };
406
408
  const channelCandidate = candidate.channelName ?? candidate['channel_name'];
407
409
  if (typeof channelCandidate === 'string' && channelCandidate.trim().length > 0) {
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  // This file is auto-generated during build - do not edit manually
3
- // Generated from package.json version: 0.3.5-test.954
3
+ // Generated from package.json version: 0.3.5-test.956
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.VERSION = void 0;
6
6
  /**
7
7
  * The package version, injected at build time.
8
8
  * @internal
9
9
  */
10
- exports.VERSION = '0.3.5-test.954';
10
+ exports.VERSION = '0.3.5-test.956';
@@ -22,6 +22,8 @@
22
22
  * flow at any time.
23
23
  */
24
24
  import { FlowFlags } from '@naylence/core';
25
+ import { getLogger } from '../util/logging.js';
26
+ const logger = getLogger('naylence.fame.flow.flow_controller');
25
27
  /**
26
28
  * Simple condition variable implementation for TypeScript/Node.js
27
29
  * Similar to Python's asyncio.Condition
@@ -147,8 +149,17 @@ export class FlowController {
147
149
  // clamp into [0, initialWindow]
148
150
  const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
149
151
  this.credits.set(flowId, newBalance);
152
+ const crossedZero = prev <= 0 && newBalance > 0;
153
+ logger.debug('flow_controller_add_credits', {
154
+ flow_id: flowId,
155
+ delta,
156
+ prev_balance: prev,
157
+ new_balance: newBalance,
158
+ initial_window: this.initialWindow,
159
+ crossed_zero: crossedZero,
160
+ });
150
161
  // wake waiters only if we crossed the zero boundary
151
- if (prev <= 0 && newBalance > 0) {
162
+ if (crossedZero) {
152
163
  this.wakeWaiters(flowId);
153
164
  }
154
165
  return newBalance;
@@ -159,11 +170,27 @@ export class FlowController {
159
170
  async acquire(flowId) {
160
171
  this.ensureFlow(flowId);
161
172
  const condition = this.conditions.get(flowId);
173
+ logger.debug('flow_controller_acquire_attempt', {
174
+ flow_id: flowId,
175
+ current_balance: this.credits.get(flowId),
176
+ });
162
177
  while (this.credits.get(flowId) <= 0) {
178
+ logger.debug('flow_controller_waiting_for_credit', {
179
+ flow_id: flowId,
180
+ });
163
181
  await condition.wait();
182
+ logger.debug('flow_controller_woke_with_credit', {
183
+ flow_id: flowId,
184
+ balance_after_wake: this.credits.get(flowId),
185
+ });
164
186
  }
165
187
  const current = this.credits.get(flowId);
166
188
  this.credits.set(flowId, current - 1);
189
+ logger.debug('flow_controller_credit_consumed', {
190
+ flow_id: flowId,
191
+ prev_balance: current,
192
+ remaining_balance: current - 1,
193
+ });
167
194
  }
168
195
  /**
169
196
  * Consume *credits* immediately (non-blocking).
@@ -183,6 +210,12 @@ export class FlowController {
183
210
  const current = this.credits.get(flowId);
184
211
  const remaining = Math.max(current - credits, 0);
185
212
  this.credits.set(flowId, remaining);
213
+ logger.debug('flow_controller_consume', {
214
+ flow_id: flowId,
215
+ requested: credits,
216
+ prev_balance: current,
217
+ remaining_balance: remaining,
218
+ });
186
219
  return remaining;
187
220
  }
188
221
  /**
@@ -203,6 +236,10 @@ export class FlowController {
203
236
  this.windowIds.delete(flowId);
204
237
  this.credits.set(flowId, this.initialWindow);
205
238
  this.wakeWaiters(flowId);
239
+ logger.debug('flow_controller_flow_reset', {
240
+ flow_id: flowId,
241
+ reset_balance: this.initialWindow,
242
+ });
206
243
  }
207
244
  /**
208
245
  * Return `[windowId, flags]` for the next outbound envelope.