@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.
Files changed (45) hide show
  1. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts +33 -1
  2. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts.map +1 -1
  3. package/dist/src/types/Components/HTMLComponentsV2/index.js +408 -82
  4. package/dist/src/types/Components/HTMLComponentsV2/index.js.map +1 -1
  5. package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts +52 -0
  6. package/dist/src/types/Components/HTMLComponentsV2/parser.d.ts.map +1 -1
  7. package/dist/src/types/Components/HTMLComponentsV2/parser.js +275 -0
  8. package/dist/src/types/Components/HTMLComponentsV2/parser.js.map +1 -1
  9. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts +26 -0
  10. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts.map +1 -1
  11. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js +509 -34
  12. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js.map +1 -1
  13. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts +10 -0
  14. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts.map +1 -1
  15. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js +76 -11
  16. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js.map +1 -1
  17. package/dist/test/index.js +76 -3
  18. package/dist/test/index.js.map +1 -1
  19. package/docs/ADVANCED_FEATURES.md +4 -0
  20. package/docs/API_REFERENCE.md +4 -0
  21. package/docs/CHAT_INPUT.md +4 -0
  22. package/docs/COMPONENTS.md +4 -0
  23. package/docs/EVENTS.md +4 -0
  24. package/docs/GETTING_STARTED.md +4 -0
  25. package/docs/LOCALIZATION.md +4 -0
  26. package/docs/README.md +4 -0
  27. package/docs/SVELTE_COMPONENTS.md +162 -6
  28. package/docs/llm/ADVANCED_FEATURES.txt +521 -0
  29. package/docs/llm/API_REFERENCE.txt +659 -0
  30. package/docs/llm/CHAT_INPUT.txt +514 -0
  31. package/docs/llm/COMPONENTS.txt +595 -0
  32. package/docs/llm/EVENTS.txt +449 -0
  33. package/docs/llm/GETTING_STARTED.txt +296 -0
  34. package/docs/llm/LOCALIZATION.txt +501 -0
  35. package/docs/llm/README.txt +193 -0
  36. package/docs/llm/SVELTE_COMPONENTS.txt +566 -0
  37. package/generated/svelte-dbi.d.ts +122 -0
  38. package/package.json +1 -1
  39. package/src/types/Components/HTMLComponentsV2/index.ts +466 -94
  40. package/src/types/Components/HTMLComponentsV2/parser.ts +317 -0
  41. package/src/types/Components/HTMLComponentsV2/svelteParser.ts +567 -35
  42. package/src/types/Components/HTMLComponentsV2/svelteRenderer.ts +91 -13
  43. package/test/index.ts +76 -3
  44. package/test/product-showcase.svelte +380 -24
  45. 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
- const ast = _parse(source);
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
- let processedScript = cleanedScript
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
- // Replace $props destructuring with data access (handles default values)
418
- .replace(/let\s+\{\s*([^}]+)\s*\}\s*=\s*\$props\(\);?/g, (match, vars) => {
419
- return vars.split(',').map((v: string) => {
420
- v = v.trim();
421
- // Skip empty strings and comments
422
- if (!v || v.startsWith('//')) return '';
423
- // Remove inline comments from the variable definition
424
- v = v.replace(/\/\/.*$/, '').trim();
425
- if (!v) return '';
426
- // Skip 'data' prop as it's already defined in the wrapper
427
- if (v === 'data') return '';
428
- // Check if there's a default value: varName = defaultValue
429
- const defaultMatch = v.match(/^(\w+)\s*=\s*(.+)$/);
430
- if (defaultMatch) {
431
- const [, varName, defaultValue] = defaultMatch;
432
- // Skip 'data' prop even with default value
433
- if (varName === 'data') return '';
434
- // Clean default value from trailing comments
435
- const cleanDefault = defaultValue.replace(/\/\/.*$/, '').trim();
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
- return `var ${v} = data.${v};`;
439
- }).filter(Boolean).join('\n');
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
- var value = target[prop];
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
- return target[prop];
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
- const result = createHandlers(initialData, component, ctx, modules);
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
- // Silently fail and return fallback
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)