pulse-js-framework 1.10.0 → 1.10.3
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/compiler/parser/_extract.js +393 -0
- package/compiler/parser/blocks.js +361 -0
- package/compiler/parser/core.js +306 -0
- package/compiler/parser/expressions.js +386 -0
- package/compiler/parser/imports.js +108 -0
- package/compiler/parser/index.js +47 -0
- package/compiler/parser/state.js +155 -0
- package/compiler/parser/style.js +445 -0
- package/compiler/parser/view.js +632 -0
- package/compiler/parser.js +15 -2372
- package/compiler/parser.js.original +2376 -0
- package/package.json +2 -1
- package/runtime/a11y/announcements.js +213 -0
- package/runtime/a11y/contrast.js +125 -0
- package/runtime/a11y/focus.js +412 -0
- package/runtime/a11y/index.js +35 -0
- package/runtime/a11y/preferences.js +121 -0
- package/runtime/a11y/utils.js +164 -0
- package/runtime/a11y/validation.js +258 -0
- package/runtime/a11y/widgets.js +545 -0
- package/runtime/a11y.js +15 -1840
- package/runtime/a11y.js.original +1844 -0
- package/runtime/graphql/cache.js +69 -0
- package/runtime/graphql/client.js +563 -0
- package/runtime/graphql/hooks.js +492 -0
- package/runtime/graphql/index.js +62 -0
- package/runtime/graphql/subscriptions.js +241 -0
- package/runtime/graphql.js +12 -1322
- package/runtime/graphql.js.original +1326 -0
- package/runtime/router/core.js +956 -0
- package/runtime/router/guards.js +90 -0
- package/runtime/router/history.js +204 -0
- package/runtime/router/index.js +36 -0
- package/runtime/router/lazy.js +180 -0
- package/runtime/router/utils.js +226 -0
- package/runtime/router.js +12 -1600
- package/runtime/router.js.original +1605 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse GraphQL - Query Caching
|
|
3
|
+
*
|
|
4
|
+
* Utilities for creating cache keys and managing query cache
|
|
5
|
+
*
|
|
6
|
+
* @module pulse-js-framework/runtime/graphql/cache
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { LRUCache } from '../lru-cache.js';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Cache Key Utilities
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract operation name from GraphQL query string
|
|
17
|
+
* @param {string} query - GraphQL query string
|
|
18
|
+
* @returns {string|null} Operation name or null
|
|
19
|
+
*/
|
|
20
|
+
export function extractOperationName(query) {
|
|
21
|
+
const match = query.match(/(?:query|mutation|subscription)\s+(\w+)/);
|
|
22
|
+
return match ? match[1] : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Simple hash function for strings
|
|
27
|
+
* @param {string} str - String to hash
|
|
28
|
+
* @returns {string} Hash string
|
|
29
|
+
*/
|
|
30
|
+
function hashString(str) {
|
|
31
|
+
if (!str) return '';
|
|
32
|
+
let hash = 0;
|
|
33
|
+
for (let i = 0; i < str.length; i++) {
|
|
34
|
+
const char = str.charCodeAt(i);
|
|
35
|
+
hash = ((hash << 5) - hash) + char;
|
|
36
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
37
|
+
}
|
|
38
|
+
return Math.abs(hash).toString(36);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Stable JSON stringify with sorted keys
|
|
43
|
+
* @param {*} obj - Object to stringify
|
|
44
|
+
* @returns {string} Stable JSON string
|
|
45
|
+
*/
|
|
46
|
+
function stableStringify(obj) {
|
|
47
|
+
if (obj === null || obj === undefined) return '';
|
|
48
|
+
if (typeof obj !== 'object') return JSON.stringify(obj);
|
|
49
|
+
if (Array.isArray(obj)) {
|
|
50
|
+
return '[' + obj.map(stableStringify).join(',') + ']';
|
|
51
|
+
}
|
|
52
|
+
const keys = Object.keys(obj).sort();
|
|
53
|
+
return '{' + keys.map(k => `"${k}":${stableStringify(obj[k])}`).join(',') + '}';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate a deterministic cache key for a GraphQL operation
|
|
58
|
+
* @param {string} query - GraphQL query string
|
|
59
|
+
* @param {Object} [variables] - Query variables
|
|
60
|
+
* @returns {string} Cache key
|
|
61
|
+
*/
|
|
62
|
+
export function generateCacheKey(query, variables) {
|
|
63
|
+
const operationName = extractOperationName(query);
|
|
64
|
+
const variablesHash = variables ? hashString(stableStringify(variables)) : '';
|
|
65
|
+
const queryHash = operationName || hashString(query.replace(/\s+/g, ' ').trim());
|
|
66
|
+
return `gql:${queryHash}${variablesHash ? ':' + variablesHash : ''}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse GraphQL - Core Client
|
|
3
|
+
*
|
|
4
|
+
* GraphQL client with error handling, HTTP requests, and interceptors
|
|
5
|
+
*
|
|
6
|
+
* @module pulse-js-framework/runtime/graphql/client
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { pulse, computed, batch } from '../pulse.js';
|
|
10
|
+
import { createHttp, HttpError } from '../http.js';
|
|
11
|
+
import { ClientError } from '../errors.js';
|
|
12
|
+
import { LRUCache } from '../lru-cache.js';
|
|
13
|
+
import { InterceptorManager } from '../interceptor-manager.js';
|
|
14
|
+
import { onWindowFocus, onWindowOnline } from '../utils.js';
|
|
15
|
+
import { generateCacheKey, extractOperationName } from './cache.js';
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// GraphQL Error Class
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Error class for GraphQL operations.
|
|
23
|
+
* Extends ClientError for consistent error handling patterns.
|
|
24
|
+
*/
|
|
25
|
+
export class GraphQLError extends ClientError {
|
|
26
|
+
static suggestions = {
|
|
27
|
+
GRAPHQL_ERROR: 'Check the GraphQL errors array for specific field errors.',
|
|
28
|
+
NETWORK_ERROR: 'Verify network connectivity and GraphQL endpoint URL.',
|
|
29
|
+
PARSE_ERROR: 'The response was not valid GraphQL JSON. Check server configuration.',
|
|
30
|
+
TIMEOUT: 'Request timed out. Consider increasing timeout or optimizing query.',
|
|
31
|
+
AUTHENTICATION_ERROR: 'Authentication required. Check your credentials or token.',
|
|
32
|
+
AUTHORIZATION_ERROR: 'Insufficient permissions for this operation.',
|
|
33
|
+
VALIDATION_ERROR: 'Invalid input provided. Check variables match schema types.',
|
|
34
|
+
SUBSCRIPTION_ERROR: 'WebSocket subscription failed. Check connection status.'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
static errorName = 'GraphQLError';
|
|
38
|
+
static defaultCode = 'GRAPHQL_ERROR';
|
|
39
|
+
static markerProperty = 'isGraphQLError';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} message - Error message
|
|
43
|
+
* @param {Object} [options={}] - Error options
|
|
44
|
+
* @param {string} [options.code] - Error code
|
|
45
|
+
* @param {Array} [options.errors] - GraphQL errors array from response
|
|
46
|
+
* @param {*} [options.data] - Partial data from response
|
|
47
|
+
* @param {Object} [options.extensions] - GraphQL extensions
|
|
48
|
+
* @param {Object} [options.response] - Full HTTP response
|
|
49
|
+
* @param {Object} [options.request] - Request configuration
|
|
50
|
+
*/
|
|
51
|
+
constructor(message, options = {}) {
|
|
52
|
+
super(message, options);
|
|
53
|
+
this.errors = options.errors || [];
|
|
54
|
+
this.data = options.data ?? null;
|
|
55
|
+
this.extensions = options.extensions || {};
|
|
56
|
+
this.response = options.response || null;
|
|
57
|
+
this.request = options.request || null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if error is a GraphQLError
|
|
62
|
+
* @param {*} error - Error to check
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
static isGraphQLError(error) {
|
|
66
|
+
return error?.isGraphQLError === true || error instanceof GraphQLError;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if response has partial data along with errors
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
hasPartialData() {
|
|
74
|
+
return this.data !== null && this.data !== undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if this is an authentication error
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
isAuthenticationError() {
|
|
82
|
+
return this.code === 'AUTHENTICATION_ERROR' ||
|
|
83
|
+
this.errors.some(e => e.extensions?.code === 'UNAUTHENTICATED');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if this is an authorization error
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
isAuthorizationError() {
|
|
91
|
+
return this.code === 'AUTHORIZATION_ERROR' ||
|
|
92
|
+
this.errors.some(e => e.extensions?.code === 'FORBIDDEN');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if this is a validation error
|
|
97
|
+
* @returns {boolean}
|
|
98
|
+
*/
|
|
99
|
+
isValidationError() {
|
|
100
|
+
return this.code === 'VALIDATION_ERROR' ||
|
|
101
|
+
this.errors.some(e => e.extensions?.code === 'BAD_USER_INPUT');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get the first error message from GraphQL errors
|
|
106
|
+
* @returns {string|null}
|
|
107
|
+
*/
|
|
108
|
+
getFirstError() {
|
|
109
|
+
return this.errors[0]?.message || null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get all error messages
|
|
114
|
+
* @returns {string[]}
|
|
115
|
+
*/
|
|
116
|
+
getAllErrors() {
|
|
117
|
+
return this.errors.map(e => e.message);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert to JSON
|
|
122
|
+
* @returns {Object}
|
|
123
|
+
*/
|
|
124
|
+
toJSON() {
|
|
125
|
+
return {
|
|
126
|
+
name: this.name,
|
|
127
|
+
message: this.message,
|
|
128
|
+
code: this.code,
|
|
129
|
+
errors: this.errors,
|
|
130
|
+
data: this.data,
|
|
131
|
+
extensions: this.extensions
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// GraphQL Client
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* GraphQL Client class
|
|
142
|
+
*/
|
|
143
|
+
export class GraphQLClient {
|
|
144
|
+
#http;
|
|
145
|
+
#ws = null;
|
|
146
|
+
#subscriptionManager = null;
|
|
147
|
+
#options;
|
|
148
|
+
#inflightQueries = new Map();
|
|
149
|
+
// LRU cache to prevent unbounded memory growth (default 500 entries)
|
|
150
|
+
#cache;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Request interceptors
|
|
154
|
+
*/
|
|
155
|
+
interceptors = {
|
|
156
|
+
request: new InterceptorManager(),
|
|
157
|
+
response: new InterceptorManager()
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {Object} options - Client options
|
|
162
|
+
*/
|
|
163
|
+
constructor(options = {}) {
|
|
164
|
+
if (!options.url) {
|
|
165
|
+
throw new GraphQLError('GraphQL client requires a url option', {
|
|
166
|
+
code: 'GRAPHQL_ERROR'
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.#options = {
|
|
171
|
+
url: options.url,
|
|
172
|
+
wsUrl: options.wsUrl || this.#deriveWsUrl(options.url),
|
|
173
|
+
headers: options.headers || {},
|
|
174
|
+
timeout: options.timeout ?? 30000,
|
|
175
|
+
credentials: options.credentials || 'same-origin',
|
|
176
|
+
retries: options.retries ?? 0,
|
|
177
|
+
retryDelay: options.retryDelay ?? 1000,
|
|
178
|
+
wsConnectionParams: options.wsConnectionParams,
|
|
179
|
+
wsReconnect: options.wsReconnect ?? true,
|
|
180
|
+
wsMaxRetries: options.wsMaxRetries ?? 5,
|
|
181
|
+
cache: options.cache ?? true,
|
|
182
|
+
cacheTime: options.cacheTime ?? 300000,
|
|
183
|
+
cacheMaxSize: options.cacheMaxSize ?? 500,
|
|
184
|
+
staleTime: options.staleTime ?? 0,
|
|
185
|
+
dedupe: options.dedupe ?? true,
|
|
186
|
+
throwOnError: options.throwOnError ?? true,
|
|
187
|
+
onError: options.onError
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Initialize LRU cache to prevent unbounded memory growth
|
|
191
|
+
this.#cache = new LRUCache(this.#options.cacheMaxSize);
|
|
192
|
+
|
|
193
|
+
// Create HTTP client for queries/mutations
|
|
194
|
+
this.#http = createHttp({
|
|
195
|
+
baseURL: '',
|
|
196
|
+
timeout: this.#options.timeout,
|
|
197
|
+
headers: {
|
|
198
|
+
'Content-Type': 'application/json',
|
|
199
|
+
...this.#options.headers
|
|
200
|
+
},
|
|
201
|
+
withCredentials: this.#options.credentials === 'include',
|
|
202
|
+
retries: this.#options.retries,
|
|
203
|
+
retryDelay: this.#options.retryDelay
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Derive WebSocket URL from HTTP URL
|
|
209
|
+
* @param {string} url - HTTP URL
|
|
210
|
+
* @returns {string} WebSocket URL
|
|
211
|
+
*/
|
|
212
|
+
#deriveWsUrl(url) {
|
|
213
|
+
try {
|
|
214
|
+
const parsed = new URL(url, globalThis.location?.origin || 'http://localhost');
|
|
215
|
+
parsed.protocol = parsed.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
216
|
+
return parsed.toString();
|
|
217
|
+
} catch {
|
|
218
|
+
return url.replace(/^http/, 'ws');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Initialize WebSocket for subscriptions (lazy)
|
|
224
|
+
*/
|
|
225
|
+
#initWebSocket() {
|
|
226
|
+
if (this.#ws) return;
|
|
227
|
+
|
|
228
|
+
this.#ws = createWebSocket(this.#options.wsUrl, {
|
|
229
|
+
protocols: ['graphql-transport-ws'],
|
|
230
|
+
reconnect: this.#options.wsReconnect,
|
|
231
|
+
maxRetries: this.#options.wsMaxRetries,
|
|
232
|
+
autoConnect: false,
|
|
233
|
+
autoParseJson: true,
|
|
234
|
+
queueWhileDisconnected: true
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
this.#subscriptionManager = new SubscriptionManager(
|
|
238
|
+
this.#ws,
|
|
239
|
+
this.#options.wsConnectionParams
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Execute a GraphQL operation
|
|
245
|
+
* @param {string} query - GraphQL query/mutation
|
|
246
|
+
* @param {Object} [variables] - Variables
|
|
247
|
+
* @param {Object} [options] - Request options
|
|
248
|
+
* @returns {Promise<Object>} Response data
|
|
249
|
+
*/
|
|
250
|
+
async #execute(query, variables, options = {}) {
|
|
251
|
+
const operationName = extractOperationName(query);
|
|
252
|
+
|
|
253
|
+
let config = {
|
|
254
|
+
query,
|
|
255
|
+
variables,
|
|
256
|
+
operationName,
|
|
257
|
+
...options
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Run request interceptors
|
|
261
|
+
for (const interceptor of this.interceptors.request) {
|
|
262
|
+
if (interceptor.fulfilled) {
|
|
263
|
+
try {
|
|
264
|
+
config = await interceptor.fulfilled(config);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
if (interceptor.rejected) {
|
|
267
|
+
config = await interceptor.rejected(err);
|
|
268
|
+
} else {
|
|
269
|
+
throw err;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const response = await this.#http.post(this.#options.url, {
|
|
277
|
+
query: config.query,
|
|
278
|
+
variables: config.variables,
|
|
279
|
+
operationName: config.operationName
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
let result = response.data;
|
|
283
|
+
|
|
284
|
+
// Run response interceptors
|
|
285
|
+
for (const interceptor of this.interceptors.response) {
|
|
286
|
+
if (interceptor.fulfilled) {
|
|
287
|
+
try {
|
|
288
|
+
result = await interceptor.fulfilled(result);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
if (interceptor.rejected) {
|
|
291
|
+
result = await interceptor.rejected(err);
|
|
292
|
+
} else {
|
|
293
|
+
throw err;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return this.#processResponse(result, config);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
// Convert HTTP errors to GraphQL errors
|
|
302
|
+
if (HttpError.isHttpError(error)) {
|
|
303
|
+
const graphqlError = new GraphQLError(error.message, {
|
|
304
|
+
code: error.isTimeout() ? 'TIMEOUT' : error.isNetworkError() ? 'NETWORK_ERROR' : 'GRAPHQL_ERROR',
|
|
305
|
+
request: config
|
|
306
|
+
});
|
|
307
|
+
this.#options.onError?.(graphqlError);
|
|
308
|
+
throw graphqlError;
|
|
309
|
+
}
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Process GraphQL response
|
|
316
|
+
* @param {Object} response - GraphQL response
|
|
317
|
+
* @param {Object} config - Request config
|
|
318
|
+
* @returns {*} Response data
|
|
319
|
+
*/
|
|
320
|
+
#processResponse(response, config) {
|
|
321
|
+
const { data, errors, extensions } = response;
|
|
322
|
+
|
|
323
|
+
// No errors - return data
|
|
324
|
+
if (!errors || errors.length === 0) {
|
|
325
|
+
return data;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Has errors
|
|
329
|
+
const error = new GraphQLError(errors[0].message, {
|
|
330
|
+
code: this.#mapErrorCode(errors[0]),
|
|
331
|
+
errors,
|
|
332
|
+
data,
|
|
333
|
+
extensions,
|
|
334
|
+
request: config
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
this.#options.onError?.(error);
|
|
338
|
+
|
|
339
|
+
if (this.#options.throwOnError) {
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Return data even with errors if throwOnError is false
|
|
344
|
+
return data;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Map GraphQL error to error code
|
|
349
|
+
* @param {Object} error - GraphQL error
|
|
350
|
+
* @returns {string} Error code
|
|
351
|
+
*/
|
|
352
|
+
#mapErrorCode(error) {
|
|
353
|
+
const code = error.extensions?.code;
|
|
354
|
+
switch (code) {
|
|
355
|
+
case 'UNAUTHENTICATED': return 'AUTHENTICATION_ERROR';
|
|
356
|
+
case 'FORBIDDEN': return 'AUTHORIZATION_ERROR';
|
|
357
|
+
case 'BAD_USER_INPUT': return 'VALIDATION_ERROR';
|
|
358
|
+
case 'INTERNAL_SERVER_ERROR': return 'GRAPHQL_ERROR';
|
|
359
|
+
default: return 'GRAPHQL_ERROR';
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Execute a GraphQL query
|
|
365
|
+
* @param {string} query - GraphQL query
|
|
366
|
+
* @param {Object} [variables] - Query variables
|
|
367
|
+
* @param {Object} [options] - Query options
|
|
368
|
+
* @returns {Promise<*>} Query result
|
|
369
|
+
*/
|
|
370
|
+
async query(query, variables, options = {}) {
|
|
371
|
+
const cacheKey = options.cacheKey || generateCacheKey(query, variables);
|
|
372
|
+
|
|
373
|
+
// Check for in-flight request (deduplication)
|
|
374
|
+
if (this.#options.dedupe && this.#inflightQueries.has(cacheKey)) {
|
|
375
|
+
return this.#inflightQueries.get(cacheKey);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const promise = this.#execute(query, variables, options)
|
|
379
|
+
.finally(() => {
|
|
380
|
+
this.#inflightQueries.delete(cacheKey);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if (this.#options.dedupe) {
|
|
384
|
+
this.#inflightQueries.set(cacheKey, promise);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return promise;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Execute a GraphQL mutation
|
|
392
|
+
* @param {string} mutation - GraphQL mutation
|
|
393
|
+
* @param {Object} [variables] - Mutation variables
|
|
394
|
+
* @param {Object} [options] - Mutation options
|
|
395
|
+
* @returns {Promise<*>} Mutation result
|
|
396
|
+
*/
|
|
397
|
+
async mutate(mutation, variables, options = {}) {
|
|
398
|
+
return this.#execute(mutation, variables, options);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Subscribe to a GraphQL subscription
|
|
403
|
+
* @param {string} subscription - GraphQL subscription
|
|
404
|
+
* @param {Object} [variables] - Subscription variables
|
|
405
|
+
* @param {Object} handlers - Event handlers
|
|
406
|
+
* @returns {Function} Unsubscribe function
|
|
407
|
+
*/
|
|
408
|
+
subscribe(subscription, variables, handlers) {
|
|
409
|
+
this.#initWebSocket();
|
|
410
|
+
return this.#subscriptionManager.subscribe(subscription, variables, handlers);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Invalidate a cache entry
|
|
415
|
+
* @param {string} cacheKey - Cache key to invalidate
|
|
416
|
+
*/
|
|
417
|
+
invalidate(cacheKey) {
|
|
418
|
+
this.#cache.delete(cacheKey);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Invalidate all cache entries
|
|
423
|
+
*/
|
|
424
|
+
invalidateAll() {
|
|
425
|
+
this.#cache.clear();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get cache statistics
|
|
430
|
+
* @returns {Object} Cache stats
|
|
431
|
+
*/
|
|
432
|
+
getCacheStats() {
|
|
433
|
+
return {
|
|
434
|
+
size: this.#cache.size,
|
|
435
|
+
keys: Array.from(this.#cache.keys())
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Get active subscriptions count
|
|
441
|
+
* @returns {number}
|
|
442
|
+
*/
|
|
443
|
+
getActiveSubscriptions() {
|
|
444
|
+
return this.#subscriptionManager?.activeCount || 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Close all subscriptions
|
|
449
|
+
*/
|
|
450
|
+
closeAllSubscriptions() {
|
|
451
|
+
this.#subscriptionManager?.closeAll();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get WebSocket connection state
|
|
456
|
+
* @returns {Pulse<string>}
|
|
457
|
+
*/
|
|
458
|
+
get wsState() {
|
|
459
|
+
this.#initWebSocket();
|
|
460
|
+
return this.#ws.state;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get WebSocket connected state
|
|
465
|
+
* @returns {Pulse<boolean>}
|
|
466
|
+
*/
|
|
467
|
+
get wsConnected() {
|
|
468
|
+
this.#initWebSocket();
|
|
469
|
+
return this.#subscriptionManager.connected;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Create a child client with merged configuration
|
|
474
|
+
* @param {Object} options - Override options
|
|
475
|
+
* @returns {GraphQLClient} New client instance
|
|
476
|
+
*/
|
|
477
|
+
create(options = {}) {
|
|
478
|
+
return new GraphQLClient({
|
|
479
|
+
...this.#options,
|
|
480
|
+
headers: { ...this.#options.headers, ...options.headers },
|
|
481
|
+
...options
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Dispose the client
|
|
487
|
+
*/
|
|
488
|
+
dispose() {
|
|
489
|
+
this.#subscriptionManager?.dispose();
|
|
490
|
+
this.#inflightQueries.clear();
|
|
491
|
+
this.#cache.clear();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ============================================================================
|
|
496
|
+
// Factory Function
|
|
497
|
+
// ============================================================================
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Create a GraphQL client instance
|
|
501
|
+
* @param {Object} options - Client options
|
|
502
|
+
* @param {string} options.url - GraphQL endpoint URL
|
|
503
|
+
* @param {string} [options.wsUrl] - WebSocket URL for subscriptions
|
|
504
|
+
* @param {Object} [options.headers] - Default headers
|
|
505
|
+
* @param {number} [options.timeout=30000] - Request timeout in ms
|
|
506
|
+
* @param {string} [options.credentials='same-origin'] - Fetch credentials
|
|
507
|
+
* @param {number} [options.retries=0] - Retry attempts
|
|
508
|
+
* @param {number} [options.retryDelay=1000] - Delay between retries
|
|
509
|
+
* @param {Object|Function} [options.wsConnectionParams] - WebSocket connection params
|
|
510
|
+
* @param {boolean} [options.wsReconnect=true] - Auto-reconnect WebSocket
|
|
511
|
+
* @param {number} [options.wsMaxRetries=5] - Max WebSocket reconnection attempts
|
|
512
|
+
* @param {boolean} [options.cache=true] - Enable query caching
|
|
513
|
+
* @param {number} [options.cacheTime=300000] - Cache TTL in ms
|
|
514
|
+
* @param {number} [options.cacheMaxSize=500] - Maximum cache entries (LRU eviction)
|
|
515
|
+
* @param {number} [options.staleTime=0] - Stale threshold in ms
|
|
516
|
+
* @param {boolean} [options.dedupe=true] - Deduplicate identical in-flight queries
|
|
517
|
+
* @param {boolean} [options.throwOnError=true] - Throw on GraphQL errors
|
|
518
|
+
* @param {Function} [options.onError] - Global error handler
|
|
519
|
+
* @returns {GraphQLClient} GraphQL client instance
|
|
520
|
+
*/
|
|
521
|
+
export function createGraphQLClient(options = {}) {
|
|
522
|
+
return new GraphQLClient(options);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ============================================================================
|
|
526
|
+
// Default Client
|
|
527
|
+
// ============================================================================
|
|
528
|
+
|
|
529
|
+
let defaultClient = null;
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Set the default GraphQL client
|
|
533
|
+
* @param {GraphQLClient} client - Client instance
|
|
534
|
+
*/
|
|
535
|
+
export function setDefaultClient(client) {
|
|
536
|
+
defaultClient = client;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Get the default GraphQL client
|
|
541
|
+
* @returns {GraphQLClient|null}
|
|
542
|
+
*/
|
|
543
|
+
export function getDefaultClient() {
|
|
544
|
+
return defaultClient;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Get client from options or default
|
|
549
|
+
* @param {Object} options - Options with optional client
|
|
550
|
+
* @returns {GraphQLClient} Client instance
|
|
551
|
+
*/
|
|
552
|
+
function getClient(options) {
|
|
553
|
+
const client = options.client || defaultClient;
|
|
554
|
+
if (!client) {
|
|
555
|
+
throw new GraphQLError(
|
|
556
|
+
'No GraphQL client provided. Either pass a client option or set a default client with setDefaultClient().',
|
|
557
|
+
{ code: 'GRAPHQL_ERROR' }
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
return client;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
|