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,226 @@
1
+ /**
2
+ * Pulse Router - Utilities
3
+ *
4
+ * Helper functions for route parsing, matching, and query string handling
5
+ *
6
+ * @module pulse-js-framework/runtime/router/utils
7
+ */
8
+
9
+ import { loggers } from '../logger.js';
10
+
11
+ const log = loggers.router;
12
+
13
+ /**
14
+ * Parse a route pattern into a regex and extract param names
15
+ * Supports: /users/:id, /posts/:id/comments, /files/*path, * (catch-all)
16
+ */
17
+ export function parsePattern(pattern) {
18
+ const paramNames = [];
19
+
20
+ // Handle standalone * as catch-all
21
+ if (pattern === '*') {
22
+ return {
23
+ regex: /^.*$/,
24
+ paramNames: []
25
+ };
26
+ }
27
+
28
+ let regexStr = pattern
29
+ // Escape special regex chars except : and *
30
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
31
+ // Handle wildcard params (*name)
32
+ .replace(/\*([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {
33
+ paramNames.push(name);
34
+ return '(.*)';
35
+ })
36
+ // Handle named params (:name)
37
+ .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {
38
+ paramNames.push(name);
39
+ return '([^/]+)';
40
+ });
41
+
42
+ // Ensure exact match
43
+ regexStr = `^${regexStr}$`;
44
+
45
+ return {
46
+ regex: new RegExp(regexStr),
47
+ paramNames
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Normalize route configuration
53
+ * Supports both simple (handler function) and full (object with meta) definitions
54
+ */
55
+ export function normalizeRoute(pattern, config) {
56
+ // Simple format: pattern -> handler
57
+ if (typeof config === 'function') {
58
+ return {
59
+ pattern,
60
+ handler: config,
61
+ meta: {},
62
+ beforeEnter: null,
63
+ children: null
64
+ };
65
+ }
66
+
67
+ // Full format: pattern -> { handler, meta, beforeEnter, children, alias, layout, group }
68
+ return {
69
+ pattern,
70
+ handler: config.handler || config.component,
71
+ meta: config.meta || {},
72
+ beforeEnter: config.beforeEnter || null,
73
+ children: config.children || null,
74
+ redirect: config.redirect || null,
75
+ alias: config.alias || null,
76
+ layout: config.layout || null,
77
+ group: config.group || false
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Build a query string from an object, supporting arrays and skipping null/undefined
83
+ *
84
+ * @param {Object} query - Query parameters object
85
+ * @returns {string} Encoded query string (without leading ?)
86
+ *
87
+ * @example
88
+ * buildQueryString({ q: 'hello world', tags: ['a', 'b'] })
89
+ * // 'q=hello+world&tags=a&tags=b'
90
+ *
91
+ * buildQueryString({ a: 'x', b: null, c: undefined })
92
+ * // 'a=x'
93
+ */
94
+ export function buildQueryString(query) {
95
+ if (!query || typeof query !== 'object') return '';
96
+
97
+ const params = new URLSearchParams();
98
+
99
+ for (const [key, value] of Object.entries(query)) {
100
+ // Skip null and undefined values
101
+ if (value === null || value === undefined) continue;
102
+
103
+ if (Array.isArray(value)) {
104
+ // Array values: ?tags=a&tags=b
105
+ for (const item of value) {
106
+ if (item !== null && item !== undefined) {
107
+ params.append(key, String(item));
108
+ }
109
+ }
110
+ } else {
111
+ params.append(key, String(value));
112
+ }
113
+ }
114
+
115
+ return params.toString();
116
+ }
117
+
118
+ /**
119
+ * Match a path against a route pattern
120
+ */
121
+ export function matchRoute(pattern, path) {
122
+ const { regex, paramNames } = parsePattern(pattern);
123
+ const match = path.match(regex);
124
+
125
+ if (!match) return null;
126
+
127
+ const params = {};
128
+ paramNames.forEach((name, i) => {
129
+ params[name] = decodeURIComponent(match[i + 1]);
130
+ });
131
+
132
+ return params;
133
+ }
134
+
135
+ // Query string validation limits
136
+ const QUERY_LIMITS = {
137
+ maxTotalLength: 2048, // 2KB max for entire query string
138
+ maxValueLength: 1024, // 1KB max per individual value
139
+ maxParams: 50 // Maximum number of query parameters
140
+ };
141
+
142
+ /**
143
+ * Parse a single query value into its typed representation
144
+ * Only converts when parseQueryTypes is enabled
145
+ *
146
+ * @param {string} value - Raw string value
147
+ * @returns {string|number|boolean} Typed value
148
+ */
149
+ function parseTypedValue(value) {
150
+ // Boolean detection
151
+ if (value === 'true') return true;
152
+ if (value === 'false') return false;
153
+
154
+ // Number detection (strict: only numeric strings, not hex/octal/empty)
155
+ if (value !== '' && !isNaN(value) && !isNaN(parseFloat(value))) {
156
+ const num = Number(value);
157
+ if (isFinite(num)) return num;
158
+ }
159
+
160
+ return value;
161
+ }
162
+
163
+ /**
164
+ * Parse query string into object with validation
165
+ *
166
+ * SECURITY: Enforces hard limits BEFORE parsing to prevent DoS attacks.
167
+ * - Max total length: 2KB
168
+ * - Max value length: 1KB
169
+ * - Max parameters: 50
170
+ *
171
+ * @param {string} search - Query string (with or without leading ?)
172
+ * @param {Object} [options] - Parsing options
173
+ * @param {boolean} [options.typed=false] - Parse numbers and booleans from string values
174
+ * @returns {Object} Parsed query parameters
175
+ */
176
+ export function parseQuery(search, options = {}) {
177
+ if (!search) return {};
178
+
179
+ const { typed = false } = options;
180
+
181
+ // Remove leading ? if present
182
+ let queryStr = search.startsWith('?') ? search.slice(1) : search;
183
+
184
+ // SECURITY: Enforce hard limit BEFORE parsing to prevent DoS
185
+ if (queryStr.length > QUERY_LIMITS.maxTotalLength) {
186
+ log.warn(`Query string exceeds maximum length (${QUERY_LIMITS.maxTotalLength} chars). Truncating.`);
187
+ queryStr = queryStr.slice(0, QUERY_LIMITS.maxTotalLength);
188
+ }
189
+
190
+ const params = new URLSearchParams(queryStr);
191
+ const query = {};
192
+ let paramCount = 0;
193
+
194
+ for (const [key, value] of params) {
195
+ // Check parameter count limit
196
+ if (paramCount >= QUERY_LIMITS.maxParams) {
197
+ log.warn(`Query string exceeds maximum parameters (${QUERY_LIMITS.maxParams}). Ignoring excess.`);
198
+ break;
199
+ }
200
+
201
+ // Validate and potentially truncate value length
202
+ let safeValue = value;
203
+ if (value.length > QUERY_LIMITS.maxValueLength) {
204
+ log.warn(`Query parameter "${key}" exceeds maximum length. Truncating.`);
205
+ safeValue = value.slice(0, QUERY_LIMITS.maxValueLength);
206
+ }
207
+
208
+ // Apply typed parsing if enabled
209
+ if (typed) {
210
+ safeValue = parseTypedValue(safeValue);
211
+ }
212
+
213
+ if (key in query) {
214
+ // Multiple values for same key
215
+ if (Array.isArray(query[key])) {
216
+ query[key].push(safeValue);
217
+ } else {
218
+ query[key] = [query[key], safeValue];
219
+ }
220
+ } else {
221
+ query[key] = safeValue;
222
+ }
223
+ paramCount++;
224
+ }
225
+ return query;
226
+ }