@mostfeatured/dbi 0.2.16 → 0.2.18

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 (81) hide show
  1. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts +4 -0
  2. package/dist/src/types/Components/HTMLComponentsV2/index.d.ts.map +1 -1
  3. package/dist/src/types/Components/HTMLComponentsV2/index.js +40 -5
  4. package/dist/src/types/Components/HTMLComponentsV2/index.js.map +1 -1
  5. package/dist/src/types/Event.d.ts +21 -13
  6. package/dist/src/types/Event.d.ts.map +1 -1
  7. package/dist/src/types/Event.js.map +1 -1
  8. package/dist/test/index.js +1 -1
  9. package/dist/test/index.js.map +1 -1
  10. package/generated/namespaceData.d.ts +3 -1
  11. package/package.json +6 -2
  12. package/.gitattributes +0 -2
  13. package/.hintrc +0 -8
  14. package/.vscode/settings.json +0 -3
  15. package/docs/ADVANCED_FEATURES.md +0 -840
  16. package/docs/API_REFERENCE.md +0 -929
  17. package/docs/CHAT_INPUT.md +0 -811
  18. package/docs/COMPONENTS.md +0 -1039
  19. package/docs/EVENTS.md +0 -568
  20. package/docs/GETTING_STARTED.md +0 -398
  21. package/docs/LOCALIZATION.md +0 -777
  22. package/docs/README.md +0 -345
  23. package/docs/SVELTE_COMPONENTS.md +0 -1111
  24. package/docs/llm/ADVANCED_FEATURES.txt +0 -521
  25. package/docs/llm/API_REFERENCE.txt +0 -659
  26. package/docs/llm/CHAT_INPUT.txt +0 -514
  27. package/docs/llm/COMPONENTS.txt +0 -595
  28. package/docs/llm/EVENTS.txt +0 -449
  29. package/docs/llm/GETTING_STARTED.txt +0 -296
  30. package/docs/llm/LOCALIZATION.txt +0 -501
  31. package/docs/llm/README.txt +0 -193
  32. package/docs/llm/SVELTE_COMPONENTS.txt +0 -566
  33. package/src/DBI.ts +0 -1007
  34. package/src/Events.ts +0 -189
  35. package/src/data/eventMap.json +0 -248
  36. package/src/index.ts +0 -23
  37. package/src/methods/handleMessageCommands.ts +0 -482
  38. package/src/methods/hookEventListeners.ts +0 -119
  39. package/src/methods/hookInteractionListeners.ts +0 -314
  40. package/src/methods/publishInteractions.ts +0 -256
  41. package/src/types/ApplicationRoleConnectionMetadata.ts +0 -19
  42. package/src/types/Builders/ButtonBuilder.ts +0 -53
  43. package/src/types/Builders/ChannelSelectMenuBuilder.ts +0 -53
  44. package/src/types/Builders/MentionableSelectMenuBuilder.ts +0 -53
  45. package/src/types/Builders/ModalBuilder.ts +0 -53
  46. package/src/types/Builders/RoleSelectMenuBuilder.ts +0 -53
  47. package/src/types/Builders/StringSelectMenuBuilder.ts +0 -53
  48. package/src/types/Builders/UserSelectMenuBuilder.ts +0 -53
  49. package/src/types/ChatInput/ChatInput.ts +0 -28
  50. package/src/types/ChatInput/ChatInputOptions.ts +0 -388
  51. package/src/types/Components/Button.ts +0 -39
  52. package/src/types/Components/ChannelSelectMenu.ts +0 -43
  53. package/src/types/Components/HTMLComponentsV2/HTMLComponentsV2Handlers.ts +0 -78
  54. package/src/types/Components/HTMLComponentsV2/index.ts +0 -761
  55. package/src/types/Components/HTMLComponentsV2/parser.ts +0 -649
  56. package/src/types/Components/HTMLComponentsV2/svelteParser.ts +0 -1503
  57. package/src/types/Components/HTMLComponentsV2/svelteRenderer.ts +0 -416
  58. package/src/types/Components/MentionableSelectMenu.ts +0 -43
  59. package/src/types/Components/Modal.ts +0 -46
  60. package/src/types/Components/RoleSelectMenu.ts +0 -43
  61. package/src/types/Components/StringSelectMenu.ts +0 -43
  62. package/src/types/Components/UserSelectMenu.ts +0 -43
  63. package/src/types/Event.ts +0 -145
  64. package/src/types/Interaction.ts +0 -100
  65. package/src/types/other/CustomEvent.ts +0 -19
  66. package/src/types/other/FakeMessageInteraction.ts +0 -408
  67. package/src/types/other/InteractionLocale.ts +0 -34
  68. package/src/types/other/Locale.ts +0 -70
  69. package/src/types/other/MessageContextMenu.ts +0 -27
  70. package/src/types/other/UserContextMenu.ts +0 -25
  71. package/src/utils/MemoryStore.ts +0 -28
  72. package/src/utils/UtilTypes.ts +0 -11
  73. package/src/utils/customId.ts +0 -49
  74. package/src/utils/permissions.ts +0 -5
  75. package/src/utils/recursiveImport.ts +0 -35
  76. package/src/utils/recursiveUnload.ts +0 -25
  77. package/src/utils/unloadModule.ts +0 -7
  78. package/test/index.ts +0 -176
  79. package/test/product-showcase.svelte +0 -558
  80. package/test/test.ts +0 -3
  81. package/tsconfig.json +0 -51
