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.
Files changed (37) hide show
  1. package/compiler/parser/_extract.js +393 -0
  2. package/compiler/parser/blocks.js +361 -0
  3. package/compiler/parser/core.js +306 -0
  4. package/compiler/parser/expressions.js +386 -0
  5. package/compiler/parser/imports.js +108 -0
  6. package/compiler/parser/index.js +47 -0
  7. package/compiler/parser/state.js +155 -0
  8. package/compiler/parser/style.js +445 -0
  9. package/compiler/parser/view.js +632 -0
  10. package/compiler/parser.js +15 -2372
  11. package/compiler/parser.js.original +2376 -0
  12. package/package.json +2 -1
  13. package/runtime/a11y/announcements.js +213 -0
  14. package/runtime/a11y/contrast.js +125 -0
  15. package/runtime/a11y/focus.js +412 -0
  16. package/runtime/a11y/index.js +35 -0
  17. package/runtime/a11y/preferences.js +121 -0
  18. package/runtime/a11y/utils.js +164 -0
  19. package/runtime/a11y/validation.js +258 -0
  20. package/runtime/a11y/widgets.js +545 -0
  21. package/runtime/a11y.js +15 -1840
  22. package/runtime/a11y.js.original +1844 -0
  23. package/runtime/graphql/cache.js +69 -0
  24. package/runtime/graphql/client.js +563 -0
  25. package/runtime/graphql/hooks.js +492 -0
  26. package/runtime/graphql/index.js +62 -0
  27. package/runtime/graphql/subscriptions.js +241 -0
  28. package/runtime/graphql.js +12 -1322
  29. package/runtime/graphql.js.original +1326 -0
  30. package/runtime/router/core.js +956 -0
  31. package/runtime/router/guards.js +90 -0
  32. package/runtime/router/history.js +204 -0
  33. package/runtime/router/index.js +36 -0
  34. package/runtime/router/lazy.js +180 -0
  35. package/runtime/router/utils.js +226 -0
  36. package/runtime/router.js +12 -1600
  37. package/runtime/router.js.original +1605 -0
