pulse-js-framework 1.7.30 → 1.7.32
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/cli/build.js +34 -1
- package/cli/dev.js +54 -3
- package/cli/index.js +20 -5
- package/compiler/preprocessor.js +819 -0
- package/loader/vite-plugin.js +57 -12
- package/package.json +14 -2
- package/runtime/async.js +14 -26
- package/runtime/devtools/diagnostics.js +51 -3
- package/runtime/devtools.js +58 -1
- package/runtime/errors.js +159 -0
- package/runtime/graphql.js +83 -113
- package/runtime/http.js +18 -100
- package/runtime/interceptor-manager.js +242 -0
- package/runtime/router.js +80 -15
- package/runtime/utils.js +121 -5
- package/runtime/websocket.js +62 -73
package/runtime/websocket.js
CHANGED
|
@@ -26,29 +26,32 @@
|
|
|
26
26
|
|
|
27
27
|
import { pulse, computed, batch, onCleanup } from './pulse.js';
|
|
28
28
|
import { createVersionedAsync } from './async.js';
|
|
29
|
-
import {
|
|
29
|
+
import { ClientError } from './errors.js';
|
|
30
30
|
import { loggers } from './logger.js';
|
|
31
|
+
import { MessageInterceptorManager } from './interceptor-manager.js';
|
|
31
32
|
|
|
32
33
|
// ============================================================================
|
|
33
34
|
// WebSocket Error Class
|
|
34
35
|
// ============================================================================
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
* WebSocket
|
|
38
|
+
* WebSocket Error with connection and reconnection context.
|
|
39
|
+
* Extends ClientError for consistent error handling patterns.
|
|
38
40
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
export class WebSocketError extends ClientError {
|
|
42
|
+
static suggestions = {
|
|
43
|
+
CONNECT_FAILED: 'Check the WebSocket URL and ensure the server is running. Verify CORS settings if connecting cross-origin.',
|
|
44
|
+
CLOSE: 'The connection was closed. Check close code for reason. Common codes: 1000 (normal), 1001 (going away), 1006 (abnormal).',
|
|
45
|
+
TIMEOUT: 'Connection timed out. Check network conditions or increase the timeout value.',
|
|
46
|
+
PARSE_ERROR: 'Failed to parse incoming message. Check message format matches expected type (JSON/binary).',
|
|
47
|
+
SEND_FAILED: 'Failed to send message. Connection may be closed or message exceeds size limit.',
|
|
48
|
+
RECONNECT_EXHAUSTED: 'Maximum reconnection attempts reached. Consider increasing maxRetries or checking server availability.'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
static errorName = 'WebSocketError';
|
|
52
|
+
static defaultCode = 'WEBSOCKET_ERROR';
|
|
53
|
+
static markerProperty = 'isWebSocketError';
|
|
47
54
|
|
|
48
|
-
/**
|
|
49
|
-
* WebSocket Error with connection context
|
|
50
|
-
*/
|
|
51
|
-
export class WebSocketError extends RuntimeError {
|
|
52
55
|
/**
|
|
53
56
|
* @param {string} message - Error message
|
|
54
57
|
* @param {Object} [options={}] - Error options
|
|
@@ -57,25 +60,21 @@ export class WebSocketError extends RuntimeError {
|
|
|
57
60
|
* @param {number} [options.closeCode] - WebSocket close code
|
|
58
61
|
* @param {string} [options.closeReason] - WebSocket close reason
|
|
59
62
|
* @param {Event} [options.event] - Original event
|
|
63
|
+
* @param {number} [options.reconnectAttempt] - Current reconnection attempt number
|
|
64
|
+
* @param {number} [options.maxRetries] - Maximum reconnection retries configured
|
|
65
|
+
* @param {number} [options.nextRetryDelay] - Delay until next retry in ms
|
|
60
66
|
*/
|
|
61
67
|
constructor(message, options = {}) {
|
|
62
|
-
|
|
63
|
-
const formattedMessage = createErrorMessage({
|
|
64
|
-
code,
|
|
65
|
-
message,
|
|
66
|
-
context: options.context,
|
|
67
|
-
suggestion: options.suggestion || WEBSOCKET_SUGGESTIONS[code]
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
super(formattedMessage, { code });
|
|
71
|
-
|
|
72
|
-
this.name = 'WebSocketError';
|
|
73
|
-
this.code = code;
|
|
68
|
+
super(message, options);
|
|
74
69
|
this.url = options.url || null;
|
|
75
70
|
this.closeCode = options.closeCode || null;
|
|
76
71
|
this.closeReason = options.closeReason || null;
|
|
77
72
|
this.event = options.event || null;
|
|
78
|
-
|
|
73
|
+
|
|
74
|
+
// Reconnection context
|
|
75
|
+
this.reconnectAttempt = typeof options.reconnectAttempt === 'number' ? options.reconnectAttempt : null;
|
|
76
|
+
this.maxRetries = typeof options.maxRetries === 'number' ? options.maxRetries : null;
|
|
77
|
+
this.nextRetryDelay = typeof options.nextRetryDelay === 'number' ? options.nextRetryDelay : null;
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
/**
|
|
@@ -89,67 +88,45 @@ export class WebSocketError extends RuntimeError {
|
|
|
89
88
|
|
|
90
89
|
isConnectFailed() { return this.code === 'CONNECT_FAILED'; }
|
|
91
90
|
isClose() { return this.code === 'CLOSE'; }
|
|
92
|
-
isTimeout() { return this.code === 'TIMEOUT'; }
|
|
93
91
|
isParseError() { return this.code === 'PARSE_ERROR'; }
|
|
94
92
|
isSendFailed() { return this.code === 'SEND_FAILED'; }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ============================================================================
|
|
98
|
-
// Message Interceptor Manager
|
|
99
|
-
// ============================================================================
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Manages message interceptors for incoming/outgoing messages
|
|
103
|
-
*/
|
|
104
|
-
class MessageInterceptorManager {
|
|
105
|
-
#handlers = new Map();
|
|
106
|
-
#idCounter = 0;
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Add a message interceptor
|
|
110
|
-
* @param {Function} onMessage - Transform message data
|
|
111
|
-
* @param {Function} [onError] - Handle interceptor errors
|
|
112
|
-
* @returns {number} Interceptor ID
|
|
113
|
-
*/
|
|
114
|
-
use(onMessage, onError) {
|
|
115
|
-
const id = this.#idCounter++;
|
|
116
|
-
this.#handlers.set(id, { onMessage, onError });
|
|
117
|
-
return id;
|
|
118
|
-
}
|
|
93
|
+
isReconnectExhausted() { return this.code === 'RECONNECT_EXHAUSTED'; }
|
|
119
94
|
|
|
120
95
|
/**
|
|
121
|
-
*
|
|
122
|
-
* @
|
|
123
|
-
*/
|
|
124
|
-
eject(id) {
|
|
125
|
-
this.#handlers.delete(id);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Remove all interceptors
|
|
96
|
+
* Check if this error occurred during reconnection
|
|
97
|
+
* @returns {boolean}
|
|
130
98
|
*/
|
|
131
|
-
|
|
132
|
-
this
|
|
99
|
+
isReconnecting() {
|
|
100
|
+
return this.reconnectAttempt !== null && this.reconnectAttempt > 0;
|
|
133
101
|
}
|
|
134
102
|
|
|
135
103
|
/**
|
|
136
|
-
*
|
|
137
|
-
* @returns {
|
|
104
|
+
* Check if more reconnection attempts are available
|
|
105
|
+
* @returns {boolean}
|
|
138
106
|
*/
|
|
139
|
-
|
|
140
|
-
|
|
107
|
+
canRetry() {
|
|
108
|
+
if (this.maxRetries === null || this.reconnectAttempt === null) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return this.reconnectAttempt < this.maxRetries;
|
|
141
112
|
}
|
|
142
113
|
|
|
143
114
|
/**
|
|
144
|
-
*
|
|
115
|
+
* Get remaining reconnection attempts
|
|
116
|
+
* @returns {number|null}
|
|
145
117
|
*/
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
118
|
+
getRemainingRetries() {
|
|
119
|
+
if (this.maxRetries === null || this.reconnectAttempt === null) {
|
|
120
|
+
return null;
|
|
149
121
|
}
|
|
122
|
+
return Math.max(0, this.maxRetries - this.reconnectAttempt);
|
|
150
123
|
}
|
|
151
124
|
}
|
|
152
125
|
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// Message Interceptor Factory
|
|
128
|
+
// ============================================================================
|
|
129
|
+
|
|
153
130
|
// ============================================================================
|
|
154
131
|
// Internal Helpers
|
|
155
132
|
// ============================================================================
|
|
@@ -487,10 +464,13 @@ export function createWebSocket(url, options = {}) {
|
|
|
487
464
|
|
|
488
465
|
function handleSocketError(event, ctx) {
|
|
489
466
|
ctx.ifCurrent(() => {
|
|
467
|
+
const attempt = reconnectAttempt.get();
|
|
490
468
|
const wsError = new WebSocketError('WebSocket error', {
|
|
491
469
|
code: 'CONNECT_FAILED',
|
|
492
470
|
url,
|
|
493
|
-
event
|
|
471
|
+
event,
|
|
472
|
+
reconnectAttempt: attempt > 0 ? attempt : null,
|
|
473
|
+
maxRetries: opts.reconnect ? opts.maxRetries : null
|
|
494
474
|
});
|
|
495
475
|
error.set(wsError);
|
|
496
476
|
eventEmitter.emit('error', wsError);
|
|
@@ -501,6 +481,12 @@ export function createWebSocket(url, options = {}) {
|
|
|
501
481
|
|
|
502
482
|
function handleError(wsError, ctx) {
|
|
503
483
|
ctx.ifCurrent(() => {
|
|
484
|
+
// Add reconnection context to error if not already present
|
|
485
|
+
if (wsError.reconnectAttempt === null && opts.reconnect) {
|
|
486
|
+
wsError.reconnectAttempt = reconnectAttempt.get();
|
|
487
|
+
wsError.maxRetries = opts.maxRetries;
|
|
488
|
+
}
|
|
489
|
+
|
|
504
490
|
error.set(wsError);
|
|
505
491
|
state.set('closed');
|
|
506
492
|
eventEmitter.emit('error', wsError);
|
|
@@ -560,7 +546,9 @@ export function createWebSocket(url, options = {}) {
|
|
|
560
546
|
if (opts.maxRetries > 0 && attempt >= opts.maxRetries) {
|
|
561
547
|
error.set(new WebSocketError('Max reconnection attempts reached', {
|
|
562
548
|
code: 'RECONNECT_EXHAUSTED',
|
|
563
|
-
url
|
|
549
|
+
url,
|
|
550
|
+
reconnectAttempt: attempt,
|
|
551
|
+
maxRetries: opts.maxRetries
|
|
564
552
|
}));
|
|
565
553
|
reconnecting.set(false);
|
|
566
554
|
return;
|
|
@@ -864,6 +852,7 @@ export function useWebSocket(url, options = {}) {
|
|
|
864
852
|
// Exports
|
|
865
853
|
// ============================================================================
|
|
866
854
|
|
|
855
|
+
// Re-export MessageInterceptorManager from shared module
|
|
867
856
|
export { MessageInterceptorManager };
|
|
868
857
|
|
|
869
858
|
export default {
|