@mostfeatured/dbi 0.2.14 → 0.2.16

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 (50) hide show
  1. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts +35 -1
  2. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts.map +1 -1
  3. package/dist/src/types/Components/HTMLComponentsV2/index.js +416 -83
  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 +28 -1
  10. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.d.ts.map +1 -1
  11. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js +478 -34
  12. package/dist/src/types/Components/HTMLComponentsV2/svelteParser.js.map +1 -1
  13. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts +12 -0
  14. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.d.ts.map +1 -1
  15. package/dist/src/types/Components/HTMLComponentsV2/svelteRenderer.js +102 -18
  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/dist/test/test.d.ts +2 -0
  20. package/dist/test/test.d.ts.map +1 -0
  21. package/dist/test/test.js +7 -0
  22. package/dist/test/test.js.map +1 -0
  23. package/docs/ADVANCED_FEATURES.md +4 -0
  24. package/docs/API_REFERENCE.md +4 -0
  25. package/docs/CHAT_INPUT.md +4 -0
  26. package/docs/COMPONENTS.md +4 -0
  27. package/docs/EVENTS.md +4 -0
  28. package/docs/GETTING_STARTED.md +4 -0
  29. package/docs/LOCALIZATION.md +4 -0
  30. package/docs/README.md +4 -0
  31. package/docs/SVELTE_COMPONENTS.md +162 -6
  32. package/docs/llm/ADVANCED_FEATURES.txt +521 -0
  33. package/docs/llm/API_REFERENCE.txt +659 -0
  34. package/docs/llm/CHAT_INPUT.txt +514 -0
  35. package/docs/llm/COMPONENTS.txt +595 -0
  36. package/docs/llm/EVENTS.txt +449 -0
  37. package/docs/llm/GETTING_STARTED.txt +296 -0
  38. package/docs/llm/LOCALIZATION.txt +501 -0
  39. package/docs/llm/README.txt +193 -0
  40. package/docs/llm/SVELTE_COMPONENTS.txt +566 -0
  41. package/generated/svelte-dbi.d.ts +122 -0
  42. package/package.json +1 -1
  43. package/src/types/Components/HTMLComponentsV2/index.ts +478 -95
  44. package/src/types/Components/HTMLComponentsV2/parser.ts +317 -0
  45. package/src/types/Components/HTMLComponentsV2/svelteParser.ts +536 -35
  46. package/src/types/Components/HTMLComponentsV2/svelteRenderer.ts +121 -20
  47. package/test/index.ts +76 -3
  48. package/test/product-showcase.svelte +383 -24
  49. package/test/test.ts +3 -0
  50. 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[];