@@ -0,0 +1,492 @@
1
+ /**
2
+ * Pulse GraphQL - React-style Hooks
3
+ *
4
+ * Reactive hooks for GraphQL queries, mutations, and subscriptions
5
+ *
6
+ * @module pulse-js-framework/runtime/graphql/hooks
7
+ */
8
+
9
+ import { pulse, computed, batch, effect, onCleanup } from '../pulse.js';
10
+ import { createVersionedAsync } from '../async.js';
11
+ import { onWindowFocus, onWindowOnline } from '../utils.js';
12
+ import { getDefaultClient, GraphQLError } from './client.js';
13
+ import { generateCacheKey } from './cache.js';
14
+
15
+ // ============================================================================
16
+ // Helper Functions
17
+ // ============================================================================
18
+
19
+ /**
20
+ * Get client from options or use default
21
+ * @param {Object} options - Hook options
22
+ * @returns {GraphQLClient} GraphQL client instance
23
+ */
24
+ function getClient(options) {
25
+ const client = options.client || getDefaultClient();
26
+ if (!client) {
27
+ throw new GraphQLError(
28
+ 'No GraphQL client provided. Either pass a client option or set a default client with setDefaultClient().',
29
+ { code: 'GRAPHQL_ERROR' }
30
+ );
31
+ }
32
+ return client;
33
+ }
34
+
35
+ // ============================================================================
36
+ // useQuery Hook
37
+ // ============================================================
38
+
39
+ /**
40
+ * Execute a GraphQL query with caching and reactivity
41
+ * @param {string} query - GraphQL query string
42
+ * @param {Object|Function} [variables] - Variables or function returning variables
43
+ * @param {Object} [options={}] - Query options
44
+ * @param {GraphQLClient} [options.client] - GraphQL client instance
45
+ * @param {boolean|Pulse<boolean>} [options.enabled=true] - Enable/disable query
46
+ * @param {boolean} [options.immediate=true] - Execute immediately
47
+ * @param {string|Function} [options.cacheKey] - Custom cache key
48
+ * @param {number} [options.cacheTime] - Cache TTL override
49
+ * @param {number} [options.staleTime] - Stale threshold override
50
+ * @param {boolean} [options.refetchOnFocus=false] - Refetch when window gains focus
51
+ * @param {boolean} [options.refetchOnReconnect=false] - Refetch when network reconnects
52
+ * @param {number} [options.refetchInterval] - Polling interval in ms
53
+ * @param {number} [options.retry] - Retry attempts
54
+ * @param {number} [options.retryDelay] - Delay between retries
55
+ * @param {Function} [options.onSuccess] - Success callback
56
+ * @param {Function} [options.onError] - Error callback
57
+ * @param {Function} [options.select] - Transform/select data
58
+ * @param {*} [options.placeholderData] - Placeholder while loading
59
+ * @param {boolean} [options.keepPreviousData=false] - Keep previous data during refetch
60
+ * @returns {Object} Query result with reactive state
61
+ */
62
+ export function useQuery(query, variables, options = {}) {
63
+ const client = getClient(options);
64
+
65
+ // Resolve variables (can be function for reactive variables)
66
+ const resolveVariables = () => {
67
+ if (typeof variables === 'function') {
68
+ return variables();
69
+ }
70
+ return variables;
71
+ };
72
+
73
+ // Generate cache key
74
+ const getCacheKey = () => {
75
+ if (typeof options.cacheKey === 'function') {
76
+ return options.cacheKey();
77
+ }
78
+ if (options.cacheKey) {
79
+ return options.cacheKey;
80
+ }
81
+ return generateCacheKey(query, resolveVariables());
82
+ };
83
+
84
+ // Check if enabled
85
+ const isEnabled = () => {
86
+ if (typeof options.enabled === 'object' && options.enabled?.get) {
87
+ return options.enabled.get();
88
+ }
89
+ return options.enabled !== false;
90
+ };
91
+
92
+ // Check if should execute immediately
93
+ const shouldExecuteImmediately = options.immediate !== false && isEnabled();
94
+
95
+ // State
96
+ const data = pulse(options.placeholderData ?? null);
97
+ const error = pulse(null);
98
+ const loading = pulse(shouldExecuteImmediately);
99
+ const fetching = pulse(false);
100
+ const isStale = pulse(false);
101
+
102
+ const versionController = createVersionedAsync();
103
+
104
+ // Execute query
105
+ async function executeQuery() {
106
+ if (!isEnabled()) return null;
107
+
108
+ const ctx = versionController.begin();
109
+
110
+ batch(() => {
111
+ fetching.set(true);
112
+ if (data.get() === null) {
113
+ loading.set(true);
114
+ }
115
+ error.set(null);
116
+ });
117
+
118
+ try {
119
+ const result = await client.query(query, resolveVariables(), {
120
+ cacheKey: getCacheKey()
121
+ });
122
+
123
+ const selectedData = options.select ? options.select(result) : result;
124
+
125
+ ctx.ifCurrent(() => {
126
+ batch(() => {
127
+ data.set(selectedData);
128
+ loading.set(false);
129
+ fetching.set(false);
130
+ isStale.set(false);
131
+ });
132
+ options.onSuccess?.(selectedData);
133
+ });
134
+
135
+ return selectedData;
136
+ } catch (err) {
137
+ const graphqlError = GraphQLError.isGraphQLError(err) ? err : new GraphQLError(err.message, {
138
+ code: 'GRAPHQL_ERROR'
139
+ });
140
+
141
+ ctx.ifCurrent(() => {
142
+ batch(() => {
143
+ error.set(graphqlError);
144
+ loading.set(false);
145
+ fetching.set(false);
146
+ });
147
+ options.onError?.(graphqlError);
148
+ });
149
+
150
+ return null;
151
+ }
152
+ }
153
+
154
+ // Initial fetch if immediate
155
+ if (shouldExecuteImmediately) {
156
+ executeQuery();
157
+ }
158
+
159
+ // Setup auto-refresh interval
160
+ if (options.refetchInterval && options.refetchInterval > 0) {
161
+ const intervalId = setInterval(() => {
162
+ if (!loading.get() && !fetching.get() && isEnabled()) {
163
+ executeQuery();
164
+ }
165
+ }, options.refetchInterval);
166
+
167
+ onCleanup(() => clearInterval(intervalId));
168
+ }
169
+
170
+ // Setup window focus listener
171
+ if (options.refetchOnFocus) {
172
+ onWindowFocus(() => { if (isEnabled()) executeQuery(); }, onCleanup);
173
+ }
174
+
175
+ // Setup online listener
176
+ if (options.refetchOnReconnect) {
177
+ onWindowOnline(() => { if (isEnabled()) executeQuery(); }, onCleanup);
178
+ }
179
+
180
+ return {
181
+ data,
182
+ error,
183
+ loading,
184
+ fetching,
185
+ status: computed(() => {
186
+ if (loading.get()) return 'loading';
187
+ if (error.get()) return 'error';
188
+ if (data.get() !== null) return 'success';
189
+ return 'idle';
190
+ }),
191
+ isStale,
192
+ refetch: executeQuery,
193
+ invalidate: () => {
194
+ isStale.set(true);
195
+ client.invalidate(getCacheKey());
196
+ },
197
+ reset: () => {
198
+ batch(() => {
199
+ data.set(null);
200
+ error.set(null);
201
+ loading.set(false);
202
+ fetching.set(false);
203
+ isStale.set(false);
204
+ });
205
+ }
206
+ };
207
+ }
208
+
209
+ // ============================================================================
210
+ // useMutation Hook
211
+ // ============================================================================
212
+
213
+ /**
214
+ * Execute GraphQL mutations
215
+ * @param {string} mutation - GraphQL mutation string
216
+ * @param {Object} [options={}] - Mutation options
217
+ * @param {GraphQLClient} [options.client] - GraphQL client instance
218
+ * @param {Function} [options.onSuccess] - Success callback
219
+ * @param {Function} [options.onError] - Error callback
220
+ * @param {Function} [options.onSettled] - Called after success or error
221
+ * @param {number} [options.retry] - Retry attempts
222
+ * @param {number} [options.retryDelay] - Delay between retries
223
+ * @param {Function} [options.onMutate] - Called before mutation (for optimistic updates)
224
+ * @param {string[]} [options.invalidateQueries] - Cache keys to invalidate on success
225
+ * @returns {Object} Mutation result with execute function
226
+ */
227
+ export function useMutation(mutation, options = {}) {
228
+ const client = getClient(options);
229
+
230
+ const data = pulse(null);
231
+ const error = pulse(null);
232
+ const loading = pulse(false);
233
+ const status = pulse('idle');
234
+
235
+ const versionController = createVersionedAsync();
236
+
237
+ /**
238
+ * Execute the mutation
239
+ * @param {Object} [variables] - Mutation variables
240
+ * @returns {Promise<*>} Mutation result
241
+ */
242
+ async function mutate(variables) {
243
+ const ctx = versionController.begin();
244
+ let rollbackContext;
245
+
246
+ batch(() => {
247
+ loading.set(true);
248
+ error.set(null);
249
+ status.set('loading');
250
+ });
251
+
252
+ try {
253
+ // Call onMutate for optimistic updates
254
+ if (options.onMutate) {
255
+ rollbackContext = await options.onMutate(variables);
256
+ }
257
+
258
+ const result = await client.mutate(mutation, variables);
259
+
260
+ ctx.ifCurrent(() => {
261
+ batch(() => {
262
+ data.set(result);
263
+ loading.set(false);
264
+ status.set('success');
265
+ });
266
+
267
+ options.onSuccess?.(result, variables);
268
+ options.onSettled?.(result, null, variables);
269
+
270
+ // Invalidate queries
271
+ if (options.invalidateQueries) {
272
+ for (const key of options.invalidateQueries) {
273
+ client.invalidate(key);
274
+ }
275
+ }
276
+ });
277
+
278
+ return result;
279
+ } catch (err) {
280
+ const graphqlError = GraphQLError.isGraphQLError(err) ? err : new GraphQLError(err.message, {
281
+ code: 'GRAPHQL_ERROR'
282
+ });
283
+
284
+ ctx.ifCurrent(() => {
285
+ batch(() => {
286
+ error.set(graphqlError);
287
+ loading.set(false);
288
+ status.set('error');
289
+ });
290
+
291
+ options.onError?.(graphqlError, variables, rollbackContext);
292
+ options.onSettled?.(null, graphqlError, variables);
293
+ });
294
+
295
+ throw graphqlError;
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Reset mutation state
301
+ */
302
+ function reset() {
303
+ batch(() => {
304
+ data.set(null);
305
+ error.set(null);
306
+ loading.set(false);
307
+ status.set('idle');
308
+ });
309
+ }
310
+
311
+ return {
312
+ data,
313
+ error,
314
+ loading,
315
+ status,
316
+ mutate,
317
+ mutateAsync: mutate,
318
+ reset
319
+ };
320
+ }
321
+
322
+ // ============================================================================
323
+ // useSubscription Hook
324
+ // ============================================================================
325
+
326
+ /**
327
+ * Subscribe to GraphQL subscriptions over WebSocket
328
+ * @param {string} subscription - GraphQL subscription string
329
+ * @param {Object|Function} [variables] - Variables or function returning variables
330
+ * @param {Object} [options={}] - Subscription options
331
+ * @param {GraphQLClient} [options.client] - GraphQL client instance
332
+ * @param {boolean|Pulse<boolean>} [options.enabled=true] - Enable/disable subscription
333
+ * @param {Function} [options.onData] - Called on each message
334
+ * @param {Function} [options.onError] - Error callback
335
+ * @param {Function} [options.onComplete] - Called when subscription ends
336
+ * @param {boolean} [options.shouldResubscribe=true] - Resubscribe on error
337
+ * @param {number} [options.retryBaseDelay=1000] - Base delay for exponential backoff (ms)
338
+ * @param {number} [options.retryMaxDelay=30000] - Maximum delay between retries (ms)
339
+ * @param {number} [options.maxRetries=Infinity] - Maximum number of retry attempts
340
+ * @returns {Object} Subscription result with reactive state
341
+ */
342
+ export function useSubscription(subscription, variables, options = {}) {
343
+ const client = getClient(options);
344
+
345
+ const data = pulse(null);
346
+ const error = pulse(null);
347
+ const status = pulse('connecting');
348
+ const retryCount = pulse(0);
349
+
350
+ let unsubscribeFn = null;
351
+ let retryTimeoutId = null;
352
+
353
+ // Backoff configuration
354
+ const retryBaseDelay = options.retryBaseDelay ?? 1000;
355
+ const retryMaxDelay = options.retryMaxDelay ?? 30000;
356
+ const maxRetries = options.maxRetries ?? Infinity;
357
+
358
+ /**
359
+ * Calculate delay with exponential backoff and jitter
360
+ * @param {number} attempt - Current retry attempt (0-indexed)
361
+ * @returns {number} Delay in milliseconds
362
+ */
363
+ function calculateBackoffDelay(attempt) {
364
+ // Exponential backoff: baseDelay * 2^attempt
365
+ const exponentialDelay = retryBaseDelay * Math.pow(2, attempt);
366
+ // Cap at max delay
367
+ const cappedDelay = Math.min(exponentialDelay, retryMaxDelay);
368
+ // Add jitter (±25%) to prevent thundering herd
369
+ const jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);
370
+ return Math.max(0, cappedDelay + jitter);
371
+ }
372
+
373
+ // Resolve variables
374
+ const resolveVariables = () => {
375
+ if (typeof variables === 'function') {
376
+ return variables();
377
+ }
378
+ return variables;
379
+ };
380
+
381
+ // Check if enabled
382
+ const isEnabled = () => {
383
+ if (typeof options.enabled === 'object' && options.enabled?.get) {
384
+ return options.enabled.get();
385
+ }
386
+ return options.enabled !== false;
387
+ };
388
+
389
+ /**
390
+ * Start subscription
391
+ */
392
+ function subscribe() {
393
+ if (!isEnabled() || unsubscribeFn) return;
394
+
395
+ status.set('connecting');
396
+
397
+ unsubscribeFn = client.subscribe(subscription, resolveVariables(), {
398
+ onData: (payload) => {
399
+ status.set('connected');
400
+ data.set(payload);
401
+ // Reset retry count on successful data
402
+ retryCount.set(0);
403
+ options.onData?.(payload);
404
+ },
405
+ onError: (err) => {
406
+ error.set(err);
407
+ status.set('error');
408
+ options.onError?.(err);
409
+
410
+ // Resubscribe on error if enabled and under max retries
411
+ if (options.shouldResubscribe !== false) {
412
+ const currentRetry = retryCount.peek();
413
+ if (currentRetry < maxRetries) {
414
+ unsubscribeFn = null;
415
+ const delay = calculateBackoffDelay(currentRetry);
416
+ retryCount.set(currentRetry + 1);
417
+ status.set('reconnecting');
418
+ retryTimeoutId = setTimeout(() => {
419
+ retryTimeoutId = null;
420
+ subscribe();
421
+ }, delay);
422
+ } else {
423
+ status.set('failed');
424
+ }
425
+ }
426
+ },
427
+ onComplete: () => {
428
+ status.set('closed');
429
+ options.onComplete?.();
430
+ unsubscribeFn = null;
431
+ }
432
+ });
433
+ }
434
+
435
+ /**
436
+ * Unsubscribe
437
+ */
438
+ function unsubscribe() {
439
+ // Cancel pending retry
440
+ if (retryTimeoutId) {
441
+ clearTimeout(retryTimeoutId);
442
+ retryTimeoutId = null;
443
+ }
444
+ if (unsubscribeFn) {
445
+ unsubscribeFn();
446
+ unsubscribeFn = null;
447
+ status.set('closed');
448
+ }
449
+ retryCount.set(0);
450
+ }
451
+
452
+ /**
453
+ * Resubscribe (unsubscribe then subscribe)
454
+ */
455
+ function resubscribe() {
456
+ unsubscribe();
457
+ subscribe();
458
+ }
459
+
460
+ // Start subscription if enabled
461
+ if (isEnabled()) {
462
+ subscribe();
463
+ }
464
+
465
+ // Watch enabled state for reactive enabling/disabling
466
+ if (typeof options.enabled === 'object' && options.enabled?.get) {
467
+ effect(() => {
468
+ if (options.enabled.get()) {
469
+ subscribe();
470
+ } else {
471
+ unsubscribe();
472
+ }
473
+ });
474
+ }
475
+
476
+ // Cleanup on dispose
477
+ onCleanup(() => {
478
+ unsubscribe();
479
+ });
480
+
481
+ return {
482
+ data,
483
+ error,
484
+ status,
485
+ retryCount,
486
+ unsubscribe,
487
+ resubscribe
488
+ };
489
+ }
490
+
491
+ // ============================================================================
492
+
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Pulse GraphQL - Main Entry Point
3
+ *
4
+ * Barrel export for all GraphQL modules
5
+ *
6
+ * @module pulse-js-framework/runtime/graphql
7
+ */
8
+
9
+ // Re-export InterceptorManager for backward compatibility
10
+ export { InterceptorManager } from '../interceptor-manager.js';
11
+
12
+ // Export all from sub-modules
13
+ export * from './client.js';
14
+ export * from './cache.js';
15
+ export * from './subscriptions.js';
16
+ export * from './hooks.js';
17
+
18
+ // Re-export main classes and functions for convenience
19
+ export {
20
+ GraphQLError,
21
+ GraphQLClient,
22
+ createGraphQLClient,
23
+ setDefaultClient,
24
+ getDefaultClient
25
+ } from './client.js';
26
+
27
+ export {
28
+ generateCacheKey,
29
+ extractOperationName
30
+ } from './cache.js';
31
+
32
+ export {
33
+ MessageType,
34
+ SubscriptionManager
35
+ } from './subscriptions.js';
36
+
37
+ export {
38
+ useQuery,
39
+ useMutation,
40
+ useSubscription
41
+ } from './hooks.js';
42
+
43
+ // Default export for backward compatibility
44
+ import { GraphQLError as _GQLError, GraphQLClient as _GQLClient, createGraphQLClient as _createGQL, setDefaultClient as _setDefault, getDefaultClient as _getDefault } from './client.js';
45
+ import { generateCacheKey as _genKey, extractOperationName as _extractOp } from './cache.js';
46
+ import { MessageType as _MsgType, SubscriptionManager as _SubMgr } from './subscriptions.js';
47
+ import { useQuery as _useQ, useMutation as _useM, useSubscription as _useS } from './hooks.js';
48
+
49
+ export default {
50
+ GraphQLError: _GQLError,
51
+ GraphQLClient: _GQLClient,
52
+ createGraphQLClient: _createGQL,
53
+ setDefaultClient: _setDefault,
54
+ getDefaultClient: _getDefault,
55
+ generateCacheKey: _genKey,
56
+ extractOperationName: _extractOp,
57
+ MessageType: _MsgType,
58
+ SubscriptionManager: _SubMgr,
59
+ useQuery: _useQ,
60
+ useMutation: _useM,
61
+ useSubscription: _useS
62
+ };