@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.
- package/dist/browser/index.cjs +115 -24
- package/dist/browser/index.mjs +115 -24
- package/dist/cjs/naylence/fame/channel/flow-controller.js +38 -1
- package/dist/cjs/naylence/fame/connector/base-async-connector.js +6 -4
- package/dist/cjs/naylence/fame/connector/broadcast-channel-connector.browser.js +54 -3
- package/dist/cjs/naylence/fame/connector/broadcast-channel-listener.js +2 -0
- package/dist/cjs/version.js +2 -2
- package/dist/esm/naylence/fame/channel/flow-controller.js +38 -1
- package/dist/esm/naylence/fame/connector/base-async-connector.js +6 -4
- package/dist/esm/naylence/fame/connector/broadcast-channel-connector.browser.js +54 -3
- package/dist/esm/naylence/fame/connector/broadcast-channel-listener.js +2 -0
- package/dist/esm/version.js +2 -2
- package/dist/node/index.cjs +115 -24
- package/dist/node/index.mjs +115 -24
- package/dist/node/node.cjs +115 -24
- package/dist/node/node.mjs +115 -24
- package/dist/types/naylence/fame/connector/broadcast-channel-connector.browser.d.ts +4 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/node/index.mjs
CHANGED
|
@@ -13,12 +13,12 @@ import fastify from 'fastify';
|
|
|
13
13
|
import websocketPlugin from '@fastify/websocket';
|
|
14
14
|
|
|
15
15
|
// This file is auto-generated during build - do not edit manually
|
|
16
|
-
// Generated from package.json version: 0.3.5-test.
|
|
16
|
+
// Generated from package.json version: 0.3.5-test.956
|
|
17
17
|
/**
|
|
18
18
|
* The package version, injected at build time.
|
|
19
19
|
* @internal
|
|
20
20
|
*/
|
|
21
|
-
const VERSION = '0.3.5-test.
|
|
21
|
+
const VERSION = '0.3.5-test.956';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Fame protocol specific error classes with WebSocket close codes and proper inheritance.
|
|
@@ -836,7 +836,7 @@ class TaskCancelledError extends Error {
|
|
|
836
836
|
* Provides functionality similar to Python's asyncio TaskSpawner with proper
|
|
837
837
|
* error handling, cancellation, and graceful shutdown capabilities.
|
|
838
838
|
*/
|
|
839
|
-
const logger$
|
|
839
|
+
const logger$1c = getLogger('naylence.fame.util.task_spawner');
|
|
840
840
|
function firstDefined(source, keys) {
|
|
841
841
|
for (const key of keys) {
|
|
842
842
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
@@ -997,7 +997,7 @@ class TaskSpawner {
|
|
|
997
997
|
const taskId = `task-${++this._taskCounter}`;
|
|
998
998
|
const taskName = normalizedOptions.name || `unnamed-${taskId}`;
|
|
999
999
|
const timeout = normalizedOptions.timeout ?? this._config.defaultTimeout;
|
|
1000
|
-
logger$
|
|
1000
|
+
logger$1c.debug('starting_background_task', {
|
|
1001
1001
|
task_name: taskName,
|
|
1002
1002
|
task_id: taskId,
|
|
1003
1003
|
});
|
|
@@ -1014,7 +1014,7 @@ class TaskSpawner {
|
|
|
1014
1014
|
task.promise
|
|
1015
1015
|
.then(() => {
|
|
1016
1016
|
if (!this._suppressCompletionLogging) {
|
|
1017
|
-
logger$
|
|
1017
|
+
logger$1c.debug('task_completed_successfully', {
|
|
1018
1018
|
task_name: taskName,
|
|
1019
1019
|
task_id: taskId,
|
|
1020
1020
|
duration_ms: Date.now() - task.startTime,
|
|
@@ -1068,7 +1068,7 @@ class TaskSpawner {
|
|
|
1068
1068
|
error.name === 'AbortError' ||
|
|
1069
1069
|
error.message === 'Task cancelled' ||
|
|
1070
1070
|
error.message === 'Aborted') {
|
|
1071
|
-
logger$
|
|
1071
|
+
logger$1c.debug('task_cancelled', {
|
|
1072
1072
|
task_name: taskName,
|
|
1073
1073
|
note: 'Task cancelled as requested',
|
|
1074
1074
|
});
|
|
@@ -1076,7 +1076,7 @@ class TaskSpawner {
|
|
|
1076
1076
|
}
|
|
1077
1077
|
// Handle timeout
|
|
1078
1078
|
if (error instanceof TaskTimeoutError) {
|
|
1079
|
-
logger$
|
|
1079
|
+
logger$1c.warning('task_timed_out', {
|
|
1080
1080
|
task_name: taskName,
|
|
1081
1081
|
error: error.message,
|
|
1082
1082
|
});
|
|
@@ -1088,7 +1088,7 @@ class TaskSpawner {
|
|
|
1088
1088
|
// Handle known WebSocket shutdown race condition (similar to Python version)
|
|
1089
1089
|
if (error.message.includes("await wasn't used with future") ||
|
|
1090
1090
|
error.message.includes('WebSocket closed during receive')) {
|
|
1091
|
-
logger$
|
|
1091
|
+
logger$1c.debug('task_shutdown_race_condition_handled', {
|
|
1092
1092
|
task_name: taskName,
|
|
1093
1093
|
note: 'Normal WebSocket close timing during shutdown - not an error',
|
|
1094
1094
|
});
|
|
@@ -1098,7 +1098,7 @@ class TaskSpawner {
|
|
|
1098
1098
|
if (error.name === 'FameTransportClose' ||
|
|
1099
1099
|
error.message.includes('normal closure') ||
|
|
1100
1100
|
error.message.includes('Connection closed')) {
|
|
1101
|
-
logger$
|
|
1101
|
+
logger$1c.debug('task_shutdown_completed_normally', {
|
|
1102
1102
|
task_name: taskName,
|
|
1103
1103
|
note: 'Task closed normally during shutdown',
|
|
1104
1104
|
});
|
|
@@ -1111,14 +1111,14 @@ class TaskSpawner {
|
|
|
1111
1111
|
// Log retriable errors as warnings (they'll be retried by upstream logic)
|
|
1112
1112
|
// Log non-retriable errors as errors (fatal failures)
|
|
1113
1113
|
if (isRetriableError) {
|
|
1114
|
-
logger$
|
|
1114
|
+
logger$1c.warning('background_task_failed', {
|
|
1115
1115
|
task_name: taskName,
|
|
1116
1116
|
error: error.message,
|
|
1117
1117
|
retriable: true,
|
|
1118
1118
|
});
|
|
1119
1119
|
}
|
|
1120
1120
|
else {
|
|
1121
|
-
logger$
|
|
1121
|
+
logger$1c.error('background_task_failed', {
|
|
1122
1122
|
task_name: taskName,
|
|
1123
1123
|
error: error.message,
|
|
1124
1124
|
stack: error.stack,
|
|
@@ -1137,11 +1137,11 @@ class TaskSpawner {
|
|
|
1137
1137
|
async shutdownTasks(options = {}) {
|
|
1138
1138
|
const { gracePeriod, cancelHanging, joinTimeout } = normalizeShutdownOptions(options);
|
|
1139
1139
|
if (this._tasks.size === 0) {
|
|
1140
|
-
logger$
|
|
1140
|
+
logger$1c.debug('shutdown_tasks_no_tasks_to_shutdown');
|
|
1141
1141
|
return;
|
|
1142
1142
|
}
|
|
1143
1143
|
this._suppressCompletionLogging = true;
|
|
1144
|
-
logger$
|
|
1144
|
+
logger$1c.debug('shutting_down_tasks', {
|
|
1145
1145
|
task_count: this._tasks.size,
|
|
1146
1146
|
task_names: Array.from(this._tasks.values()).map((t) => t.name),
|
|
1147
1147
|
grace_period_ms: gracePeriod,
|
|
@@ -1156,7 +1156,7 @@ class TaskSpawner {
|
|
|
1156
1156
|
if (cancelHanging) {
|
|
1157
1157
|
const stillRunning = tasks.filter((task) => task.getState() === TaskState.RUNNING && !completed.has(task));
|
|
1158
1158
|
if (stillRunning.length > 0) {
|
|
1159
|
-
logger$
|
|
1159
|
+
logger$1c.debug('tasks_did_not_complete_within_grace_period', {
|
|
1160
1160
|
hanging_count: stillRunning.length,
|
|
1161
1161
|
});
|
|
1162
1162
|
// Wait for them to finish with individual timeouts
|
|
@@ -1166,7 +1166,7 @@ class TaskSpawner {
|
|
|
1166
1166
|
}
|
|
1167
1167
|
catch (error) {
|
|
1168
1168
|
if (error instanceof TaskTimeoutError) {
|
|
1169
|
-
logger$
|
|
1169
|
+
logger$1c.warning('task_did_not_shutdown', {
|
|
1170
1170
|
task_name: task.name || task.id,
|
|
1171
1171
|
join_timeout_ms: joinTimeout,
|
|
1172
1172
|
});
|
|
@@ -1177,7 +1177,7 @@ class TaskSpawner {
|
|
|
1177
1177
|
}
|
|
1178
1178
|
else if (!(error instanceof TaskCancelledError)) {
|
|
1179
1179
|
/* istanbul ignore next - unreachable defensive branch */
|
|
1180
|
-
logger$
|
|
1180
|
+
logger$1c.error('task_raised_during_cancellation', {
|
|
1181
1181
|
task_name: task.name || task.id,
|
|
1182
1182
|
error: error instanceof Error ? error.message : String(error),
|
|
1183
1183
|
});
|
|
@@ -2270,6 +2270,7 @@ function validateKeyCorrelationTtlSec(ttlSec) {
|
|
|
2270
2270
|
* condition/promise and ensure at most one notifier coroutine exists for a
|
|
2271
2271
|
* flow at any time.
|
|
2272
2272
|
*/
|
|
2273
|
+
const logger$1b = getLogger('naylence.fame.flow.flow_controller');
|
|
2273
2274
|
/**
|
|
2274
2275
|
* Simple condition variable implementation for TypeScript/Node.js
|
|
2275
2276
|
* Similar to Python's asyncio.Condition
|
|
@@ -2395,8 +2396,17 @@ class FlowController {
|
|
|
2395
2396
|
// clamp into [0, initialWindow]
|
|
2396
2397
|
const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
|
|
2397
2398
|
this.credits.set(flowId, newBalance);
|
|
2399
|
+
const crossedZero = prev <= 0 && newBalance > 0;
|
|
2400
|
+
logger$1b.debug('flow_controller_add_credits', {
|
|
2401
|
+
flow_id: flowId,
|
|
2402
|
+
delta,
|
|
2403
|
+
prev_balance: prev,
|
|
2404
|
+
new_balance: newBalance,
|
|
2405
|
+
initial_window: this.initialWindow,
|
|
2406
|
+
crossed_zero: crossedZero,
|
|
2407
|
+
});
|
|
2398
2408
|
// wake waiters only if we crossed the zero boundary
|
|
2399
|
-
if (
|
|
2409
|
+
if (crossedZero) {
|
|
2400
2410
|
this.wakeWaiters(flowId);
|
|
2401
2411
|
}
|
|
2402
2412
|
return newBalance;
|
|
@@ -2407,11 +2417,27 @@ class FlowController {
|
|
|
2407
2417
|
async acquire(flowId) {
|
|
2408
2418
|
this.ensureFlow(flowId);
|
|
2409
2419
|
const condition = this.conditions.get(flowId);
|
|
2420
|
+
logger$1b.debug('flow_controller_acquire_attempt', {
|
|
2421
|
+
flow_id: flowId,
|
|
2422
|
+
current_balance: this.credits.get(flowId),
|
|
2423
|
+
});
|
|
2410
2424
|
while (this.credits.get(flowId) <= 0) {
|
|
2425
|
+
logger$1b.debug('flow_controller_waiting_for_credit', {
|
|
2426
|
+
flow_id: flowId,
|
|
2427
|
+
});
|
|
2411
2428
|
await condition.wait();
|
|
2429
|
+
logger$1b.debug('flow_controller_woke_with_credit', {
|
|
2430
|
+
flow_id: flowId,
|
|
2431
|
+
balance_after_wake: this.credits.get(flowId),
|
|
2432
|
+
});
|
|
2412
2433
|
}
|
|
2413
2434
|
const current = this.credits.get(flowId);
|
|
2414
2435
|
this.credits.set(flowId, current - 1);
|
|
2436
|
+
logger$1b.debug('flow_controller_credit_consumed', {
|
|
2437
|
+
flow_id: flowId,
|
|
2438
|
+
prev_balance: current,
|
|
2439
|
+
remaining_balance: current - 1,
|
|
2440
|
+
});
|
|
2415
2441
|
}
|
|
2416
2442
|
/**
|
|
2417
2443
|
* Consume *credits* immediately (non-blocking).
|
|
@@ -2431,6 +2457,12 @@ class FlowController {
|
|
|
2431
2457
|
const current = this.credits.get(flowId);
|
|
2432
2458
|
const remaining = Math.max(current - credits, 0);
|
|
2433
2459
|
this.credits.set(flowId, remaining);
|
|
2460
|
+
logger$1b.debug('flow_controller_consume', {
|
|
2461
|
+
flow_id: flowId,
|
|
2462
|
+
requested: credits,
|
|
2463
|
+
prev_balance: current,
|
|
2464
|
+
remaining_balance: remaining,
|
|
2465
|
+
});
|
|
2434
2466
|
return remaining;
|
|
2435
2467
|
}
|
|
2436
2468
|
/**
|
|
@@ -2451,6 +2483,10 @@ class FlowController {
|
|
|
2451
2483
|
this.windowIds.delete(flowId);
|
|
2452
2484
|
this.credits.set(flowId, this.initialWindow);
|
|
2453
2485
|
this.wakeWaiters(flowId);
|
|
2486
|
+
logger$1b.debug('flow_controller_flow_reset', {
|
|
2487
|
+
flow_id: flowId,
|
|
2488
|
+
reset_balance: this.initialWindow,
|
|
2489
|
+
});
|
|
2454
2490
|
}
|
|
2455
2491
|
/**
|
|
2456
2492
|
* Return `[windowId, flags]` for the next outbound envelope.
|
|
@@ -9298,10 +9334,12 @@ class BaseAsyncConnector extends TaskSpawner {
|
|
|
9298
9334
|
throw new FameTransportClose('Connection closed', 1006);
|
|
9299
9335
|
}
|
|
9300
9336
|
// Apply flow control if enabled and not a credit update
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
'credits' in envelope.frame)
|
|
9337
|
+
const isCreditUpdateFrame = Boolean(envelope.frame &&
|
|
9338
|
+
(envelope.frame.type === 'CreditUpdate' ||
|
|
9339
|
+
envelope.frame.type === 'credit_update' ||
|
|
9340
|
+
('flowId' in envelope.frame && 'credits' in envelope.frame) ||
|
|
9341
|
+
('flow_id' in envelope.frame && 'credits' in envelope.frame)));
|
|
9342
|
+
if (this._fcEnabled && !isCreditUpdateFrame) {
|
|
9305
9343
|
const flowId = envelope.flowId || this._connectorFlowId;
|
|
9306
9344
|
envelope.flowId = flowId;
|
|
9307
9345
|
const t0 = this._metrics ? performance.now() : 0;
|
|
@@ -9796,6 +9834,7 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9796
9834
|
? Math.floor(config.inboxCapacity)
|
|
9797
9835
|
: DEFAULT_INBOX_CAPACITY$7;
|
|
9798
9836
|
this.inbox = new BoundedAsyncQueue(preferredCapacity);
|
|
9837
|
+
this.inboxCapacity = preferredCapacity;
|
|
9799
9838
|
this.connectorId = BroadcastChannelConnector.generateConnectorId();
|
|
9800
9839
|
this.channel = new BroadcastChannel(this.channelName);
|
|
9801
9840
|
logger$_.debug('broadcast_channel_connector_created', {
|
|
@@ -9855,15 +9894,27 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9855
9894
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
9856
9895
|
const accepted = this.inbox.tryEnqueue(payload);
|
|
9857
9896
|
if (accepted) {
|
|
9897
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9898
|
+
source: 'listener',
|
|
9899
|
+
enqueue_strategy: 'try',
|
|
9900
|
+
payload_length: payload.byteLength,
|
|
9901
|
+
});
|
|
9858
9902
|
return;
|
|
9859
9903
|
}
|
|
9860
9904
|
}
|
|
9861
9905
|
this.inbox.enqueue(payload);
|
|
9906
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9907
|
+
source: 'listener',
|
|
9908
|
+
enqueue_strategy: 'enqueue',
|
|
9909
|
+
payload_length: payload.byteLength,
|
|
9910
|
+
});
|
|
9862
9911
|
}
|
|
9863
9912
|
catch (error) {
|
|
9864
9913
|
if (error instanceof QueueFullError) {
|
|
9865
9914
|
logger$_.warning('broadcast_channel_receive_queue_full', {
|
|
9866
9915
|
channel: this.channelName,
|
|
9916
|
+
inbox_capacity: this.inboxCapacity,
|
|
9917
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
9867
9918
|
});
|
|
9868
9919
|
}
|
|
9869
9920
|
else {
|
|
@@ -9874,8 +9925,10 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9874
9925
|
}
|
|
9875
9926
|
}
|
|
9876
9927
|
};
|
|
9877
|
-
|
|
9878
|
-
|
|
9928
|
+
if (!config.passive) {
|
|
9929
|
+
this.channel.addEventListener('message', this.onMsg);
|
|
9930
|
+
this.listenerRegistered = true;
|
|
9931
|
+
}
|
|
9879
9932
|
// Setup visibility change monitoring
|
|
9880
9933
|
this.visibilityChangeHandler = () => {
|
|
9881
9934
|
const isHidden = document.hidden;
|
|
@@ -9947,15 +10000,25 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9947
10000
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
9948
10001
|
const accepted = this.inbox.tryEnqueue(item);
|
|
9949
10002
|
if (accepted) {
|
|
10003
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10004
|
+
enqueue_strategy: 'try',
|
|
10005
|
+
item_type: this._describeInboxItem(item),
|
|
10006
|
+
});
|
|
9950
10007
|
return;
|
|
9951
10008
|
}
|
|
9952
10009
|
}
|
|
9953
10010
|
this.inbox.enqueue(item);
|
|
10011
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10012
|
+
enqueue_strategy: 'enqueue',
|
|
10013
|
+
item_type: this._describeInboxItem(item),
|
|
10014
|
+
});
|
|
9954
10015
|
}
|
|
9955
10016
|
catch (error) {
|
|
9956
10017
|
if (error instanceof QueueFullError) {
|
|
9957
10018
|
logger$_.warning('broadcast_channel_push_queue_full', {
|
|
9958
10019
|
channel: this.channelName,
|
|
10020
|
+
inbox_capacity: this.inboxCapacity,
|
|
10021
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
9959
10022
|
});
|
|
9960
10023
|
throw error;
|
|
9961
10024
|
}
|
|
@@ -9978,7 +10041,11 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9978
10041
|
});
|
|
9979
10042
|
}
|
|
9980
10043
|
async _transportReceive() {
|
|
9981
|
-
|
|
10044
|
+
const item = await this.inbox.dequeue();
|
|
10045
|
+
this.logInboxSnapshot('broadcast_channel_inbox_dequeued', {
|
|
10046
|
+
item_type: this._describeInboxItem(item),
|
|
10047
|
+
});
|
|
10048
|
+
return item;
|
|
9982
10049
|
}
|
|
9983
10050
|
async _transportClose(code, reason) {
|
|
9984
10051
|
logger$_.debug('broadcast_channel_transport_closing', {
|
|
@@ -10032,6 +10099,28 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
10032
10099
|
}
|
|
10033
10100
|
return rawOrEnvelope;
|
|
10034
10101
|
}
|
|
10102
|
+
_describeInboxItem(item) {
|
|
10103
|
+
if (item instanceof Uint8Array) {
|
|
10104
|
+
return 'bytes';
|
|
10105
|
+
}
|
|
10106
|
+
if (item.envelope) {
|
|
10107
|
+
return 'channel_message';
|
|
10108
|
+
}
|
|
10109
|
+
if (item.frame) {
|
|
10110
|
+
return 'envelope';
|
|
10111
|
+
}
|
|
10112
|
+
return 'unknown';
|
|
10113
|
+
}
|
|
10114
|
+
logInboxSnapshot(event, extra = {}) {
|
|
10115
|
+
logger$_.debug(event, {
|
|
10116
|
+
channel: this.channelName,
|
|
10117
|
+
connector_id: this.connectorId,
|
|
10118
|
+
connector_state: this.state,
|
|
10119
|
+
inbox_capacity: this.inboxCapacity,
|
|
10120
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
10121
|
+
...extra,
|
|
10122
|
+
});
|
|
10123
|
+
}
|
|
10035
10124
|
_shouldSkipDuplicateAck(senderId, payload) {
|
|
10036
10125
|
const dedupKey = this._extractAckDedupKey(payload);
|
|
10037
10126
|
if (!dedupKey) {
|
|
@@ -36170,6 +36259,7 @@ class BroadcastChannelListener extends TransportListener {
|
|
|
36170
36259
|
type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
|
|
36171
36260
|
channelName: this._channelName,
|
|
36172
36261
|
inboxCapacity: this._inboxCapacity,
|
|
36262
|
+
passive: true,
|
|
36173
36263
|
};
|
|
36174
36264
|
}
|
|
36175
36265
|
try {
|
|
@@ -36230,6 +36320,7 @@ class BroadcastChannelListener extends TransportListener {
|
|
|
36230
36320
|
type: BROADCAST_CHANNEL_CONNECTOR_TYPE,
|
|
36231
36321
|
channelName: this._channelName,
|
|
36232
36322
|
inboxCapacity: this._inboxCapacity,
|
|
36323
|
+
passive: true,
|
|
36233
36324
|
};
|
|
36234
36325
|
const channelCandidate = candidate.channelName ?? candidate['channel_name'];
|
|
36235
36326
|
if (typeof channelCandidate === 'string' && channelCandidate.trim().length > 0) {
|