pulse-js-framework 1.7.4 → 1.7.6
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/README.md +78 -392
- package/cli/analyze.js +127 -46
- package/cli/build.js +51 -13
- package/cli/dev.js +14 -0
- package/cli/docs-test.js +633 -0
- package/cli/format.js +64 -8
- package/cli/index.js +313 -31
- package/cli/lint.js +121 -27
- package/cli/logger.js +32 -4
- package/cli/release.js +50 -20
- package/cli/utils/cli-ui.js +452 -0
- package/compiler/parser.js +19 -2
- package/core/errors.js +2 -297
- package/package.json +16 -4
- package/runtime/async.js +282 -14
- package/runtime/dom-adapter.js +920 -0
- package/runtime/dom-advanced.js +357 -0
- package/runtime/dom-binding.js +230 -0
- package/runtime/dom-conditional.js +133 -0
- package/runtime/dom-element.js +142 -0
- package/runtime/dom-lifecycle.js +178 -0
- package/runtime/dom-list.js +267 -0
- package/runtime/dom-selector.js +267 -0
- package/runtime/dom.js +131 -1122
- package/runtime/errors.js +575 -0
- package/runtime/form.js +417 -22
- package/runtime/logger.js +144 -69
- package/runtime/logger.prod.js +43 -18
- package/runtime/native.js +398 -52
- package/runtime/pulse.js +202 -80
- package/runtime/router.js +31 -42
- package/runtime/store.js +90 -12
- package/runtime/utils.js +279 -18
- package/types/async.d.ts +310 -0
- package/types/form.d.ts +378 -0
- package/types/index.d.ts +44 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Error Classes - Structured error handling for the framework
|
|
3
|
+
* @module pulse-js-framework/core/errors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base error class for all Pulse framework errors.
|
|
8
|
+
* Provides source location tracking and code snippet formatting.
|
|
9
|
+
*/
|
|
10
|
+
export class PulseError extends Error {
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} message - Error message
|
|
13
|
+
* @param {Object} [options={}] - Error options
|
|
14
|
+
* @param {number} [options.line] - Line number where error occurred
|
|
15
|
+
* @param {number} [options.column] - Column number where error occurred
|
|
16
|
+
* @param {string} [options.file] - Source file path
|
|
17
|
+
* @param {string} [options.code] - Error code for documentation lookup
|
|
18
|
+
* @param {string} [options.source] - Original source code
|
|
19
|
+
* @param {string} [options.suggestion] - Helpful suggestion to fix the error
|
|
20
|
+
*/
|
|
21
|
+
constructor(message, options = {}) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'PulseError';
|
|
24
|
+
this.line = options.line ?? null;
|
|
25
|
+
this.column = options.column ?? null;
|
|
26
|
+
this.file = options.file ?? null;
|
|
27
|
+
this.code = options.code ?? 'PULSE_ERROR';
|
|
28
|
+
this.source = options.source ?? null;
|
|
29
|
+
this.suggestion = options.suggestion ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Format the error with a code snippet showing context
|
|
34
|
+
* @param {string} [source] - Source code to use (overrides this.source)
|
|
35
|
+
* @param {number} [contextLines=2] - Number of lines to show before/after error
|
|
36
|
+
* @returns {string} Formatted error with code snippet
|
|
37
|
+
*/
|
|
38
|
+
formatWithSnippet(source = this.source, contextLines = 2) {
|
|
39
|
+
if (!this.line || !source) {
|
|
40
|
+
return this.format();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const lines = source.split('\n');
|
|
44
|
+
const start = Math.max(0, this.line - 1 - contextLines);
|
|
45
|
+
const end = Math.min(lines.length, this.line + contextLines);
|
|
46
|
+
const gutterWidth = String(end).length;
|
|
47
|
+
|
|
48
|
+
let output = `\n${this.name}: ${this.message}\n`;
|
|
49
|
+
output += ` at ${this.file || '<anonymous>'}:${this.line}:${this.column || 1}\n\n`;
|
|
50
|
+
|
|
51
|
+
for (let i = start; i < end; i++) {
|
|
52
|
+
const lineNum = i + 1;
|
|
53
|
+
const isErrorLine = lineNum === this.line;
|
|
54
|
+
const prefix = isErrorLine ? '>' : ' ';
|
|
55
|
+
const gutter = String(lineNum).padStart(gutterWidth);
|
|
56
|
+
output += `${prefix} ${gutter} | ${lines[i]}\n`;
|
|
57
|
+
|
|
58
|
+
if (isErrorLine && this.column) {
|
|
59
|
+
const padding = ' '.repeat(gutterWidth + 3 + Math.max(0, this.column - 1));
|
|
60
|
+
output += ` ${padding}^\n`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.suggestion) {
|
|
65
|
+
output += `\n → ${this.suggestion}\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this.code && this.code !== 'PULSE_ERROR') {
|
|
69
|
+
output += `\n See: ${getDocsUrl(this.code)}\n`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return output;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Format error without source code context
|
|
77
|
+
* @returns {string} Formatted error message
|
|
78
|
+
*/
|
|
79
|
+
format() {
|
|
80
|
+
let output = `${this.name}: ${this.message}`;
|
|
81
|
+
|
|
82
|
+
if (this.file || this.line) {
|
|
83
|
+
output += `\n at ${this.file || '<anonymous>'}`;
|
|
84
|
+
if (this.line) output += `:${this.line}`;
|
|
85
|
+
if (this.column) output += `:${this.column}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.suggestion) {
|
|
89
|
+
output += `\n\n → ${this.suggestion}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (this.code && this.code !== 'PULSE_ERROR') {
|
|
93
|
+
output += `\n See: ${getDocsUrl(this.code)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return output;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Convert to plain object for JSON serialization
|
|
101
|
+
* @returns {Object} Plain object representation
|
|
102
|
+
*/
|
|
103
|
+
toJSON() {
|
|
104
|
+
return {
|
|
105
|
+
name: this.name,
|
|
106
|
+
message: this.message,
|
|
107
|
+
line: this.line,
|
|
108
|
+
column: this.column,
|
|
109
|
+
file: this.file,
|
|
110
|
+
code: this.code,
|
|
111
|
+
suggestion: this.suggestion
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Compiler Errors
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Base class for all compilation errors
|
|
122
|
+
*/
|
|
123
|
+
export class CompileError extends PulseError {
|
|
124
|
+
constructor(message, options = {}) {
|
|
125
|
+
super(message, { code: 'COMPILE_ERROR', ...options });
|
|
126
|
+
this.name = 'CompileError';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Error during lexical analysis (tokenization)
|
|
132
|
+
*/
|
|
133
|
+
export class LexerError extends CompileError {
|
|
134
|
+
constructor(message, options = {}) {
|
|
135
|
+
super(message, { code: 'LEXER_ERROR', ...options });
|
|
136
|
+
this.name = 'LexerError';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Error during parsing (AST construction)
|
|
142
|
+
*/
|
|
143
|
+
export class ParserError extends CompileError {
|
|
144
|
+
/**
|
|
145
|
+
* @param {string} message - Error message
|
|
146
|
+
* @param {Object} [options={}] - Error options
|
|
147
|
+
* @param {Object} [options.token] - The problematic token
|
|
148
|
+
*/
|
|
149
|
+
constructor(message, options = {}) {
|
|
150
|
+
super(message, { code: 'PARSER_ERROR', ...options });
|
|
151
|
+
this.name = 'ParserError';
|
|
152
|
+
this.token = options.token ?? null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Error during code transformation/generation
|
|
158
|
+
*/
|
|
159
|
+
export class TransformError extends CompileError {
|
|
160
|
+
constructor(message, options = {}) {
|
|
161
|
+
super(message, { code: 'TRANSFORM_ERROR', ...options });
|
|
162
|
+
this.name = 'TransformError';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// Runtime Errors
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Base class for all runtime errors
|
|
172
|
+
*/
|
|
173
|
+
export class RuntimeError extends PulseError {
|
|
174
|
+
constructor(message, options = {}) {
|
|
175
|
+
super(message, { code: 'RUNTIME_ERROR', ...options });
|
|
176
|
+
this.name = 'RuntimeError';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Error in the reactivity system (effects, computed values)
|
|
182
|
+
*/
|
|
183
|
+
export class ReactivityError extends RuntimeError {
|
|
184
|
+
constructor(message, options = {}) {
|
|
185
|
+
super(message, { code: 'REACTIVITY_ERROR', ...options });
|
|
186
|
+
this.name = 'ReactivityError';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Error in DOM operations
|
|
192
|
+
*/
|
|
193
|
+
export class DOMError extends RuntimeError {
|
|
194
|
+
constructor(message, options = {}) {
|
|
195
|
+
super(message, { code: 'DOM_ERROR', ...options });
|
|
196
|
+
this.name = 'DOMError';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Error in store operations
|
|
202
|
+
*/
|
|
203
|
+
export class StoreError extends RuntimeError {
|
|
204
|
+
constructor(message, options = {}) {
|
|
205
|
+
super(message, { code: 'STORE_ERROR', ...options });
|
|
206
|
+
this.name = 'StoreError';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Error in router operations
|
|
212
|
+
*/
|
|
213
|
+
export class RouterError extends RuntimeError {
|
|
214
|
+
constructor(message, options = {}) {
|
|
215
|
+
super(message, { code: 'ROUTER_ERROR', ...options });
|
|
216
|
+
this.name = 'RouterError';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// CLI Errors
|
|
222
|
+
// ============================================================================
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Base class for CLI errors
|
|
226
|
+
*/
|
|
227
|
+
export class CLIError extends PulseError {
|
|
228
|
+
constructor(message, options = {}) {
|
|
229
|
+
super(message, { code: 'CLI_ERROR', ...options });
|
|
230
|
+
this.name = 'CLIError';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Configuration file error
|
|
236
|
+
*/
|
|
237
|
+
export class ConfigError extends CLIError {
|
|
238
|
+
constructor(message, options = {}) {
|
|
239
|
+
super(message, { code: 'CONFIG_ERROR', ...options });
|
|
240
|
+
this.name = 'ConfigError';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ============================================================================
|
|
245
|
+
// Documentation & URLs
|
|
246
|
+
// ============================================================================
|
|
247
|
+
|
|
248
|
+
const DOCS_BASE_URL = 'https://pulse-js.fr';
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Generate documentation URL for an error code
|
|
252
|
+
* @param {string} code - Error code
|
|
253
|
+
* @returns {string} Documentation URL
|
|
254
|
+
*/
|
|
255
|
+
export function getDocsUrl(code) {
|
|
256
|
+
const paths = {
|
|
257
|
+
// Reactivity
|
|
258
|
+
'COMPUTED_SET': 'reactivity/computed#read-only',
|
|
259
|
+
'CIRCULAR_DEPENDENCY': 'reactivity/effects#circular-dependencies',
|
|
260
|
+
'EFFECT_ERROR': 'reactivity/effects#error-handling',
|
|
261
|
+
// DOM
|
|
262
|
+
'MOUNT_ERROR': 'dom/mounting#troubleshooting',
|
|
263
|
+
'SELECTOR_INVALID': 'dom/elements#selectors',
|
|
264
|
+
'LIST_NO_KEY': 'dom/lists#key-function',
|
|
265
|
+
// Router
|
|
266
|
+
'ROUTE_NOT_FOUND': 'router/routes#catch-all',
|
|
267
|
+
'LAZY_TIMEOUT': 'router/lazy-loading#timeout',
|
|
268
|
+
'GUARD_ERROR': 'router/guards#error-handling',
|
|
269
|
+
// Store
|
|
270
|
+
'PERSIST_ERROR': 'store/persistence#troubleshooting',
|
|
271
|
+
'STORE_TYPE_ERROR': 'store/state#valid-types',
|
|
272
|
+
// Compiler
|
|
273
|
+
'PARSER_ERROR': 'compiler/syntax',
|
|
274
|
+
'DUPLICATE_BLOCK': 'compiler/structure#blocks',
|
|
275
|
+
'INVALID_DIRECTIVE': 'compiler/directives',
|
|
276
|
+
'LEXER_ERROR': 'compiler/syntax#tokens'
|
|
277
|
+
};
|
|
278
|
+
return `${DOCS_BASE_URL}/${paths[code] || 'errors#' + code.toLowerCase()}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// Helper Functions
|
|
283
|
+
// ============================================================================
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Common error suggestions based on error patterns
|
|
287
|
+
*/
|
|
288
|
+
export const SUGGESTIONS = {
|
|
289
|
+
// Compiler
|
|
290
|
+
'undefined-variable': (name) =>
|
|
291
|
+
`Did you forget to declare '${name}' in the state block or import it?`,
|
|
292
|
+
'duplicate-declaration': (name) =>
|
|
293
|
+
`Remove the duplicate declaration of '${name}'`,
|
|
294
|
+
'unexpected-token': (expected, got) =>
|
|
295
|
+
`Expected ${expected} but got '${got}'. Check for missing braces, quotes, or semicolons.`,
|
|
296
|
+
'missing-closing-brace': () =>
|
|
297
|
+
`Add the missing closing brace '}'`,
|
|
298
|
+
'missing-closing-paren': () =>
|
|
299
|
+
`Add the missing closing parenthesis ')'`,
|
|
300
|
+
'invalid-directive': (name) =>
|
|
301
|
+
`'${name}' is not a valid directive. Valid directives: @if, @for, @click, @link, @outlet`,
|
|
302
|
+
'empty-block': (blockName) =>
|
|
303
|
+
`The ${blockName} block is empty. Add content or remove the block.`,
|
|
304
|
+
|
|
305
|
+
// Reactivity
|
|
306
|
+
'computed-set': (name) =>
|
|
307
|
+
`Use a regular pulse() if you need to set values directly, or modify the source pulse(s) that '${name || 'this computed'}' depends on.`,
|
|
308
|
+
'circular-dependency': () =>
|
|
309
|
+
`Check for effects that modify their own dependencies. Consider using batch() to group updates.`,
|
|
310
|
+
'effect-cleanup': () =>
|
|
311
|
+
`Return a cleanup function from your effect: effect(() => { ... return () => cleanup(); })`,
|
|
312
|
+
|
|
313
|
+
// DOM
|
|
314
|
+
'mount-not-found': (selector) =>
|
|
315
|
+
`Ensure "${selector}" exists in the DOM. Use DOMContentLoaded or place script at end of <body>.`,
|
|
316
|
+
'invalid-selector': (selector) =>
|
|
317
|
+
`"${selector}" is not a valid CSS selector. Use tag.class#id[attr] format.`,
|
|
318
|
+
'list-needs-key': () =>
|
|
319
|
+
`Add a key function as third argument: list(items, render, item => item.id)`,
|
|
320
|
+
|
|
321
|
+
// Router
|
|
322
|
+
'route-not-found': (path) =>
|
|
323
|
+
`Add a route for "${path}" or use a catch-all route: '/*path': NotFoundPage`,
|
|
324
|
+
'lazy-timeout': (ms) =>
|
|
325
|
+
`The component took longer than ${ms}ms to load. Check your network or increase timeout.`,
|
|
326
|
+
|
|
327
|
+
// Store
|
|
328
|
+
'persist-quota': () =>
|
|
329
|
+
`localStorage is full. Consider clearing old data or reducing state size.`,
|
|
330
|
+
'invalid-store-value': (type) =>
|
|
331
|
+
`Store values must be serializable. ${type} cannot be persisted.`
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Format an error with source code context
|
|
336
|
+
* @param {Error} error - The error to format
|
|
337
|
+
* @param {string} [source] - Source code for context
|
|
338
|
+
* @returns {string} Formatted error string
|
|
339
|
+
*/
|
|
340
|
+
export function formatError(error, source) {
|
|
341
|
+
if (error instanceof PulseError) {
|
|
342
|
+
return error.formatWithSnippet(source);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Handle plain Error objects with line/column properties
|
|
346
|
+
if (error.line && source) {
|
|
347
|
+
const pulseError = new PulseError(error.message, {
|
|
348
|
+
line: error.line,
|
|
349
|
+
column: error.column,
|
|
350
|
+
file: error.file
|
|
351
|
+
});
|
|
352
|
+
return pulseError.formatWithSnippet(source);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return error.stack || error.message;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Create a parser error from a token
|
|
360
|
+
* @param {string} message - Error message
|
|
361
|
+
* @param {Object} token - Token object with line/column info
|
|
362
|
+
* @param {Object} [options={}] - Additional options
|
|
363
|
+
* @returns {ParserError} The created error
|
|
364
|
+
*/
|
|
365
|
+
export function createParserError(message, token, options = {}) {
|
|
366
|
+
return new ParserError(message, {
|
|
367
|
+
line: token?.line || 1,
|
|
368
|
+
column: token?.column || 1,
|
|
369
|
+
token,
|
|
370
|
+
...options
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Create a user-friendly error message with context and suggestions
|
|
376
|
+
* @param {Object} options - Error options
|
|
377
|
+
* @param {string} options.code - Error code (e.g., 'COMPUTED_SET')
|
|
378
|
+
* @param {string} options.message - Main error message
|
|
379
|
+
* @param {string} [options.context] - What the user was trying to do
|
|
380
|
+
* @param {string} [options.suggestion] - How to fix it
|
|
381
|
+
* @param {Object} [options.details] - Additional details
|
|
382
|
+
* @returns {string} Formatted error message
|
|
383
|
+
*/
|
|
384
|
+
export function createErrorMessage({ code, message, context, suggestion, details }) {
|
|
385
|
+
let output = `[Pulse] ${message}`;
|
|
386
|
+
|
|
387
|
+
if (context) {
|
|
388
|
+
output += `\n\n Context: ${context}`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (details) {
|
|
392
|
+
for (const [key, value] of Object.entries(details)) {
|
|
393
|
+
output += `\n ${key}: ${value}`;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (suggestion) {
|
|
398
|
+
output += `\n\n → ${suggestion}`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (code) {
|
|
402
|
+
output += `\n See: ${getDocsUrl(code)}`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return output;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ============================================================================
|
|
409
|
+
// Pre-built Error Creators
|
|
410
|
+
// ============================================================================
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Error creators for common scenarios with good DX
|
|
414
|
+
*/
|
|
415
|
+
export const Errors = {
|
|
416
|
+
/**
|
|
417
|
+
* Cannot set computed value
|
|
418
|
+
*/
|
|
419
|
+
computedSet(name) {
|
|
420
|
+
return new ReactivityError(
|
|
421
|
+
createErrorMessage({
|
|
422
|
+
code: 'COMPUTED_SET',
|
|
423
|
+
message: `Cannot set computed value${name ? ` '${name}'` : ''}.`,
|
|
424
|
+
context: 'Computed values are derived from other pulses and update automatically.',
|
|
425
|
+
suggestion: SUGGESTIONS['computed-set'](name)
|
|
426
|
+
}),
|
|
427
|
+
{ code: 'COMPUTED_SET' }
|
|
428
|
+
);
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Circular dependency in effects
|
|
433
|
+
*/
|
|
434
|
+
circularDependency(effectIds, pending) {
|
|
435
|
+
return new ReactivityError(
|
|
436
|
+
createErrorMessage({
|
|
437
|
+
code: 'CIRCULAR_DEPENDENCY',
|
|
438
|
+
message: 'Infinite loop detected in reactive effects.',
|
|
439
|
+
context: 'An effect is repeatedly triggering itself or other effects.',
|
|
440
|
+
details: {
|
|
441
|
+
'Most active effects': effectIds.slice(0, 5).join(', '),
|
|
442
|
+
'Still pending': pending.slice(0, 5).join(', ') || 'none'
|
|
443
|
+
},
|
|
444
|
+
suggestion: SUGGESTIONS['circular-dependency']()
|
|
445
|
+
}),
|
|
446
|
+
{ code: 'CIRCULAR_DEPENDENCY' }
|
|
447
|
+
);
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Mount target not found
|
|
452
|
+
*/
|
|
453
|
+
mountNotFound(selector) {
|
|
454
|
+
return new DOMError(
|
|
455
|
+
createErrorMessage({
|
|
456
|
+
code: 'MOUNT_ERROR',
|
|
457
|
+
message: `Mount target not found: "${selector}"`,
|
|
458
|
+
context: 'The element must exist in the DOM before mounting.',
|
|
459
|
+
suggestion: SUGGESTIONS['mount-not-found'](selector)
|
|
460
|
+
}),
|
|
461
|
+
{ code: 'MOUNT_ERROR' }
|
|
462
|
+
);
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* List missing key function
|
|
467
|
+
*/
|
|
468
|
+
listNoKey() {
|
|
469
|
+
return new DOMError(
|
|
470
|
+
createErrorMessage({
|
|
471
|
+
code: 'LIST_NO_KEY',
|
|
472
|
+
message: 'List rendering without key function may cause performance issues.',
|
|
473
|
+
context: 'Keys help Pulse efficiently update only changed items.',
|
|
474
|
+
suggestion: SUGGESTIONS['list-needs-key']()
|
|
475
|
+
}),
|
|
476
|
+
{ code: 'LIST_NO_KEY' }
|
|
477
|
+
);
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Route not found
|
|
482
|
+
*/
|
|
483
|
+
routeNotFound(path) {
|
|
484
|
+
return new RouterError(
|
|
485
|
+
createErrorMessage({
|
|
486
|
+
code: 'ROUTE_NOT_FOUND',
|
|
487
|
+
message: `No route matched: "${path}"`,
|
|
488
|
+
context: 'Navigation attempted to an undefined route.',
|
|
489
|
+
suggestion: SUGGESTIONS['route-not-found'](path)
|
|
490
|
+
}),
|
|
491
|
+
{ code: 'ROUTE_NOT_FOUND' }
|
|
492
|
+
);
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Lazy load timeout
|
|
497
|
+
*/
|
|
498
|
+
lazyTimeout(timeout) {
|
|
499
|
+
return new RouterError(
|
|
500
|
+
createErrorMessage({
|
|
501
|
+
code: 'LAZY_TIMEOUT',
|
|
502
|
+
message: `Lazy component load timed out after ${timeout}ms.`,
|
|
503
|
+
context: 'The dynamic import took too long to resolve.',
|
|
504
|
+
suggestion: SUGGESTIONS['lazy-timeout'](timeout)
|
|
505
|
+
}),
|
|
506
|
+
{ code: 'LAZY_TIMEOUT' }
|
|
507
|
+
);
|
|
508
|
+
},
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Store persistence error
|
|
512
|
+
*/
|
|
513
|
+
persistError(operation, cause) {
|
|
514
|
+
return new StoreError(
|
|
515
|
+
createErrorMessage({
|
|
516
|
+
code: 'PERSIST_ERROR',
|
|
517
|
+
message: `Failed to ${operation} persisted state.`,
|
|
518
|
+
context: cause?.message || 'localStorage operation failed.',
|
|
519
|
+
suggestion: SUGGESTIONS['persist-quota']()
|
|
520
|
+
}),
|
|
521
|
+
{ code: 'PERSIST_ERROR' }
|
|
522
|
+
);
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Invalid store value type
|
|
527
|
+
*/
|
|
528
|
+
invalidStoreValue(type) {
|
|
529
|
+
return new StoreError(
|
|
530
|
+
createErrorMessage({
|
|
531
|
+
code: 'STORE_TYPE_ERROR',
|
|
532
|
+
message: `Invalid value type in store: ${type}`,
|
|
533
|
+
context: 'Store values must be JSON-serializable.',
|
|
534
|
+
suggestion: SUGGESTIONS['invalid-store-value'](type)
|
|
535
|
+
}),
|
|
536
|
+
{ code: 'STORE_TYPE_ERROR' }
|
|
537
|
+
);
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Native API not available
|
|
542
|
+
*/
|
|
543
|
+
nativeNotAvailable(api) {
|
|
544
|
+
return new RuntimeError(
|
|
545
|
+
createErrorMessage({
|
|
546
|
+
code: 'NATIVE_ERROR',
|
|
547
|
+
message: `Native API '${api}' is not available.`,
|
|
548
|
+
context: 'This API only works in Pulse native mobile apps.',
|
|
549
|
+
suggestion: `Use isNativeAvailable() to check before calling native APIs, or use getPlatform() to detect the environment.`
|
|
550
|
+
}),
|
|
551
|
+
{ code: 'NATIVE_ERROR' }
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
export default {
|
|
557
|
+
PulseError,
|
|
558
|
+
CompileError,
|
|
559
|
+
LexerError,
|
|
560
|
+
ParserError,
|
|
561
|
+
TransformError,
|
|
562
|
+
RuntimeError,
|
|
563
|
+
ReactivityError,
|
|
564
|
+
DOMError,
|
|
565
|
+
StoreError,
|
|
566
|
+
RouterError,
|
|
567
|
+
CLIError,
|
|
568
|
+
ConfigError,
|
|
569
|
+
SUGGESTIONS,
|
|
570
|
+
Errors,
|
|
571
|
+
formatError,
|
|
572
|
+
createParserError,
|
|
573
|
+
createErrorMessage,
|
|
574
|
+
getDocsUrl
|
|
575
|
+
};
|