@@ -352,14 +705,19 @@ function parseImports(script: string): { imports: ImportInfo[]; cleanedScript: s
352
705
  /**
353
706
  * Load modules and create injection variables
354
707
  */
355
- function loadModules(imports: ImportInfo[]): { modules: Record<string, any>; varDeclarations: string } {
708
+ function loadModules(imports: ImportInfo[], sourceDir?: string): { modules: Record<string, any>; varDeclarations: string } {
356
709
  const modules: Record<string, any> = {};
357
710
  const declarations: string[] = [];
711
+ const path = require("path");
358
712
 
359
713
  for (const importInfo of imports) {
360
714
  try {
715
+ // Resolve relative paths from source file directory
716
+ const resolvedPath = importInfo.moduleName.startsWith('.') && sourceDir
717
+ ? path.resolve(sourceDir, importInfo.moduleName)
718
+ : importInfo.moduleName;
361
719
  // Try to require the module
362
- const mod = require(importInfo.moduleName);
720
+ const mod = require(resolvedPath);
363
721
 
364
722
  if (importInfo.isDefault && importInfo.defaultName) {
365
723
  // Default or namespace import
@@ -384,15 +742,16 @@ function loadModules(imports: ImportInfo[]): { modules: Record<string, any>; var
384
742
  /**
385
743
  * Create a handler context from script content
386
744
  * This evaluates the Svelte script and returns the handler functions and effects
745
+ * @param sourceDir - The directory of the source file (used for resolving relative imports)
387
746
  */
388
- export function createHandlerContext(scriptContent: string, initialData: Record<string, any> = {}, component?: any, ctx?: any): HandlerContextResult {
747
+ export function createHandlerContext(scriptContent: string, initialData: Record<string, any> = {}, component?: any, ctx?: any, sourceDir?: string): HandlerContextResult {
389
748
  const handlers: Record<string, Function> = {};
390
749
  const effects: Function[] = [];
391
750
 
392
751
  try {
393
752
  // Parse and extract imports first
394
753
  const { imports, cleanedScript } = parseImports(scriptContent);
395
- const { modules, varDeclarations } = loadModules(imports);
754
+ const { modules, varDeclarations } = loadModules(imports, sourceDir);
396
755
 
397
756
  // Extract only function declarations from the script
398
757
  const functionNames = extractFunctionNames(cleanedScript);
@@ -405,7 +764,15 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
405
764
  // 2. Remove $props destructuring
406
765
  // 3. Convert $effect to __registerEffect__
407
766
  // 4. Keep only function declarations
408
- let processedScript = cleanedScript
767
+
768
+ // First, remove comments from the script to avoid regex issues with braces in comments
769
+ let scriptWithoutComments = cleanedScript
770
+ // Remove single-line comments (but preserve the newline)
771
+ .replace(/\/\/.*$/gm, '')
772
+ // Remove multi-line comments
773
+ .replace(/\/\*[\s\S]*?\*\//g, '');
774
+
775
+ let processedScript = scriptWithoutComments
409
776
  // Remove $state declarations completely or make them var
410
777
  .replace(/let\s+(\w+)\s*=\s*\$state\(([^)]*)\);?/g, 'var $1 = $2;')
411
778
  // Remove $derived declarations but keep the value
@@ -413,31 +780,46 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
413
780
  // Convert $effect calls to __registerEffect__ calls
414
781
  .replace(/\$effect\s*\(\s*((?:function\s*\([^)]*\)|\([^)]*\)\s*=>|\(\)\s*=>)[^}]*\{[\s\S]*?\}\s*)\);?/g, '__registerEffect__($1);')
415
782
  // 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};`;
783
+ .replace(/\$effect\s*\(\s*\(\)\s*=>\s*\{([\s\S]*?)\}\s*\);?/g, '__registerEffect__(function() {$1});');
784
+
785
+ // Handle $props destructuring with proper brace counting (supports nested objects like { options = { a: 1 } })
786
+ processedScript = processedScript.replace(/let\s+\{([\s\S]*?)\}\s*=\s*\$props\(\);?/g, (match) => {
787
+ // Find the opening brace after 'let'
788
+ const letIndex = match.indexOf('{');
789
+ if (letIndex === -1) return match;
790
+
791
+ // Use brace counting to find the matching closing brace
792
+ let braceCount = 0;
793
+ let startIndex = letIndex;
794
+ let endIndex = -1;
795
+
796
+ for (let i = startIndex; i < match.length; i++) {
797
+ if (match[i] === '{') braceCount++;
798
+ else if (match[i] === '}') {
799
+ braceCount--;
800
+ if (braceCount === 0) {
801
+ endIndex = i;
802
+ break;
437
803
  }
438
- return `var ${v} = data.${v};`;
439
- }).filter(Boolean).join('\n');
440
- });
804
+ }
805
+ }
806
+
807
+ if (endIndex === -1) return match;
808
+
809
+ // Extract the content between braces
810
+ const content = match.substring(startIndex + 1, endIndex);
811
+
812
+ // Parse props with proper handling of nested braces
813
+ const props = parsePropsContent(content);
814
+
815
+ return props.map(prop => {
816
+ if (!prop.name || prop.name === 'data') return '';
817
+ if (prop.defaultValue !== undefined) {
818
+ return `var ${prop.name} = data.${prop.name} ?? ${prop.defaultValue};`;
819
+ }
820
+ return `var ${prop.name} = data.${prop.name};`;
821
+ }).filter(Boolean).join('\n');
822
+ });
441
823
 
442
824
  // Add module variable declarations at the beginning of processed script
443
825
  if (varDeclarations) {
@@ -488,6 +870,102 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
488
870
  __destroyCallbacks__.push(fn);
489
871
  }
490
872
 
873
+ // Modal store - stores rendered modal definitions
874
+ var __modals__ = new Map();
875
+
876
+ // Register a modal (called internally when modals are parsed)
877
+ function __registerModal__(modalId, modalDef) {
878
+ __modals__.set(modalId, modalDef);
879
+ }
880
+
881
+ // Show a modal by ID and return a Promise that resolves with the submitted fields
882
+ // Modal definitions are rendered from <components type="modal" id="xxx">
883
+ // Usage:
884
+ // await showModal("edit-product"); // Just show, use onsubmit handler
885
+ // const { fields, interaction } = await showModal("edit-product"); // Get response
886
+ // await showModal("edit-product", { timeout: 60000 }); // 60 second timeout
887
+ // await showModal("edit-product", { timeout: 0 }); // No timeout (unlimited)
888
+ function showModal(modalId, options) {
889
+ if (!__ctx__ || !__ctx__.interaction) {
890
+ return Promise.reject(new Error("showModal requires an interaction context"));
891
+ }
892
+
893
+ // Default timeout: 10 minutes (600000ms), 0 = no timeout
894
+ var timeout = (options && typeof options.timeout === 'number') ? options.timeout : 600000;
895
+
896
+ // Disable auto-render IMMEDIATELY since we're showing a modal
897
+ // This prevents flushRender from trying to update after modal is shown
898
+ __autoRenderEnabled__ = false;
899
+ __asyncInteractionCalled__ = true;
900
+
901
+ // Create a promise that will be resolved when modal is submitted
902
+ return new Promise(async function(resolve, reject) {
903
+ var timeoutId = null;
904
+ var modal = null;
905
+
906
+ try {
907
+ // Re-render to get latest modal definitions (reactive)
908
+ var renderResult = await __component__.toJSON({ data: __data__ });
909
+
910
+ // Get modals from the render result stored on component
911
+ var modals = __component__.__lastRenderModals__;
912
+ if (!modals) {
913
+ reject(new Error("No modals defined in this component. Use <components type=\\"modal\\" id=\\"...\\">"));
914
+ return;
915
+ }
916
+
917
+ modal = modals.get(modalId);
918
+ if (!modal) {
919
+ reject(new Error("Modal '" + modalId + "' not found. Available modals: " + Array.from(modals.keys()).join(", ")));
920
+ return;
921
+ }
922
+
923
+ // Wrapped resolve/reject to clear timeout
924
+ var wrappedResolve = function(result) {
925
+ if (timeoutId) clearTimeout(timeoutId);
926
+ resolve(result);
927
+ };
928
+ var wrappedReject = function(error) {
929
+ if (timeoutId) clearTimeout(timeoutId);
930
+ reject(error);
931
+ };
932
+
933
+ // Store the promise resolver in component's pending modals map
934
+ // This will be resolved when modal submit interaction is received
935
+ __component__._pendingModals.set(modal.customId, { resolve: wrappedResolve, reject: wrappedReject });
936
+
937
+ // Set up timeout if enabled (timeout > 0)
938
+ if (timeout > 0) {
939
+ timeoutId = setTimeout(function() {
940
+ // Clean up pending modal
941
+ __component__._pendingModals.delete(modal.customId);
942
+ reject(new Error("Modal '" + modalId + "' timed out after " + timeout + "ms"));
943
+ }, timeout);
944
+ }
945
+
946
+ // Show the modal using the interaction
947
+ var i = __ctx__.interaction;
948
+
949
+ // Modal must be shown immediately - no deferral allowed
950
+ await i.showModal({
951
+ title: modal.title,
952
+ customId: modal.customId,
953
+ components: modal.components
954
+ });
955
+
956
+ // Note: Promise resolves later when modal is submitted
957
+ // If you don't await for the response, the promise just hangs (which is fine)
958
+ } catch (err) {
959
+ // Clean up pending modal and timeout if show failed
960
+ if (timeoutId) clearTimeout(timeoutId);
961
+ if (modal && modal.customId) {
962
+ __component__._pendingModals.delete(modal.customId);
963
+ }
964
+ reject(new Error("Failed to show modal: " + (err.message || err)));
965
+ }
966
+ });
967
+ }
968
+
491
969
  // Rate-limit aware edit function with retry
492
970
  function __safeEdit__(editFn, retryCount) {
493
971
  retryCount = retryCount || 0;
@@ -610,6 +1088,9 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
610
1088
  // Track if we're inside a handler execution
611
1089
  var __inHandlerExecution__ = false;
612
1090
 
1091
+ // Track pending low-priority updates (from intervals/timeouts)
1092
+ var __pendingLowPriorityRender__ = false;
1093
+
613
1094
  // Mark that we have pending data changes
614
1095
  function __markDataChanged__() {
615
1096
  __hasDataChanges__ = true;
@@ -622,6 +1103,20 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
622
1103
  }
623
1104
  }
624
1105
 
1106
+ // Low-priority update - used for background tasks like intervals
1107
+ // If a handler is currently running, skip this update (the handler's update will include our changes)
1108
+ function lowPriorityUpdate(callback) {
1109
+ if (__inHandlerExecution__) {
1110
+ // Handler is running - just update data, skip render
1111
+ // The handler will render when it finishes
1112
+ if (callback) callback();
1113
+ return;
1114
+ }
1115
+
1116
+ // No handler running - proceed with update
1117
+ if (callback) callback();
1118
+ }
1119
+
625
1120
  // Flush pending render (called after interaction methods complete)
626
1121
  function __flushRender__() {
627
1122
  if (__hasDataChanges__ && __autoRenderEnabled__) {
@@ -893,7 +1388,12 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
893
1388
  const createHandlers = factoryFunc(console);
894
1389
 
895
1390
  // Execute with the actual data, component, ctx, and imported modules to get handlers with proper closure
896
- const result = createHandlers(initialData, component, ctx, modules);
1391
+ let result;
1392
+ try {
1393
+ result = createHandlers(initialData, component, ctx, modules);
1394
+ } catch (execError) {
1395
+ throw execError;
1396
+ }
897
1397
 
898
1398
  Object.assign(handlers, result.handlers || {});
899
1399
  effects.push(...(result.effects || []));
@@ -924,7 +1424,8 @@ export function createHandlerContext(scriptContent: string, initialData: Record<
924
1424
  setInHandler: result.setInHandler || (() => { })
925
1425
  };
926
1426
  } catch (error) {
927
- // Silently fail and return fallback
1427
+ // Log the error for debugging
1428
+ console.error("[DBI-Svelte] createHandlerContext failed:", error);
928
1429
  }
929
1430
 
930
1431
  // Function to run all effects (fallback)