@@ -1,1503 +0,0 @@
1
- import * as stuffs from "stuffs";
2
-
3
- // Lazy imports to avoid issues with package managers that don't properly hoist dependencies
4
- let _parse: typeof import("svelte/compiler").parse;
5
-
6
- async function ensureImports() {
7
- if (!_parse) {
8
- const svelteCompiler = await import("svelte/compiler");
9
- _parse = svelteCompiler.parse;
10
- }
11
- }
12
-
13
- /**
14
- * Simple AST walker for Svelte AST nodes
15
- */
16
- function walkSvelteAst(node: any, callback: (node: any) => void) {
17
- if (!node || typeof node !== 'object') return;
18
-
19
- callback(node);
20
-
21
- // Walk children based on node type
22
- if (node.children && Array.isArray(node.children)) {
23
- for (const child of node.children) {
24
- walkSvelteAst(child, callback);
25
- }
26
- }
27
- if (node.fragment && node.fragment.nodes) {
28
- for (const child of node.fragment.nodes) {
29
- walkSvelteAst(child, callback);
30
- }
31
- }
32
- if (node.nodes && Array.isArray(node.nodes)) {
33
- for (const child of node.nodes) {
34
- walkSvelteAst(child, callback);
35
- }
36
- }
37
- // Handle other potential child properties
38
- if (node.else) {
39
- walkSvelteAst(node.else, callback);
40
- }
41
- if (node.consequent) {
42
- walkSvelteAst(node.consequent, callback);
43
- }
44
- if (node.alternate) {
45
- walkSvelteAst(node.alternate, callback);
46
- }
47
- if (node.then) {
48
- walkSvelteAst(node.then, callback);
49
- }
50
- if (node.catch) {
51
- walkSvelteAst(node.catch, callback);
52
- }
53
- if (node.body) {
54
- if (Array.isArray(node.body)) {
55
- for (const child of node.body) {
56
- walkSvelteAst(child, callback);
57
- }
58
- } else {
59
- walkSvelteAst(node.body, callback);
60
- }
61
- }
62
- }
63
-
64
- export interface SvelteHandlerInfo {
65
- name: string;
66
- handlerName: string;
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;
80
- }
81
-
82
- export interface SvelteComponentInfo {
83
- handlers: Map<string, SvelteHandlerInfo>;
84
- /** Modal onsubmit handlers keyed by modal id */
85
- modalHandlers: Map<string, ModalHandlerInfo>;
86
- scriptContent: string;
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[];
94
- }
95
-
96
- /**
97
- * Parse a Svelte component and extract event handlers
98
- * Also injects auto-generated names into elements that have handlers but no name
99
- */
100
- export async function parseSvelteComponent(source: string, data?: Record<string, any>): Promise<SvelteComponentInfo> {
101
- await ensureImports();
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
-
136
- const handlers = new Map<string, SvelteHandlerInfo>();
137
- const modalHandlers = new Map<string, ModalHandlerInfo>();
138
- let scriptContent = "";
139
-
140
- // Extract script content
141
- if (ast.instance) {
142
- scriptContent = source.substring(ast.instance.content.start, ast.instance.content.end);
143
- }
144
-
145
- // Track elements that need auto-generated names (node -> name mapping)
146
- // We'll inject these into the source after the walk
147
- const elementsNeedingNames: Array<{ node: any; name: string; handlerName: string; eventType: string; element: string }> = [];
148
- let autoNameCounter = 0;
149
-
150
- // Walk through HTML nodes to find event handlers
151
- walkSvelteAst(ast.html || ast.fragment, (node: any) => {
152
- if (node.type === "Element" || node.type === "InlineComponent" || node.type === "RegularElement" || node.type === "Component") {
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
- }
210
-
211
- // Find name attribute
212
- const nameAttr = attributes.find((attr: any) =>
213
- attr.type === "Attribute" && attr.name === "name"
214
- );
215
-
216
- // Check if element has an onclick/onchange/handler and get the handler info
217
- let foundHandler: { eventType: string; handlerName: string } | null = null;
218
-
219
- for (const attr of attributes) {
220
- const isEventHandler = attr.type === "EventHandler";
221
- const isOnAttribute = attr.type === "Attribute" && attr.name && attr.name.startsWith("on");
222
- const isHandlerAttribute = attr.type === "Attribute" && attr.name === "handler";
223
-
224
- if (isEventHandler || isOnAttribute || isHandlerAttribute) {
225
- // For "handler" attribute, use the element type to determine eventType
226
- // button -> onclick, select -> onchange
227
- let eventType = attr.name;
228
- if (isHandlerAttribute) {
229
- const elementName = node.name.toLowerCase();
230
- if (elementName === "button") {
231
- eventType = "onclick";
232
- } else if (elementName.includes("select")) {
233
- eventType = "onchange";
234
- } else {
235
- eventType = "handler"; // fallback
236
- }
237
- }
238
- let handlerName = "";
239
-
240
- if (attr.type === "Attribute" && Array.isArray(attr.value)) {
241
- const exprValue = attr.value.find((v: any) => v.type === "ExpressionTag" || v.type === "MustacheTag");
242
- if (exprValue && exprValue.expression) {
243
- if (exprValue.expression.type === "Identifier") {
244
- handlerName = exprValue.expression.name;
245
- } else if (exprValue.expression.type === "CallExpression" && exprValue.expression.callee) {
246
- handlerName = exprValue.expression.callee.name;
247
- }
248
- }
249
- } else if (attr.expression) {
250
- if (attr.expression.type === "Identifier") {
251
- handlerName = attr.expression.name;
252
- } else if (attr.expression.type === "CallExpression" && attr.expression.callee) {
253
- handlerName = attr.expression.callee.name;
254
- } else if (attr.expression.type === "MemberExpression") {
255
- handlerName = extractMemberExpressionName(attr.expression);
256
- }
257
- }
258
-
259
- if (handlerName) {
260
- foundHandler = { eventType, handlerName };
261
- break;
262
- }
263
- }
264
- }
265
-
266
- if (!foundHandler) return; // No handler found, skip
267
-
268
- let componentName: string;
269
- if (nameAttr) {
270
- componentName = getAttributeValue(nameAttr);
271
- } else {
272
- // No name attribute - generate a deterministic one based on position
273
- // Use the handler name and counter for deterministic naming
274
- const positionKey = `${node.name.toLowerCase()}_${autoNameCounter++}`;
275
-
276
- // If data is provided, use/store in $autoNames for persistence across re-renders
277
- if (data) {
278
- if (!data.$autoNames) {
279
- data.$autoNames = {};
280
- }
281
- if (!data.$autoNames[positionKey]) {
282
- data.$autoNames[positionKey] = `__auto_${positionKey}`;
283
- }
284
- componentName = data.$autoNames[positionKey];
285
- } else {
286
- // No data - use deterministic name based on position
287
- componentName = `__auto_${positionKey}`;
288
- }
289
-
290
- // Track this element for source injection
291
- elementsNeedingNames.push({
292
- node,
293
- name: componentName,
294
- handlerName: foundHandler.handlerName,
295
- eventType: foundHandler.eventType,
296
- element: node.name.toLowerCase()
297
- });
298
- }
299
-
300
- // Add to handlers map
301
- handlers.set(componentName, {
302
- name: componentName,
303
- handlerName: foundHandler.handlerName,
304
- eventType: foundHandler.eventType,
305
- element: node.name.toLowerCase(),
306
- });
307
- }
308
- });
309
-
310
- // Inject auto-generated names into the source
311
- // Sort by position descending so we don't mess up offsets
312
- let processedSource = source;
313
- const sortedElements = [...elementsNeedingNames].sort((a, b) => b.node.start - a.node.start);
314
-
315
- for (const { node, name } of sortedElements) {
316
- // Find the position right after the opening tag name
317
- // e.g., <button ...> -> insert after "button"
318
- const tagEnd = node.start + 1 + node.name.length; // +1 for '<'
319
- processedSource = processedSource.slice(0, tagEnd) + ` name="${name}"` + processedSource.slice(tagEnd);
320
- }
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
-
356
- return {
357
- handlers,
358
- modalHandlers,
359
- scriptContent,
360
- processedSource,
361
- declaredProps,
362
- propsWithDefaults,
363
- declaredFunctions
364
- };
365
- }
366
-
367
- /**
368
- * Extract the full name from a MemberExpression (e.g., obj.method)
369
- */
370
- function extractMemberExpressionName(expr: any): string {
371
- if (expr.type === "Identifier") {
372
- return expr.name;
373
- }
374
- if (expr.type === "MemberExpression") {
375
- const object = extractMemberExpressionName(expr.object);
376
- const property = expr.property.name || expr.property.value;
377
- return `${object}.${property}`;
378
- }
379
- return "";
380
- }
381
-
382
- /**
383
- * Get the value from an attribute
384
- */
385
- function getAttributeValue(attr: any): string {
386
- if (!attr.value) return "";
387
-
388
- if (Array.isArray(attr.value)) {
389
- // Static text value
390
- if (attr.value[0]?.type === "Text") {
391
- return attr.value[0].data;
392
- }
393
- // Expression value
394
- if (attr.value[0]?.expression) {
395
- return extractExpressionValue(attr.value[0].expression);
396
- }
397
- }
398
-
399
- return "";
400
- }
401
-
402
- /**
403
- * Extract value from an expression
404
- */
405
- function extractExpressionValue(expr: any): string {
406
- if (expr.type === "Identifier") {
407
- return expr.name;
408
- }
409
- if (expr.type === "Literal") {
410
- return String(expr.value);
411
- }
412
- return "";
413
- }
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
-
621
- export interface HandlerContextResult {
622
- handlers: Record<string, Function>;
623
- effects: Function[];
624
- runEffects: () => void;
625
- hasPendingRender: () => boolean;
626
- flushRender: () => Promise<void>;
627
- wrappedCtx: any; // Proxy-wrapped ctx for passing to handlers
628
- // Lifecycle hooks
629
- mountCallbacks: Function[];
630
- destroyCallbacks: Function[];
631
- runMount: () => void;
632
- runDestroy: () => void;
633
- // Handler execution tracking
634
- setInHandler: (value: boolean) => void;
635
- }
636
-
637
- /**
638
- * Parse import statements from script and extract module info
639
- */
640
- interface ImportInfo {
641
- moduleName: string;
642
- imports: { name: string; alias?: string }[];
643
- isDefault: boolean;
644
- defaultName?: string;
645
- }
646
-
647
- function parseImports(script: string): { imports: ImportInfo[]; cleanedScript: string } {
648
- const imports: ImportInfo[] = [];
649
-
650
- // Match: import { a, b as c } from "module"
651
- // Match: import name from "module"
652
- // Match: import * as name from "module"
653
- const importRegex = /import\s+(?:(\w+)\s*,?\s*)?(?:\{\s*([^}]+)\s*\})?(?:\*\s+as\s+(\w+))?\s+from\s+["']([^"']+)["'];?/g;
654
-
655
- let cleanedScript = script;
656
- let match;
657
-
658
- while ((match = importRegex.exec(script)) !== null) {
659
- const [fullMatch, defaultImport, namedImports, namespaceImport, moduleName] = match;
660
-
661
- // Skip svelte internal imports - we provide these ourselves
662
- if (moduleName === 'svelte' || moduleName.startsWith('svelte/')) {
663
- cleanedScript = cleanedScript.replace(fullMatch, '');
664
- continue;
665
- }
666
-
667
- const importInfo: ImportInfo = {
668
- moduleName,
669
- imports: [],
670
- isDefault: false
671
- };
672
-
673
- // Default import: import name from "module"
674
- if (defaultImport) {
675
- importInfo.isDefault = true;
676
- importInfo.defaultName = defaultImport;
677
- }
678
-
679
- // Namespace import: import * as name from "module"
680
- if (namespaceImport) {
681
- importInfo.isDefault = true;
682
- importInfo.defaultName = namespaceImport;
683
- }
684
-
685
- // Named imports: import { a, b as c } from "module"
686
- if (namedImports) {
687
- const parts = namedImports.split(',').map(s => s.trim()).filter(Boolean);
688
- for (const part of parts) {
689
- const aliasMatch = part.match(/^(\w+)\s+as\s+(\w+)$/);
690
- if (aliasMatch) {
691
- importInfo.imports.push({ name: aliasMatch[1], alias: aliasMatch[2] });
692
- } else {
693
- importInfo.imports.push({ name: part });
694
- }
695
- }
696
- }
697
-
698
- imports.push(importInfo);
699
- cleanedScript = cleanedScript.replace(fullMatch, '');
700
- }
701
-
702
- return { imports, cleanedScript };
703
- }
704
-
705
- /**
706
- * Load modules and create injection variables
707
- */
708
- function loadModules(imports: ImportInfo[], sourceDir?: string): { modules: Record<string, any>; varDeclarations: string } {
709
- const modules: Record<string, any> = {};
710
- const declarations: string[] = [];
711
- const path = require("path");
712
-
713
- for (const importInfo of imports) {
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;
719
- // Try to require the module
720
- const mod = require(resolvedPath);
721
-
722
- if (importInfo.isDefault && importInfo.defaultName) {
723
- // Default or namespace import
724
- modules[importInfo.defaultName] = mod.default || mod;
725
- declarations.push(`var ${importInfo.defaultName} = __modules__["${importInfo.defaultName}"];`);
726
- }
727
-
728
- // Named imports
729
- for (const imp of importInfo.imports) {
730
- const varName = imp.alias || imp.name;
731
- modules[varName] = mod[imp.name];
732
- declarations.push(`var ${varName} = __modules__["${varName}"];`);
733
- }
734
- } catch (err) {
735
- // Module import failed
736
- }
737
- }
738
-
739
- return { modules, varDeclarations: declarations.join('\n') };
740
- }
741
-
742
- /**
743
- * Create a handler context from script content
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)
746
- */
747
- export function createHandlerContext(scriptContent: string, initialData: Record<string, any> = {}, component?: any, ctx?: any, sourceDir?: string): HandlerContextResult {
748
- const handlers: Record<string, Function> = {};
749
- const effects: Function[] = [];
750
-
751
- try {
752
- // Parse and extract imports first
753
- const { imports, cleanedScript } = parseImports(scriptContent);
754
- const { modules, varDeclarations } = loadModules(imports, sourceDir);
755
-
756
- // Extract only function declarations from the script
757
- const functionNames = extractFunctionNames(cleanedScript);
758
-
759
- // Extract $effect calls and convert them to collectable functions
760
- const effectBodies = extractEffectBodies(cleanedScript);
761
-
762
- // Process script to be safe for evaluation:
763
- // 1. Remove reactive declarations (let x = $state(...))
764
- // 2. Remove $props destructuring
765
- // 3. Convert $effect to __registerEffect__
766
- // 4. Keep only function declarations
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
776
- // Remove $state declarations completely or make them var
777
- .replace(/let\s+(\w+)\s*=\s*\$state\(([^)]*)\);?/g, 'var $1 = $2;')
778
- // Remove $derived declarations but keep the value
779
- .replace(/let\s+(\w+)\s*=\s*\$derived\(([^)]+)\);?/g, 'var $1 = $2;')
780
- // Convert $effect calls to __registerEffect__ calls
781
- .replace(/\$effect\s*\(\s*((?:function\s*\([^)]*\)|\([^)]*\)\s*=>|\(\)\s*=>)[^}]*\{[\s\S]*?\}\s*)\);?/g, '__registerEffect__($1);')
782
- // Simpler $effect pattern: $effect(() => { ... })
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;
803
- }
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
- });
823
-
824
- // Add module variable declarations at the beginning of processed script
825
- if (varDeclarations) {
826
- processedScript = varDeclarations + '\n\n' + processedScript;
827
- }
828
-
829
- // Wrap everything in an IIFE that takes data and component as parameters
830
- // This ensures data and 'this' are always available in the function scope
831
- // Also provides helper functions: render(), update() and rerender()
832
- // Data is wrapped in a Proxy for automatic reactivity
833
- // Interaction methods (reply, followUp, deferReply) are wrapped to auto-render after completion
834
- const wrappedScript = `
835
- return function(__data__, __component__, __ctx__, __modules__) {
836
- __modules__ = __modules__ || {};
837
- var self = __component__;
838
- var __effects__ = [];
839
- var __renderPending__ = false;
840
- var __autoRenderEnabled__ = true;
841
- var __hasDataChanges__ = false;
842
-
843
- // Lifecycle callbacks
844
- var __mountCallbacks__ = [];
845
- var __destroyCallbacks__ = [];
846
- var __isMounted__ = false;
847
-
848
- // Store last message reference for background updates (intervals, timeouts)
849
- var __lastMessage__ = __ctx__?.interaction?.message || null;
850
-
851
- // Throttle configuration
852
- var __throttleMinInterval__ = 250; // Minimum ms between renders
853
- var __lastRenderTime__ = 0;
854
- var __pendingRenderTimeout__ = null;
855
- var __isRateLimited__ = false;
856
- var __rateLimitEndTime__ = 0;
857
-
858
- function __registerEffect__(fn) {
859
- __effects__.push(fn);
860
- }
861
-
862
- // Lifecycle: onMount - called when component is first rendered
863
- // If callback returns a function, that function is called on destroy
864
- function onMount(fn) {
865
- __mountCallbacks__.push(fn);
866
- }
867
-
868
- // Lifecycle: onDestroy - called when ref is cleaned up
869
- function onDestroy(fn) {
870
- __destroyCallbacks__.push(fn);
871
- }
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
-
969
- // Rate-limit aware edit function with retry
970
- function __safeEdit__(editFn, retryCount) {
971
- retryCount = retryCount || 0;
972
- var maxRetries = 3;
973
-
974
- return editFn().catch(function(err) {
975
- // Check for rate limit (429)
976
- if (err.status === 429 || (err.message && err.message.includes('rate limit'))) {
977
- var retryAfter = err.retry_after || err.retryAfter || 1;
978
- __isRateLimited__ = true;
979
- __rateLimitEndTime__ = Date.now() + (retryAfter * 1000);
980
-
981
- return new Promise(function(resolve) {
982
- setTimeout(function() {
983
- __isRateLimited__ = false;
984
- if (retryCount < maxRetries) {
985
- resolve(__safeEdit__(editFn, retryCount + 1));
986
- } else {
987
- resolve();
988
- }
989
- }, retryAfter * 1000);
990
- });
991
- }
992
- return Promise.resolve();
993
- });
994
- }
995
-
996
- // Throttled render - ensures minimum interval between renders
997
- function __throttledRender__(immediate) {
998
- var now = Date.now();
999
- var timeSinceLastRender = now - __lastRenderTime__;
1000
- var waitTime = 0;
1001
-
1002
- // If rate limited, calculate wait time
1003
- if (__isRateLimited__ && __rateLimitEndTime__ > now) {
1004
- waitTime = Math.max(waitTime, __rateLimitEndTime__ - now);
1005
- }
1006
-
1007
- // If within throttle interval, schedule for later
1008
- if (!immediate && timeSinceLastRender < __throttleMinInterval__) {
1009
- waitTime = Math.max(waitTime, __throttleMinInterval__ - timeSinceLastRender);
1010
- }
1011
-
1012
- // Clear any pending render
1013
- if (__pendingRenderTimeout__) {
1014
- clearTimeout(__pendingRenderTimeout__);
1015
- __pendingRenderTimeout__ = null;
1016
- }
1017
-
1018
- if (waitTime > 0) {
1019
- return new Promise(function(resolve) {
1020
- __pendingRenderTimeout__ = setTimeout(function() {
1021
- __pendingRenderTimeout__ = null;
1022
- __lastRenderTime__ = Date.now();
1023
- resolve(__executeRender__());
1024
- }, waitTime);
1025
- });
1026
- }
1027
-
1028
- __lastRenderTime__ = now;
1029
- return __executeRender__();
1030
- }
1031
-
1032
- // Actual render execution
1033
- async function __executeRender__() {
1034
- var components = await __component__.toJSON({ data: __data__ });
1035
-
1036
- // Try to use current interaction if available
1037
- if (__ctx__ && __ctx__.interaction) {
1038
- try {
1039
- var i = __ctx__.interaction;
1040
-
1041
- // Update last message reference
1042
- if (i.message) {
1043
- __lastMessage__ = i.message;
1044
- }
1045
-
1046
- if (i.replied || i.deferred) {
1047
- // Already replied, use message.edit with rate limit handling
1048
- return __safeEdit__(function() {
1049
- return i.message.edit({
1050
- components: components,
1051
- flags: ["IsComponentsV2"],
1052
- });
1053
- });
1054
- } else {
1055
- // Not replied yet, use update with rate limit handling
1056
- return __safeEdit__(function() {
1057
- return i.update({
1058
- components: components,
1059
- flags: ["IsComponentsV2"],
1060
- });
1061
- });
1062
- }
1063
- } catch (err) {
1064
- // Silently fail
1065
- }
1066
- }
1067
-
1068
- // Fallback: Use last message reference (for intervals/timeouts outside interaction)
1069
- if (__lastMessage__) {
1070
- return __safeEdit__(function() {
1071
- return __lastMessage__.edit({
1072
- components: components,
1073
- flags: ["IsComponentsV2"],
1074
- });
1075
- });
1076
- }
1077
-
1078
- return Promise.resolve();
1079
- }
1080
-
1081
- // Helper: Auto-detect whether to use update() or rerender()
1082
- // If interaction was already replied/deferred, use message.edit
1083
- // Otherwise use interaction.update
1084
- function render(immediate) {
1085
- return __throttledRender__(immediate);
1086
- }
1087
-
1088
- // Track if we're inside a handler execution
1089
- var __inHandlerExecution__ = false;
1090
-
1091
- // Track pending low-priority updates (from intervals/timeouts)
1092
- var __pendingLowPriorityRender__ = false;
1093
-
1094
- // Mark that we have pending data changes
1095
- function __markDataChanged__() {
1096
- __hasDataChanges__ = true;
1097
-
1098
- // If we're NOT inside a handler (e.g., interval/timeout), trigger render immediately
1099
- // The throttle will prevent too many renders
1100
- if (!__inHandlerExecution__ && __autoRenderEnabled__ && __lastMessage__) {
1101
- __hasDataChanges__ = false;
1102
- __throttledRender__(false);
1103
- }
1104
- }
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
-
1120
- // Flush pending render (called after interaction methods complete)
1121
- function __flushRender__() {
1122
- if (__hasDataChanges__ && __autoRenderEnabled__) {
1123
- __hasDataChanges__ = false;
1124
- render();
1125
- }
1126
- }
1127
-
1128
- // Create reactive proxy for data
1129
- // Also properly forwards Object.keys/values/entries operations
1130
- function __createReactiveProxy__(target, path) {
1131
- if (typeof target !== 'object' || target === null) return target;
1132
-
1133
- return new Proxy(target, {
1134
- get: function(obj, prop) {
1135
- var value = obj[prop];
1136
- // Wrap nested objects in proxy too
1137
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1138
- return __createReactiveProxy__(value, path + '.' + String(prop));
1139
- }
1140
- return value;
1141
- },
1142
- set: function(obj, prop, value) {
1143
- var oldValue = obj[prop];
1144
- obj[prop] = value;
1145
- // Only mark as changed if value actually changed
1146
- if (oldValue !== value) {
1147
- __markDataChanged__();
1148
- }
1149
- return true;
1150
- },
1151
- // Forward Object.keys/values/entries operations
1152
- ownKeys: function(target) {
1153
- return Reflect.ownKeys(target);
1154
- },
1155
- getOwnPropertyDescriptor: function(target, prop) {
1156
- return Reflect.getOwnPropertyDescriptor(target, prop);
1157
- },
1158
- has: function(target, prop) {
1159
- return Reflect.has(target, prop);
1160
- }
1161
- });
1162
- }
1163
-
1164
- // Wrap data in reactive proxy
1165
- var data = __createReactiveProxy__(__data__, 'data');
1166
-
1167
- // Track if an async interaction method was called
1168
- var __asyncInteractionCalled__ = false;
1169
-
1170
- // Wrap interaction methods to auto-render after completion
1171
- var interaction = null;
1172
- var ctx = null;
1173
-
1174
- if (__ctx__ && __ctx__.interaction) {
1175
- var originalInteraction = __ctx__.interaction;
1176
-
1177
- // Create a proxy for interaction that wraps reply/followUp/deferReply
1178
- // Also properly forwards Object.keys/values/entries operations
1179
- // Uses Reflect.get to properly handle getters with correct 'this' binding
1180
- interaction = new Proxy(originalInteraction, {
1181
- get: function(target, prop, receiver) {
1182
- // Use Reflect.get to properly handle getters (like 'roles', 'users', etc.)
1183
- // This ensures 'this' context is correct for Discord.js Collection getters
1184
- var value = Reflect.get(target, prop, target);
1185
-
1186
- // Wrap methods that "consume" the interaction (reply, followUp, defer)
1187
- if (prop === 'reply' || prop === 'followUp' || prop === 'deferReply' || prop === 'deferUpdate') {
1188
- return function() {
1189
- // Mark that async interaction was called - this prevents sync flush at handler end
1190
- __asyncInteractionCalled__ = true;
1191
-
1192
- var result = value.apply(target, arguments);
1193
- // After the reply completes, flush any pending renders using throttled render
1194
- if (result && typeof result.then === 'function') {
1195
- result.then(function() {
1196
- if (__hasDataChanges__ && __autoRenderEnabled__) {
1197
- __hasDataChanges__ = false;
1198
- // Use throttled render which handles rate limits
1199
- __throttledRender__(false);
1200
- }
1201
- }).catch(function(err) {
1202
- // Silently fail
1203
- });
1204
- }
1205
- return result;
1206
- };
1207
- }
1208
-
1209
- // Wrap update method - this one renders directly, no need to flush after
1210
- if (prop === 'update') {
1211
- return function() {
1212
- __autoRenderEnabled__ = false; // Disable auto since we're doing manual update
1213
- __asyncInteractionCalled__ = true;
1214
- return value.apply(target, arguments);
1215
- };
1216
- }
1217
-
1218
- // Bind functions to original target
1219
- if (typeof value === 'function') {
1220
- return value.bind(target);
1221
- }
1222
-
1223
- return value;
1224
- },
1225
- // Forward Object.keys/values/entries operations
1226
- ownKeys: function(target) {
1227
- return Reflect.ownKeys(target);
1228
- },
1229
- getOwnPropertyDescriptor: function(target, prop) {
1230
- return Reflect.getOwnPropertyDescriptor(target, prop);
1231
- },
1232
- has: function(target, prop) {
1233
- return Reflect.has(target, prop);
1234
- }
1235
- });
1236
-
1237
- // Create wrapped ctx with the proxied interaction
1238
- // Also properly forwards Object.keys/values/entries operations
1239
- ctx = new Proxy(__ctx__, {
1240
- get: function(target, prop, receiver) {
1241
- if (prop === 'interaction') {
1242
- return interaction;
1243
- }
1244
- // Use Reflect.get for proper getter handling
1245
- return Reflect.get(target, prop, target);
1246
- },
1247
- // Forward Object.keys/values/entries operations
1248
- ownKeys: function(target) {
1249
- return Reflect.ownKeys(target);
1250
- },
1251
- getOwnPropertyDescriptor: function(target, prop) {
1252
- return Reflect.getOwnPropertyDescriptor(target, prop);
1253
- },
1254
- has: function(target, prop) {
1255
- return Reflect.has(target, prop);
1256
- }
1257
- });
1258
- }
1259
-
1260
- // Helper: Force update message using interaction.update (for button clicks without reply)
1261
- // Helper: Force update message using interaction.update (for button clicks without reply)
1262
- async function update() {
1263
- if (!__ctx__ || !__ctx__.interaction) {
1264
- return Promise.resolve();
1265
- }
1266
- __autoRenderEnabled__ = false; // Disable auto-render since manual update called
1267
- var components = await __component__.toJSON({ data: __data__ });
1268
- return __safeEdit__(function() {
1269
- return __ctx__.interaction.update({
1270
- components: components,
1271
- flags: ["IsComponentsV2"],
1272
- });
1273
- });
1274
- }
1275
-
1276
- // Helper: Force re-render message using message.edit (after reply/followUp)
1277
- async function rerender() {
1278
- if (!__ctx__ || !__ctx__.interaction || !__ctx__.interaction.message) {
1279
- return Promise.resolve();
1280
- }
1281
- __autoRenderEnabled__ = false; // Disable auto-render since manual rerender called
1282
- var components = await __component__.toJSON({ data: __data__ });
1283
- return __safeEdit__(function() {
1284
- return __ctx__.interaction.message.edit({
1285
- components: components,
1286
- flags: ["IsComponentsV2"],
1287
- });
1288
- });
1289
- }
1290
-
1291
- // Helper: Disable auto-render (for handlers that don't need UI update)
1292
- function noRender() {
1293
- __autoRenderEnabled__ = false;
1294
- }
1295
-
1296
- // Helper: Set throttle interval (minimum ms between renders)
1297
- function setThrottle(ms) {
1298
- __throttleMinInterval__ = ms;
1299
- }
1300
-
1301
- // Helper: Destroy this component instance (clears intervals, timers, removes ref)
1302
- // Call this when you want to clean up the component manually
1303
- function destroy() {
1304
- // Run all destroy callbacks (clears intervals, timers, etc.)
1305
- __runDestroy__();
1306
-
1307
- // Clear the ref from DBI store if available
1308
- if (__data__ && __data__.$ref && __component__ && __component__.dbi) {
1309
- __component__.dbi.data.refs.delete(__data__.$ref);
1310
- }
1311
-
1312
- // Disable further auto-renders
1313
- __autoRenderEnabled__ = false;
1314
- }
1315
-
1316
- // Check if there are pending data changes that need SYNC render
1317
- // Returns false if async interaction was called (reply/followUp will handle render)
1318
- function __hasPendingRender__() {
1319
- return __hasDataChanges__ && __autoRenderEnabled__ && !__asyncInteractionCalled__;
1320
- }
1321
-
1322
- // Synchronous flush for when handler completes without async interaction
1323
- // Only called when no reply/followUp was made - uses throttled render
1324
- function __syncFlushRender__() {
1325
- if (__hasDataChanges__ && __autoRenderEnabled__ && !__asyncInteractionCalled__) {
1326
- __hasDataChanges__ = false;
1327
- return __throttledRender__(true); // immediate=true for sync flush
1328
- }
1329
- return Promise.resolve();
1330
- }
1331
-
1332
- // Run all mount callbacks, if callback returns a function add it to destroy callbacks
1333
- function __runMount__() {
1334
- if (__isMounted__) return;
1335
- __isMounted__ = true;
1336
- for (var i = 0; i < __mountCallbacks__.length; i++) {
1337
- try {
1338
- var result = __mountCallbacks__[i]();
1339
- // If mount callback returns a function, add it to destroy callbacks
1340
- if (typeof result === 'function') {
1341
- __destroyCallbacks__.push(result);
1342
- }
1343
- } catch (err) {
1344
- // Mount callback failed
1345
- }
1346
- }
1347
- }
1348
-
1349
- // Run all destroy callbacks
1350
- function __runDestroy__() {
1351
- for (var i = 0; i < __destroyCallbacks__.length; i++) {
1352
- try {
1353
- __destroyCallbacks__[i]();
1354
- } catch (err) {
1355
- // Destroy callback failed
1356
- }
1357
- }
1358
- // Clear pending timeouts
1359
- if (__pendingRenderTimeout__) {
1360
- clearTimeout(__pendingRenderTimeout__);
1361
- __pendingRenderTimeout__ = null;
1362
- }
1363
- }
1364
-
1365
- // Set handler execution flag
1366
- function __setInHandler__(value) {
1367
- __inHandlerExecution__ = value;
1368
- }
1369
-
1370
- ${processedScript}
1371
- return {
1372
- handlers: { ${functionNames.length > 0 ? functionNames.join(", ") : ''} },
1373
- effects: __effects__,
1374
- hasPendingRender: __hasPendingRender__,
1375
- flushRender: __syncFlushRender__,
1376
- wrappedCtx: ctx,
1377
- mountCallbacks: __mountCallbacks__,
1378
- destroyCallbacks: __destroyCallbacks__,
1379
- runMount: __runMount__,
1380
- runDestroy: __runDestroy__,
1381
- setInHandler: __setInHandler__
1382
- };
1383
- };
1384
- `;
1385
-
1386
- // Create the factory function
1387
- const factoryFunc = new Function('console', wrappedScript);
1388
- const createHandlers = factoryFunc(console);
1389
-
1390
- // Execute with the actual data, component, ctx, and imported modules to get handlers with proper closure
1391
- let result;
1392
- try {
1393
- result = createHandlers(initialData, component, ctx, modules);
1394
- } catch (execError) {
1395
- throw execError;
1396
- }
1397
-
1398
- Object.assign(handlers, result.handlers || {});
1399
- effects.push(...(result.effects || []));
1400
-
1401
- // Return full result including render helpers
1402
- // Function to run all effects
1403
- const runEffects = () => {
1404
- for (const effect of effects) {
1405
- try {
1406
- effect();
1407
- } catch (error) {
1408
- // Effect failed
1409
- }
1410
- }
1411
- };
1412
-
1413
- return {
1414
- handlers,
1415
- effects,
1416
- runEffects,
1417
- hasPendingRender: result.hasPendingRender || (() => false),
1418
- flushRender: result.flushRender || (() => Promise.resolve()),
1419
- wrappedCtx: result.wrappedCtx || ctx,
1420
- mountCallbacks: result.mountCallbacks || [],
1421
- destroyCallbacks: result.destroyCallbacks || [],
1422
- runMount: result.runMount || (() => { }),
1423
- runDestroy: result.runDestroy || (() => { }),
1424
- setInHandler: result.setInHandler || (() => { })
1425
- };
1426
- } catch (error) {
1427
- // Log the error for debugging
1428
- console.error("[DBI-Svelte] createHandlerContext failed:", error);
1429
- }
1430
-
1431
- // Function to run all effects (fallback)
1432
- const runEffects = () => {
1433
- for (const effect of effects) {
1434
- try {
1435
- effect();
1436
- } catch (error) {
1437
- // Effect failed
1438
- }
1439
- }
1440
- };
1441
-
1442
- return {
1443
- handlers,
1444
- effects,
1445
- runEffects,
1446
- hasPendingRender: () => false,
1447
- flushRender: () => Promise.resolve(),
1448
- wrappedCtx: ctx,
1449
- mountCallbacks: [],
1450
- destroyCallbacks: [],
1451
- runMount: () => { },
1452
- runDestroy: () => { },
1453
- setInHandler: () => { }
1454
- };
1455
- }
1456
-
1457
- /**
1458
- * Extract $effect callback bodies from script content
1459
- */
1460
- function extractEffectBodies(script: string): string[] {
1461
- const bodies: string[] = [];
1462
- // Match $effect(() => { ... }) or $effect(function() { ... })
1463
- const effectRegex = /\$effect\s*\(\s*(?:(?:function\s*\([^)]*\)|\([^)]*\)\s*=>|\(\)\s*=>)\s*\{([\s\S]*?)\})\s*\)/g;
1464
- let match;
1465
- while ((match = effectRegex.exec(script)) !== null) {
1466
- bodies.push(match[1]);
1467
- }
1468
- return bodies;
1469
- }
1470
-
1471
- /**
1472
- * Extract function names from script content (excluding effect callbacks)
1473
- */
1474
- function extractFunctionNames(script: string): string[] {
1475
- const names: string[] = [];
1476
-
1477
- // Match function declarations: function name() {}
1478
- const functionDeclRegex = /function\s+(\w+)\s*\(/g;
1479
- let match;
1480
- while ((match = functionDeclRegex.exec(script)) !== null) {
1481
- names.push(match[1]);
1482
- }
1483
-
1484
- // Match function expressions: const name = function() {}
1485
- const functionExprRegex = /(?:const|let|var)\s+(\w+)\s*=\s*function\s*\(/g;
1486
- while ((match = functionExprRegex.exec(script)) !== null) {
1487
- names.push(match[1]);
1488
- }
1489
-
1490
- // Match arrow functions: const name = () => {}
1491
- const arrowFunctionRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g;
1492
- while ((match = arrowFunctionRegex.exec(script)) !== null) {
1493
- names.push(match[1]);
1494
- }
1495
-
1496
- // Match arrow functions without parentheses: const name = x => {}
1497
- const simpleArrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\w+\s*=>/g;
1498
- while ((match = simpleArrowRegex.exec(script)) !== null) {
1499
- names.push(match[1]);
1500
- }
1501
-
1502
- return [...new Set(names)]; // Remove duplicates
1503
- }