@mostfeatured/dbi 0.2.13 → 0.2.15
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/dist/src/types/Components/HTMLComponentsV2/index.d.ts +33 -1
- package/dist/src/types/Components/HTMLComponentsV2/index.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/index.js +408 -82
- package/dist/src/types/Components/HTMLComponentsV2/index.js.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts +52 -0
- package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/parser.js +275 -0
- package/dist/src/types/Components/HTMLComponentsV2/parser.js.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts +26 -0
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js +509 -34
- package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts +10 -0
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts.map +1 -1
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js +76 -11
- package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js.map +1 -1
- package/dist/test/index.js +76 -3
- package/dist/test/index.js.map +1 -1
- package/docs/ADVANCED_FEATURES.md +4 -0
- package/docs/API_REFERENCE.md +4 -0
- package/docs/CHAT_INPUT.md +4 -0
- package/docs/COMPONENTS.md +4 -0
- package/docs/EVENTS.md +4 -0
- package/docs/GETTING_STARTED.md +4 -0
- package/docs/LOCALIZATION.md +4 -0
- package/docs/README.md +4 -0
- package/docs/SVELTE_COMPONENTS.md +162 -6
- package/docs/llm/ADVANCED_FEATURES.txt +521 -0
- package/docs/llm/API_REFERENCE.txt +659 -0
- package/docs/llm/CHAT_INPUT.txt +514 -0
- package/docs/llm/COMPONENTS.txt +595 -0
- package/docs/llm/EVENTS.txt +449 -0
- package/docs/llm/GETTING_STARTED.txt +296 -0
- package/docs/llm/LOCALIZATION.txt +501 -0
- package/docs/llm/README.txt +193 -0
- package/docs/llm/SVELTE_COMPONENTS.txt +566 -0
- package/generated/svelte-dbi.d.ts +122 -0
- package/package.json +1 -1
- package/src/types/Components/HTMLComponentsV2/index.ts +466 -94
- package/src/types/Components/HTMLComponentsV2/parser.ts +317 -0
- package/src/types/Components/HTMLComponentsV2/svelteParser.ts +567 -35
- package/src/types/Components/HTMLComponentsV2/svelteRenderer.ts +91 -13
- package/test/index.ts +76 -3
- package/test/product-showcase.svelte +380 -24
- package/llm.txt +0 -1088
|
@@ -64,14 +64,33 @@ function walkSvelteAst(node: any, callback: (node: any) => void) {
|
|
|
64
64
|
export interface SvelteHandlerInfo {
|
|
65
65
|
name: string;
|
|
66
66
|
handlerName: string;
|
|
67
|
-
eventType: string; // onclick, onchange, etc.
|
|
68
|
-
element: string; // button, string-select, etc.
|
|
67
|
+
eventType: string; // onclick, onchange, onsubmit, etc.
|
|
68
|
+
element: string; // button, string-select, components (for modal), etc.
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ModalHandlerInfo {
|
|
72
|
+
modalId: string;
|
|
73
|
+
onsubmitHandler?: string; // Handler function name for onsubmit
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface SvelteValidationWarning {
|
|
77
|
+
type: 'missing-data' | 'unused-data' | 'undefined-handler' | 'syntax-error' | 'runtime-error';
|
|
78
|
+
message: string;
|
|
79
|
+
details?: string;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
export interface SvelteComponentInfo {
|
|
72
83
|
handlers: Map<string, SvelteHandlerInfo>;
|
|
84
|
+
/** Modal onsubmit handlers keyed by modal id */
|
|
85
|
+
modalHandlers: Map<string, ModalHandlerInfo>;
|
|
73
86
|
scriptContent: string;
|
|
74
87
|
processedSource: string; // Source with auto-generated names injected
|
|
88
|
+
/** Props extracted from $props() destructuring */
|
|
89
|
+
declaredProps: string[];
|
|
90
|
+
/** Props that have default values (don't require data) */
|
|
91
|
+
propsWithDefaults: string[];
|
|
92
|
+
/** Function names declared in the script */
|
|
93
|
+
declaredFunctions: string[];
|
|
75
94
|
}
|
|
76
95
|
|
|
77
96
|
/**
|
|
@@ -80,8 +99,42 @@ export interface SvelteComponentInfo {
|
|
|
80
99
|
*/
|
|
81
100
|
export async function parseSvelteComponent(source: string, data?: Record<string, any>): Promise<SvelteComponentInfo> {
|
|
82
101
|
await ensureImports();
|
|
83
|
-
|
|
102
|
+
|
|
103
|
+
let ast;
|
|
104
|
+
try {
|
|
105
|
+
ast = _parse(source);
|
|
106
|
+
} catch (parseError: any) {
|
|
107
|
+
// Format Svelte parse error with helpful details
|
|
108
|
+
const errorMessage = parseError.message || 'Unknown parse error';
|
|
109
|
+
const location = parseError.start || parseError.loc;
|
|
110
|
+
let details = errorMessage;
|
|
111
|
+
|
|
112
|
+
if (location) {
|
|
113
|
+
const lines = source.split('\n');
|
|
114
|
+
const lineNum = location.line || 1;
|
|
115
|
+
const column = location.column || 0;
|
|
116
|
+
const errorLine = lines[lineNum - 1] || '';
|
|
117
|
+
const prevLine = lines[lineNum - 2] || '';
|
|
118
|
+
const nextLine = lines[lineNum] || '';
|
|
119
|
+
|
|
120
|
+
details = `
|
|
121
|
+
Svelte Parse Error at line ${lineNum}, column ${column}:
|
|
122
|
+
${errorMessage}
|
|
123
|
+
|
|
124
|
+
${lineNum > 1 ? `${lineNum - 1} | ${prevLine}\n` : ''}${lineNum} | ${errorLine}
|
|
125
|
+
${' '.repeat(String(lineNum).length + 3 + column)}^
|
|
126
|
+
${nextLine ? `${lineNum + 1} | ${nextLine}` : ''}
|
|
127
|
+
`.trim();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const enhancedError = new Error(`[DBI-Svelte] Failed to parse Svelte component:\n${details}`);
|
|
131
|
+
(enhancedError as any).originalError = parseError;
|
|
132
|
+
(enhancedError as any).type = 'svelte-parse-error';
|
|
133
|
+
throw enhancedError;
|
|
134
|
+
}
|
|
135
|
+
|
|
84
136
|
const handlers = new Map<string, SvelteHandlerInfo>();
|
|
137
|
+
const modalHandlers = new Map<string, ModalHandlerInfo>();
|
|
85
138
|
let scriptContent = "";
|
|
86
139
|
|
|
87
140
|
// Extract script content
|
|
@@ -98,6 +151,62 @@ export async function parseSvelteComponent(source: string, data?: Record<string,
|
|
|
98
151
|
walkSvelteAst(ast.html || ast.fragment, (node: any) => {
|
|
99
152
|
if (node.type === "Element" || node.type === "InlineComponent" || node.type === "RegularElement" || node.type === "Component") {
|
|
100
153
|
const attributes = node.attributes || [];
|
|
154
|
+
const nodeName = node.name.toLowerCase();
|
|
155
|
+
|
|
156
|
+
// Special handling for <components type="modal"> elements
|
|
157
|
+
if (nodeName === "components") {
|
|
158
|
+
const typeAttr = attributes.find((attr: any) =>
|
|
159
|
+
attr.type === "Attribute" && attr.name === "type"
|
|
160
|
+
);
|
|
161
|
+
const typeValue = typeAttr ? getAttributeValue(typeAttr) : null;
|
|
162
|
+
|
|
163
|
+
if (typeValue === "modal") {
|
|
164
|
+
// This is a modal definition - extract id and onsubmit handler
|
|
165
|
+
const idAttr = attributes.find((attr: any) =>
|
|
166
|
+
attr.type === "Attribute" && attr.name === "id"
|
|
167
|
+
);
|
|
168
|
+
const modalId = idAttr ? getAttributeValue(idAttr) : null;
|
|
169
|
+
|
|
170
|
+
if (modalId) {
|
|
171
|
+
const modalInfo: ModalHandlerInfo = { modalId };
|
|
172
|
+
|
|
173
|
+
// Find onsubmit handler
|
|
174
|
+
for (const attr of attributes) {
|
|
175
|
+
const isOnSubmit = (attr.type === "Attribute" && attr.name === "onsubmit") ||
|
|
176
|
+
(attr.type === "EventHandler" && attr.name === "submit");
|
|
177
|
+
|
|
178
|
+
if (isOnSubmit) {
|
|
179
|
+
let handlerName = "";
|
|
180
|
+
|
|
181
|
+
if (attr.type === "Attribute" && Array.isArray(attr.value)) {
|
|
182
|
+
const exprValue = attr.value.find((v: any) => v.type === "ExpressionTag" || v.type === "MustacheTag");
|
|
183
|
+
if (exprValue && exprValue.expression) {
|
|
184
|
+
if (exprValue.expression.type === "Identifier") {
|
|
185
|
+
handlerName = exprValue.expression.name;
|
|
186
|
+
} else if (exprValue.expression.type === "CallExpression" && exprValue.expression.callee) {
|
|
187
|
+
handlerName = exprValue.expression.callee.name;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} else if (attr.expression) {
|
|
191
|
+
if (attr.expression.type === "Identifier") {
|
|
192
|
+
handlerName = attr.expression.name;
|
|
193
|
+
} else if (attr.expression.type === "CallExpression" && attr.expression.callee) {
|
|
194
|
+
handlerName = attr.expression.callee.name;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (handlerName) {
|
|
199
|
+
modalInfo.onsubmitHandler = handlerName;
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
modalHandlers.set(modalId, modalInfo);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return; // Don't process <components> as regular elements
|
|
209
|
+
}
|
|
101
210
|
|
|
102
211
|
// Find name attribute
|
|
103
212
|
const nameAttr = attributes.find((attr: any) =>
|
|
@@ -210,10 +319,48 @@ export async function parseSvelteComponent(source: string, data?: Record<string,
|
|
|
210
319
|
processedSource = processedSource.slice(0, tagEnd) + ` name="${name}"` + processedSource.slice(tagEnd);
|
|
211
320
|
}
|
|
212
321
|
|
|
322
|
+
// Extract declared props from $props() destructuring
|
|
323
|
+
const declaredProps: string[] = [];
|
|
324
|
+
const propsWithDefaults: string[] = [];
|
|
325
|
+
const propsMatch = scriptContent.match(/let\s+\{([\s\S]*?)\}\s*=\s*\$props\(\)/);
|
|
326
|
+
if (propsMatch) {
|
|
327
|
+
const propsContent = propsMatch[1];
|
|
328
|
+
// Remove comments first
|
|
329
|
+
const cleanedContent = propsContent
|
|
330
|
+
.replace(/\/\/.*$/gm, '')
|
|
331
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
332
|
+
const props = parsePropsContent(cleanedContent);
|
|
333
|
+
for (const prop of props) {
|
|
334
|
+
if (prop.name) {
|
|
335
|
+
declaredProps.push(prop.name);
|
|
336
|
+
if (prop.defaultValue !== undefined) {
|
|
337
|
+
propsWithDefaults.push(prop.name);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Extract declared function names
|
|
344
|
+
const declaredFunctions: string[] = [];
|
|
345
|
+
const funcRegex = /(?:async\s+)?function\s+(\w+)\s*\(/g;
|
|
346
|
+
let funcMatch;
|
|
347
|
+
while ((funcMatch = funcRegex.exec(scriptContent)) !== null) {
|
|
348
|
+
declaredFunctions.push(funcMatch[1]);
|
|
349
|
+
}
|
|
350
|
+
// Also match arrow functions assigned to variables: const/let/var name = (async) () =>
|
|
351
|
+
const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g;
|
|
352
|
+
while ((funcMatch = arrowRegex.exec(scriptContent)) !== null) {
|
|
353
|
+
declaredFunctions.push(funcMatch[1]);
|
|
354
|
+
}
|
|
355
|
+
|
|
213
356
|
return {
|
|
214
357
|
handlers,
|
|
358
|
+
modalHandlers,
|
|
215
359
|
scriptContent,
|
|
216
|
-
processedSource
|
|
360
|
+
processedSource,
|
|
361
|
+
declaredProps,
|
|
362
|
+
propsWithDefaults,
|
|
363
|
+
declaredFunctions
|
|
217
364
|
};
|
|
218
365
|
}
|
|
219
366
|
|
|
@@ -265,6 +412,212 @@ function extractExpressionValue(expr: any): string {
|
|
|
265
412
|
return "";
|
|
266
413
|
}
|
|
267
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Parse $props() destructuring content with proper brace/bracket counting
|
|
417
|
+
* Handles nested objects like: { name, options = { a: 1, b: [1, 2] }, count = 0 }
|
|
418
|
+
*/
|
|
419
|
+
function parsePropsContent(content: string): Array<{ name: string; defaultValue?: string }> {
|
|
420
|
+
const props: Array<{ name: string; defaultValue?: string }> = [];
|
|
421
|
+
let current = '';
|
|
422
|
+
let braceCount = 0;
|
|
423
|
+
let bracketCount = 0;
|
|
424
|
+
let parenCount = 0;
|
|
425
|
+
let inString: string | null = null;
|
|
426
|
+
|
|
427
|
+
for (let i = 0; i <= content.length; i++) {
|
|
428
|
+
const char = content[i];
|
|
429
|
+
const prevChar = i > 0 ? content[i - 1] : '';
|
|
430
|
+
|
|
431
|
+
// Handle string boundaries
|
|
432
|
+
if ((char === '"' || char === "'" || char === '`') && prevChar !== '\\') {
|
|
433
|
+
if (inString === char) {
|
|
434
|
+
inString = null;
|
|
435
|
+
} else if (!inString) {
|
|
436
|
+
inString = char;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Only process structural characters when not in a string
|
|
441
|
+
if (!inString) {
|
|
442
|
+
if (char === '{') braceCount++;
|
|
443
|
+
else if (char === '}') braceCount--;
|
|
444
|
+
else if (char === '[') bracketCount++;
|
|
445
|
+
else if (char === ']') bracketCount--;
|
|
446
|
+
else if (char === '(') parenCount++;
|
|
447
|
+
else if (char === ')') parenCount--;
|
|
448
|
+
|
|
449
|
+
// Split on comma only when at top level (no nested braces/brackets/parens)
|
|
450
|
+
if ((char === ',' || i === content.length) && braceCount === 0 && bracketCount === 0 && parenCount === 0) {
|
|
451
|
+
const trimmed = current.trim();
|
|
452
|
+
if (trimmed) {
|
|
453
|
+
// Parse "name = defaultValue" or just "name"
|
|
454
|
+
const equalsIndex = trimmed.indexOf('=');
|
|
455
|
+
if (equalsIndex > 0) {
|
|
456
|
+
const name = trimmed.substring(0, equalsIndex).trim();
|
|
457
|
+
const defaultValue = trimmed.substring(equalsIndex + 1).trim();
|
|
458
|
+
props.push({ name, defaultValue });
|
|
459
|
+
} else {
|
|
460
|
+
props.push({ name: trimmed });
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
current = '';
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (i < content.length) {
|
|
469
|
+
current += char;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return props;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Validate a Svelte component and return warnings
|
|
478
|
+
* Call this during development/registration to catch potential issues early
|
|
479
|
+
*/
|
|
480
|
+
export function validateSvelteComponent(
|
|
481
|
+
componentInfo: SvelteComponentInfo,
|
|
482
|
+
data: Record<string, any> = {},
|
|
483
|
+
componentName: string = 'unknown'
|
|
484
|
+
): SvelteValidationWarning[] {
|
|
485
|
+
const warnings: SvelteValidationWarning[] = [];
|
|
486
|
+
|
|
487
|
+
// Skip internal props/data keys (used by the framework)
|
|
488
|
+
const internalKeys = ['$ref', '$unRef', '__unRefWrapped__', '$autoNames', 'data'];
|
|
489
|
+
|
|
490
|
+
// 1. Check for props declared but not provided in data (missing required data)
|
|
491
|
+
// Skip props that have default values - they don't require data
|
|
492
|
+
for (const prop of componentInfo.declaredProps) {
|
|
493
|
+
if (internalKeys.includes(prop)) continue;
|
|
494
|
+
if (componentInfo.propsWithDefaults.includes(prop)) continue; // Has default, not required
|
|
495
|
+
if (!(prop in data)) {
|
|
496
|
+
warnings.push({
|
|
497
|
+
type: 'missing-data',
|
|
498
|
+
message: `[${componentName}] Prop "${prop}" is declared in $props() without a default value but not provided in data`,
|
|
499
|
+
details: `Add "${prop}" to your data object or provide a default value in $props()`
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// 2. Check for data provided but not declared in props (potential typo or unused)
|
|
505
|
+
for (const key of Object.keys(data)) {
|
|
506
|
+
if (internalKeys.includes(key)) continue;
|
|
507
|
+
if (key.startsWith('$')) continue; // Skip all $-prefixed internal keys
|
|
508
|
+
if (!componentInfo.declaredProps.includes(key)) {
|
|
509
|
+
warnings.push({
|
|
510
|
+
type: 'unused-data',
|
|
511
|
+
message: `[${componentName}] Data key "${key}" is provided but not declared in $props()`,
|
|
512
|
+
details: `This data won't be accessible in the component. Add it to $props() destructuring.`
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// 3. Check for undefined handlers referenced in elements
|
|
518
|
+
for (const [elementName, handlerInfo] of componentInfo.handlers) {
|
|
519
|
+
if (!componentInfo.declaredFunctions.includes(handlerInfo.handlerName)) {
|
|
520
|
+
warnings.push({
|
|
521
|
+
type: 'undefined-handler',
|
|
522
|
+
message: `[${componentName}] Handler "${handlerInfo.handlerName}" referenced by <${handlerInfo.element} name="${elementName}"> is not defined`,
|
|
523
|
+
details: `Make sure to define "function ${handlerInfo.handlerName}(ctx) { ... }" in your script`
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// 4. Check for undefined modal submit handlers
|
|
529
|
+
for (const [modalId, modalInfo] of componentInfo.modalHandlers) {
|
|
530
|
+
if (modalInfo.onsubmitHandler && !componentInfo.declaredFunctions.includes(modalInfo.onsubmitHandler)) {
|
|
531
|
+
warnings.push({
|
|
532
|
+
type: 'undefined-handler',
|
|
533
|
+
message: `[${componentName}] Modal submit handler "${modalInfo.onsubmitHandler}" for modal "${modalId}" is not defined`,
|
|
534
|
+
details: `Make sure to define "function ${modalInfo.onsubmitHandler}(ctx, fields) { ... }" in your script`
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// 5. Check for modal handler signatures (ctx parameter is optional for regular handlers)
|
|
540
|
+
// Only modal handlers MUST have fields parameter to access submitted data
|
|
541
|
+
const handlerFunctionRegex = /(?:async\s+)?function\s+(\w+)\s*\(\s*(\w*)\s*(?:,\s*(\w+))?\s*\)/g;
|
|
542
|
+
let match;
|
|
543
|
+
const scriptContent = componentInfo.scriptContent;
|
|
544
|
+
|
|
545
|
+
while ((match = handlerFunctionRegex.exec(scriptContent)) !== null) {
|
|
546
|
+
const funcName = match[1];
|
|
547
|
+
const firstParam = match[2];
|
|
548
|
+
const secondParam = match[3];
|
|
549
|
+
|
|
550
|
+
// Only check modal handlers - they need 'fields' param to access form data
|
|
551
|
+
const isModalHandler = Array.from(componentInfo.modalHandlers.values()).some(m => m.onsubmitHandler === funcName);
|
|
552
|
+
|
|
553
|
+
if (isModalHandler && !secondParam) {
|
|
554
|
+
warnings.push({
|
|
555
|
+
type: 'syntax-error',
|
|
556
|
+
message: `[${componentName}] Modal handler "${funcName}" should have "ctx" and "fields" parameters`,
|
|
557
|
+
details: `Change to "function ${funcName}(ctx, fields) { ... }" to receive submitted form data`
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// 6. Check for common template mistakes
|
|
563
|
+
const templateContent = componentInfo.processedSource;
|
|
564
|
+
|
|
565
|
+
// Check for onclick= without braces (common mistake)
|
|
566
|
+
const badOnClickRegex = /onclick\s*=\s*["']([^"']+)["']/gi;
|
|
567
|
+
while ((match = badOnClickRegex.exec(templateContent)) !== null) {
|
|
568
|
+
if (!match[1].startsWith('{')) {
|
|
569
|
+
warnings.push({
|
|
570
|
+
type: 'syntax-error',
|
|
571
|
+
message: `[${componentName}] onclick handler should use curly braces: onclick={${match[1]}}`,
|
|
572
|
+
details: `String values are not valid for event handlers. Use onclick={handlerName} syntax.`
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Check for undefined variables in {expression} blocks (basic check)
|
|
578
|
+
const expressionRegex = /\{([^}]+)\}/g;
|
|
579
|
+
const knownIdentifiers = new Set([
|
|
580
|
+
...componentInfo.declaredProps,
|
|
581
|
+
...componentInfo.declaredFunctions,
|
|
582
|
+
// Common Svelte/JS globals
|
|
583
|
+
'true', 'false', 'null', 'undefined', 'console', 'Math', 'JSON', 'Array', 'Object',
|
|
584
|
+
'Date', 'Number', 'String', 'Boolean', 'Promise', 'Map', 'Set',
|
|
585
|
+
// Common Svelte constructs
|
|
586
|
+
'#if', '/if', '#each', '/each', '#await', '/await', ':else', ':then', ':catch',
|
|
587
|
+
'@html', '@debug', '@const'
|
|
588
|
+
]);
|
|
589
|
+
|
|
590
|
+
// Add variables from script (let, const, var declarations)
|
|
591
|
+
const varDeclRegex = /(?:let|const|var)\s+(\w+)/g;
|
|
592
|
+
while ((match = varDeclRegex.exec(scriptContent)) !== null) {
|
|
593
|
+
knownIdentifiers.add(match[1]);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return warnings;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Log validation warnings to console with colors
|
|
601
|
+
*/
|
|
602
|
+
export function logValidationWarnings(warnings: SvelteValidationWarning[]): void {
|
|
603
|
+
if (warnings.length === 0) return;
|
|
604
|
+
|
|
605
|
+
console.warn(`\n⚠️ Svelte Component Validation Warnings (${warnings.length}):`);
|
|
606
|
+
|
|
607
|
+
for (const warning of warnings) {
|
|
608
|
+
const icon = warning.type === 'missing-data' ? '❌' :
|
|
609
|
+
warning.type === 'unused-data' ? '⚠️' :
|
|
610
|
+
warning.type === 'undefined-handler' ? '🔗' :
|
|
611
|
+
warning.type === 'syntax-error' ? '💥' : '⚡';
|
|
612
|
+
|
|
613
|
+
console.warn(` ${icon} ${warning.message}`);
|
|
614
|
+
if (warning.details) {
|
|
615
|
+
console.warn(` └─ ${warning.details}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
console.warn('');
|
|
619
|
+
}
|
|
620
|
+
|
|
268
621
|
export interface HandlerContextResult {
|
|
269
622
|
handlers: Record<string, Function>;
|
|
270
623
|
effects: Function[];
|
|
@@ -405,7 +758,15 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
405
758
|
// 2. Remove $props destructuring
|
|
406
759
|
// 3. Convert $effect to __registerEffect__
|
|
407
760
|
// 4. Keep only function declarations
|
|
408
|
-
|
|
761
|
+
|
|
762
|
+
// First, remove comments from the script to avoid regex issues with braces in comments
|
|
763
|
+
let scriptWithoutComments = cleanedScript
|
|
764
|
+
// Remove single-line comments (but preserve the newline)
|
|
765
|
+
.replace(/\/\/.*$/gm, '')
|
|
766
|
+
// Remove multi-line comments
|
|
767
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
768
|
+
|
|
769
|
+
let processedScript = scriptWithoutComments
|
|
409
770
|
// Remove $state declarations completely or make them var
|
|
410
771
|
.replace(/let\s+(\w+)\s*=\s*\$state\(([^)]*)\);?/g, 'var $1 = $2;')
|
|
411
772
|
// Remove $derived declarations but keep the value
|
|
@@ -413,31 +774,46 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
413
774
|
// Convert $effect calls to __registerEffect__ calls
|
|
414
775
|
.replace(/\$effect\s*\(\s*((?:function\s*\([^)]*\)|\([^)]*\)\s*=>|\(\)\s*=>)[^}]*\{[\s\S]*?\}\s*)\);?/g, '__registerEffect__($1);')
|
|
415
776
|
// Simpler $effect pattern: $effect(() => { ... })
|
|
416
|
-
.replace(/\$effect\s*\(\s*\(\)\s*=>\s*\{([\s\S]*?)\}\s*\);?/g, '__registerEffect__(function() {$1});')
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return `var ${varName} = data.${varName} ?? ${cleanDefault};`;
|
|
777
|
+
.replace(/\$effect\s*\(\s*\(\)\s*=>\s*\{([\s\S]*?)\}\s*\);?/g, '__registerEffect__(function() {$1});');
|
|
778
|
+
|
|
779
|
+
// Handle $props destructuring with proper brace counting (supports nested objects like { options = { a: 1 } })
|
|
780
|
+
processedScript = processedScript.replace(/let\s+\{([\s\S]*?)\}\s*=\s*\$props\(\);?/g, (match) => {
|
|
781
|
+
// Find the opening brace after 'let'
|
|
782
|
+
const letIndex = match.indexOf('{');
|
|
783
|
+
if (letIndex === -1) return match;
|
|
784
|
+
|
|
785
|
+
// Use brace counting to find the matching closing brace
|
|
786
|
+
let braceCount = 0;
|
|
787
|
+
let startIndex = letIndex;
|
|
788
|
+
let endIndex = -1;
|
|
789
|
+
|
|
790
|
+
for (let i = startIndex; i < match.length; i++) {
|
|
791
|
+
if (match[i] === '{') braceCount++;
|
|
792
|
+
else if (match[i] === '}') {
|
|
793
|
+
braceCount--;
|
|
794
|
+
if (braceCount === 0) {
|
|
795
|
+
endIndex = i;
|
|
796
|
+
break;
|
|
437
797
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (endIndex === -1) return match;
|
|
802
|
+
|
|
803
|
+
// Extract the content between braces
|
|
804
|
+
const content = match.substring(startIndex + 1, endIndex);
|
|
805
|
+
|
|
806
|
+
// Parse props with proper handling of nested braces
|
|
807
|
+
const props = parsePropsContent(content);
|
|
808
|
+
|
|
809
|
+
return props.map(prop => {
|
|
810
|
+
if (!prop.name || prop.name === 'data') return '';
|
|
811
|
+
if (prop.defaultValue !== undefined) {
|
|
812
|
+
return `var ${prop.name} = data.${prop.name} ?? ${prop.defaultValue};`;
|
|
813
|
+
}
|
|
814
|
+
return `var ${prop.name} = data.${prop.name};`;
|
|
815
|
+
}).filter(Boolean).join('\n');
|
|
816
|
+
});
|
|
441
817
|
|
|
442
818
|
// Add module variable declarations at the beginning of processed script
|
|
443
819
|
if (varDeclarations) {
|
|
@@ -488,6 +864,102 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
488
864
|
__destroyCallbacks__.push(fn);
|
|
489
865
|
}
|
|
490
866
|
|
|
867
|
+
// Modal store - stores rendered modal definitions
|
|
868
|
+
var __modals__ = new Map();
|
|
869
|
+
|
|
870
|
+
// Register a modal (called internally when modals are parsed)
|
|
871
|
+
function __registerModal__(modalId, modalDef) {
|
|
872
|
+
__modals__.set(modalId, modalDef);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Show a modal by ID and return a Promise that resolves with the submitted fields
|
|
876
|
+
// Modal definitions are rendered from <components type="modal" id="xxx">
|
|
877
|
+
// Usage:
|
|
878
|
+
// await showModal("edit-product"); // Just show, use onsubmit handler
|
|
879
|
+
// const { fields, interaction } = await showModal("edit-product"); // Get response
|
|
880
|
+
// await showModal("edit-product", { timeout: 60000 }); // 60 second timeout
|
|
881
|
+
// await showModal("edit-product", { timeout: 0 }); // No timeout (unlimited)
|
|
882
|
+
function showModal(modalId, options) {
|
|
883
|
+
if (!__ctx__ || !__ctx__.interaction) {
|
|
884
|
+
return Promise.reject(new Error("showModal requires an interaction context"));
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Default timeout: 10 minutes (600000ms), 0 = no timeout
|
|
888
|
+
var timeout = (options && typeof options.timeout === 'number') ? options.timeout : 600000;
|
|
889
|
+
|
|
890
|
+
// Disable auto-render IMMEDIATELY since we're showing a modal
|
|
891
|
+
// This prevents flushRender from trying to update after modal is shown
|
|
892
|
+
__autoRenderEnabled__ = false;
|
|
893
|
+
__asyncInteractionCalled__ = true;
|
|
894
|
+
|
|
895
|
+
// Create a promise that will be resolved when modal is submitted
|
|
896
|
+
return new Promise(async function(resolve, reject) {
|
|
897
|
+
var timeoutId = null;
|
|
898
|
+
var modal = null;
|
|
899
|
+
|
|
900
|
+
try {
|
|
901
|
+
// Re-render to get latest modal definitions (reactive)
|
|
902
|
+
var renderResult = await __component__.toJSON({ data: __data__ });
|
|
903
|
+
|
|
904
|
+
// Get modals from the render result stored on component
|
|
905
|
+
var modals = __component__.__lastRenderModals__;
|
|
906
|
+
if (!modals) {
|
|
907
|
+
reject(new Error("No modals defined in this component. Use <components type=\\"modal\\" id=\\"...\\">"));
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
modal = modals.get(modalId);
|
|
912
|
+
if (!modal) {
|
|
913
|
+
reject(new Error("Modal '" + modalId + "' not found. Available modals: " + Array.from(modals.keys()).join(", ")));
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Wrapped resolve/reject to clear timeout
|
|
918
|
+
var wrappedResolve = function(result) {
|
|
919
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
920
|
+
resolve(result);
|
|
921
|
+
};
|
|
922
|
+
var wrappedReject = function(error) {
|
|
923
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
924
|
+
reject(error);
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// Store the promise resolver in component's pending modals map
|
|
928
|
+
// This will be resolved when modal submit interaction is received
|
|
929
|
+
__component__._pendingModals.set(modal.customId, { resolve: wrappedResolve, reject: wrappedReject });
|
|
930
|
+
|
|
931
|
+
// Set up timeout if enabled (timeout > 0)
|
|
932
|
+
if (timeout > 0) {
|
|
933
|
+
timeoutId = setTimeout(function() {
|
|
934
|
+
// Clean up pending modal
|
|
935
|
+
__component__._pendingModals.delete(modal.customId);
|
|
936
|
+
reject(new Error("Modal '" + modalId + "' timed out after " + timeout + "ms"));
|
|
937
|
+
}, timeout);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Show the modal using the interaction
|
|
941
|
+
var i = __ctx__.interaction;
|
|
942
|
+
|
|
943
|
+
// Modal must be shown immediately - no deferral allowed
|
|
944
|
+
await i.showModal({
|
|
945
|
+
title: modal.title,
|
|
946
|
+
customId: modal.customId,
|
|
947
|
+
components: modal.components
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
// Note: Promise resolves later when modal is submitted
|
|
951
|
+
// If you don't await for the response, the promise just hangs (which is fine)
|
|
952
|
+
} catch (err) {
|
|
953
|
+
// Clean up pending modal and timeout if show failed
|
|
954
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
955
|
+
if (modal && modal.customId) {
|
|
956
|
+
__component__._pendingModals.delete(modal.customId);
|
|
957
|
+
}
|
|
958
|
+
reject(new Error("Failed to show modal: " + (err.message || err)));
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
|
|
491
963
|
// Rate-limit aware edit function with retry
|
|
492
964
|
function __safeEdit__(editFn, retryCount) {
|
|
493
965
|
retryCount = retryCount || 0;
|
|
@@ -610,6 +1082,9 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
610
1082
|
// Track if we're inside a handler execution
|
|
611
1083
|
var __inHandlerExecution__ = false;
|
|
612
1084
|
|
|
1085
|
+
// Track pending low-priority updates (from intervals/timeouts)
|
|
1086
|
+
var __pendingLowPriorityRender__ = false;
|
|
1087
|
+
|
|
613
1088
|
// Mark that we have pending data changes
|
|
614
1089
|
function __markDataChanged__() {
|
|
615
1090
|
__hasDataChanges__ = true;
|
|
@@ -622,6 +1097,20 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
622
1097
|
}
|
|
623
1098
|
}
|
|
624
1099
|
|
|
1100
|
+
// Low-priority update - used for background tasks like intervals
|
|
1101
|
+
// If a handler is currently running, skip this update (the handler's update will include our changes)
|
|
1102
|
+
function lowPriorityUpdate(callback) {
|
|
1103
|
+
if (__inHandlerExecution__) {
|
|
1104
|
+
// Handler is running - just update data, skip render
|
|
1105
|
+
// The handler will render when it finishes
|
|
1106
|
+
if (callback) callback();
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// No handler running - proceed with update
|
|
1111
|
+
if (callback) callback();
|
|
1112
|
+
}
|
|
1113
|
+
|
|
625
1114
|
// Flush pending render (called after interaction methods complete)
|
|
626
1115
|
function __flushRender__() {
|
|
627
1116
|
if (__hasDataChanges__ && __autoRenderEnabled__) {
|
|
@@ -631,6 +1120,7 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
631
1120
|
}
|
|
632
1121
|
|
|
633
1122
|
// Create reactive proxy for data
|
|
1123
|
+
// Also properly forwards Object.keys/values/entries operations
|
|
634
1124
|
function __createReactiveProxy__(target, path) {
|
|
635
1125
|
if (typeof target !== 'object' || target === null) return target;
|
|
636
1126
|
|
|
@@ -651,6 +1141,16 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
651
1141
|
__markDataChanged__();
|
|
652
1142
|
}
|
|
653
1143
|
return true;
|
|
1144
|
+
},
|
|
1145
|
+
// Forward Object.keys/values/entries operations
|
|
1146
|
+
ownKeys: function(target) {
|
|
1147
|
+
return Reflect.ownKeys(target);
|
|
1148
|
+
},
|
|
1149
|
+
getOwnPropertyDescriptor: function(target, prop) {
|
|
1150
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
1151
|
+
},
|
|
1152
|
+
has: function(target, prop) {
|
|
1153
|
+
return Reflect.has(target, prop);
|
|
654
1154
|
}
|
|
655
1155
|
});
|
|
656
1156
|
}
|
|
@@ -669,9 +1169,13 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
669
1169
|
var originalInteraction = __ctx__.interaction;
|
|
670
1170
|
|
|
671
1171
|
// Create a proxy for interaction that wraps reply/followUp/deferReply
|
|
1172
|
+
// Also properly forwards Object.keys/values/entries operations
|
|
1173
|
+
// Uses Reflect.get to properly handle getters with correct 'this' binding
|
|
672
1174
|
interaction = new Proxy(originalInteraction, {
|
|
673
|
-
get: function(target, prop) {
|
|
674
|
-
|
|
1175
|
+
get: function(target, prop, receiver) {
|
|
1176
|
+
// Use Reflect.get to properly handle getters (like 'roles', 'users', etc.)
|
|
1177
|
+
// This ensures 'this' context is correct for Discord.js Collection getters
|
|
1178
|
+
var value = Reflect.get(target, prop, target);
|
|
675
1179
|
|
|
676
1180
|
// Wrap methods that "consume" the interaction (reply, followUp, defer)
|
|
677
1181
|
if (prop === 'reply' || prop === 'followUp' || prop === 'deferReply' || prop === 'deferUpdate') {
|
|
@@ -711,16 +1215,38 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
711
1215
|
}
|
|
712
1216
|
|
|
713
1217
|
return value;
|
|
1218
|
+
},
|
|
1219
|
+
// Forward Object.keys/values/entries operations
|
|
1220
|
+
ownKeys: function(target) {
|
|
1221
|
+
return Reflect.ownKeys(target);
|
|
1222
|
+
},
|
|
1223
|
+
getOwnPropertyDescriptor: function(target, prop) {
|
|
1224
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
1225
|
+
},
|
|
1226
|
+
has: function(target, prop) {
|
|
1227
|
+
return Reflect.has(target, prop);
|
|
714
1228
|
}
|
|
715
1229
|
});
|
|
716
1230
|
|
|
717
1231
|
// Create wrapped ctx with the proxied interaction
|
|
1232
|
+
// Also properly forwards Object.keys/values/entries operations
|
|
718
1233
|
ctx = new Proxy(__ctx__, {
|
|
719
|
-
get: function(target, prop) {
|
|
1234
|
+
get: function(target, prop, receiver) {
|
|
720
1235
|
if (prop === 'interaction') {
|
|
721
1236
|
return interaction;
|
|
722
1237
|
}
|
|
723
|
-
|
|
1238
|
+
// Use Reflect.get for proper getter handling
|
|
1239
|
+
return Reflect.get(target, prop, target);
|
|
1240
|
+
},
|
|
1241
|
+
// Forward Object.keys/values/entries operations
|
|
1242
|
+
ownKeys: function(target) {
|
|
1243
|
+
return Reflect.ownKeys(target);
|
|
1244
|
+
},
|
|
1245
|
+
getOwnPropertyDescriptor: function(target, prop) {
|
|
1246
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
1247
|
+
},
|
|
1248
|
+
has: function(target, prop) {
|
|
1249
|
+
return Reflect.has(target, prop);
|
|
724
1250
|
}
|
|
725
1251
|
});
|
|
726
1252
|
}
|
|
@@ -856,7 +1382,12 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
856
1382
|
const createHandlers = factoryFunc(console);
|
|
857
1383
|
|
|
858
1384
|
// Execute with the actual data, component, ctx, and imported modules to get handlers with proper closure
|
|
859
|
-
|
|
1385
|
+
let result;
|
|
1386
|
+
try {
|
|
1387
|
+
result = createHandlers(initialData, component, ctx, modules);
|
|
1388
|
+
} catch (execError) {
|
|
1389
|
+
throw execError;
|
|
1390
|
+
}
|
|
860
1391
|
|
|
861
1392
|
Object.assign(handlers, result.handlers || {});
|
|
862
1393
|
effects.push(...(result.effects || []));
|
|
@@ -887,7 +1418,8 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
|
|
|
887
1418
|
setInHandler: result.setInHandler || (() => { })
|
|
888
1419
|
};
|
|
889
1420
|
} catch (error) {
|
|
890
|
-
//
|
|
1421
|
+
// Log the error for debugging
|
|
1422
|
+
console.error("[DBI-Svelte] createHandlerContext failed:", error);
|
|
891
1423
|
}
|
|
892
1424
|
|
|
893
1425
|
// Function to run all effects (fallback)
|