@mostfeatured/dbi 0.2.29-dev.1 → 0.2.30

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.
@@ -13,53 +13,90 @@ async function ensureImports() {
13
13
  }
14
14
  }
15
15
  /**
16
- * Simple AST walker for Svelte AST nodes
16
+ * Simple AST walker for Svelte AST nodes with parent tracking
17
17
  */
18
- function walkSvelteAst(node, callback) {
18
+ function walkSvelteAst(node, callback, context) {
19
19
  if (!node || typeof node !== 'object')
20
20
  return;
21
- callback(node);
21
+ // Initialize context if not provided
22
+ const ctx = context || { parents: [], eachBlocks: [] };
23
+ callback(node, ctx);
24
+ // Create new context for children with this node as parent
25
+ const childContext = {
26
+ parents: [...ctx.parents, node],
27
+ eachBlocks: [...ctx.eachBlocks]
28
+ };
29
+ // If this is an EachBlock, add it to the context for children
30
+ if (node.type === 'EachBlock') {
31
+ const iterVar = node.context?.name || node.context?.type === 'Identifier' ? node.context.name : 'item';
32
+ const indexVar = node.index;
33
+ // Determine best key expression for unique naming
34
+ // Priority: explicit key > iter.id > iter.name > index > counter
35
+ let keyExpr = indexVar || `__idx_${ctx.eachBlocks.length}`;
36
+ childContext.eachBlocks = [...ctx.eachBlocks, {
37
+ node,
38
+ iterVar,
39
+ indexVar,
40
+ keyExpr
41
+ }];
42
+ }
22
43
  // Walk children based on node type
23
44
  if (node.children && Array.isArray(node.children)) {
24
45
  for (const child of node.children) {
25
- walkSvelteAst(child, callback);
46
+ walkSvelteAst(child, callback, childContext);
26
47
  }
27
48
  }
28
49
  if (node.fragment && node.fragment.nodes) {
29
50
  for (const child of node.fragment.nodes) {
30
- walkSvelteAst(child, callback);
51
+ walkSvelteAst(child, callback, childContext);
31
52
  }
32
53
  }
33
54
  if (node.nodes && Array.isArray(node.nodes)) {
34
55
  for (const child of node.nodes) {
35
- walkSvelteAst(child, callback);
56
+ walkSvelteAst(child, callback, childContext);
57
+ }
58
+ }
59
+ // Handle {#each} body
60
+ if (node.body && node.type === 'EachBlock') {
61
+ if (node.body.nodes) {
62
+ for (const child of node.body.nodes) {
63
+ walkSvelteAst(child, callback, childContext);
64
+ }
65
+ }
66
+ else if (Array.isArray(node.body)) {
67
+ for (const child of node.body) {
68
+ walkSvelteAst(child, callback, childContext);
69
+ }
70
+ }
71
+ else {
72
+ walkSvelteAst(node.body, callback, childContext);
73
+ }
74
+ }
75
+ else if (node.body) {
76
+ if (Array.isArray(node.body)) {
77
+ for (const child of node.body) {
78
+ walkSvelteAst(child, callback, childContext);
79
+ }
80
+ }
81
+ else {
82
+ walkSvelteAst(node.body, callback, childContext);
36
83
  }
37
84
  }
38
85
  // Handle other potential child properties
39
86
  if (node.else) {
40
- walkSvelteAst(node.else, callback);
87
+ walkSvelteAst(node.else, callback, childContext);
41
88
  }
42
89
  if (node.consequent) {
43
- walkSvelteAst(node.consequent, callback);
90
+ walkSvelteAst(node.consequent, callback, childContext);
44
91
  }
45
92
  if (node.alternate) {
46
- walkSvelteAst(node.alternate, callback);
93
+ walkSvelteAst(node.alternate, callback, childContext);
47
94
  }
48
95
  if (node.then) {
49
- walkSvelteAst(node.then, callback);
96
+ walkSvelteAst(node.then, callback, childContext);
50
97
  }
51
98
  if (node.catch) {
52
- walkSvelteAst(node.catch, callback);
53
- }
54
- if (node.body) {
55
- if (Array.isArray(node.body)) {
56
- for (const child of node.body) {
57
- walkSvelteAst(child, callback);
58
- }
59
- }
60
- else {
61
- walkSvelteAst(node.body, callback);
62
- }
99
+ walkSvelteAst(node.catch, callback, childContext);
63
100
  }
64
101
  }
65
102
  /**
@@ -113,7 +150,7 @@ ${nextLine ? `${lineNum + 1} | ${nextLine}` : ''}
113
150
  const inlineHandlers = new Map();
114
151
  let inlineHandlerCounter = 0;
115
152
  // Walk through HTML nodes to find event handlers
116
- walkSvelteAst(ast.html || ast.fragment, (node) => {
153
+ walkSvelteAst(ast.html || ast.fragment, (node, walkContext) => {
117
154
  if (node.type === "Element" || node.type === "InlineComponent" || node.type === "RegularElement" || node.type === "Component") {
118
155
  const attributes = node.attributes || [];
119
156
  const nodeName = node.name.toLowerCase();
@@ -245,26 +282,34 @@ ${nextLine ? `${lineNum + 1} | ${nextLine}` : ''}
245
282
  if (!foundHandler)
246
283
  return; // No handler found, skip
247
284
  let componentName;
285
+ const isInsideEach = walkContext.eachBlocks.length > 0;
248
286
  if (nameAttr) {
249
287
  componentName = getAttributeValue(nameAttr);
250
288
  }
251
289
  else {
252
- // No name attribute - generate a deterministic one based on position
253
- // Use the handler name and counter for deterministic naming
290
+ // No name attribute - generate one
291
+ // If inside {#each}, we need a DYNAMIC name using the loop variable
254
292
  const positionKey = `${node.name.toLowerCase()}_${autoNameCounter++}`;
255
- // If data is provided, use/store in $autoNames for persistence across re-renders
256
- if (data) {
257
- if (!data.$autoNames) {
258
- data.$autoNames = {};
259
- }
260
- if (!data.$autoNames[positionKey]) {
261
- data.$autoNames[positionKey] = `__auto_${positionKey}`;
262
- }
263
- componentName = data.$autoNames[positionKey];
293
+ if (isInsideEach) {
294
+ // Inside {#each} - we'll inject a dynamic name expression
295
+ // The actual name will be generated at runtime using loop variables
296
+ componentName = `__auto_${positionKey}`;
264
297
  }
265
298
  else {
266
- // No data - use deterministic name based on position
267
- componentName = `__auto_${positionKey}`;
299
+ // Not in loop - use static deterministic name
300
+ // If data is provided, use/store in $autoNames for persistence across re-renders
301
+ if (data) {
302
+ if (!data.$autoNames) {
303
+ data.$autoNames = {};
304
+ }
305
+ if (!data.$autoNames[positionKey]) {
306
+ data.$autoNames[positionKey] = `__auto_${positionKey}`;
307
+ }
308
+ componentName = data.$autoNames[positionKey];
309
+ }
310
+ else {
311
+ componentName = `__auto_${positionKey}`;
312
+ }
268
313
  }
269
314
  // Track this element for source injection
270
315
  elementsNeedingNames.push({
@@ -272,7 +317,12 @@ ${nextLine ? `${lineNum + 1} | ${nextLine}` : ''}
272
317
  name: componentName,
273
318
  handlerName: foundHandler.handlerName,
274
319
  eventType: foundHandler.eventType,
275
- element: node.name.toLowerCase()
320
+ element: node.name.toLowerCase(),
321
+ eachContext: isInsideEach ? walkContext.eachBlocks.map(b => ({
322
+ iterVar: b.iterVar,
323
+ indexVar: b.indexVar,
324
+ keyExpr: b.keyExpr
325
+ })) : undefined
276
326
  });
277
327
  }
278
328
  // Add to handlers map
@@ -290,11 +340,36 @@ ${nextLine ? `${lineNum + 1} | ${nextLine}` : ''}
290
340
  // Sort by position descending so we don't mess up offsets
291
341
  let processedSource = source;
292
342
  const sortedElements = [...elementsNeedingNames].sort((a, b) => b.node.start - a.node.start);
293
- for (const { node, name } of sortedElements) {
343
+ for (const { node, name, eachContext } of sortedElements) {
294
344
  // Find the position right after the opening tag name
295
345
  // e.g., <button ...> -> insert after "button"
296
346
  const tagEnd = node.start + 1 + node.name.length; // +1 for '<'
297
- processedSource = processedSource.slice(0, tagEnd) + ` name="${name}"` + processedSource.slice(tagEnd);
347
+ let nameAttrValue;
348
+ if (eachContext && eachContext.length > 0) {
349
+ // Inside {#each} - generate dynamic name expression using loop variables
350
+ // Build a unique key from all nested loop contexts
351
+ // Priority: item.id > item.name > index
352
+ const keyParts = [name];
353
+ for (let i = 0; i < eachContext.length; i++) {
354
+ const ctx = eachContext[i];
355
+ // Use index variable if available, otherwise use a property from iter var
356
+ if (ctx.indexVar) {
357
+ keyParts.push(`\${${ctx.indexVar}}`);
358
+ }
359
+ else {
360
+ // Try common unique identifiers: id, name, key, value
361
+ // Using the iter var directly as fallback
362
+ keyParts.push(`\${${ctx.iterVar}?.id ?? ${ctx.iterVar}?.name ?? ${ctx.iterVar}?.key ?? ${ctx.iterVar}?.value ?? JSON.stringify(${ctx.iterVar}).slice(0,20)}`);
363
+ }
364
+ }
365
+ // Use backtick template literal for dynamic name
366
+ nameAttrValue = `{\`${keyParts.join('_')}\`}`;
367
+ }
368
+ else {
369
+ // Static name - use quotes
370
+ nameAttrValue = `"${name}"`;
371
+ }
372
+ processedSource = processedSource.slice(0, tagEnd) + ` name=${nameAttrValue}` + processedSource.slice(tagEnd);
298
373
  }
299
374
  // Extract declared props from $props() destructuring
300
375
  const declaredProps = [];
@@ -917,16 +992,14 @@ function createHandlerContext(scriptContent, initialData = {}, component, ctx, s
917
992
  });
918
993
  }
919
994
 
920
- // Rate-limit aware edit function with retry
921
- function __safeEdit__(editFn, retryCount) {
995
+ // Rate-limit aware edit function with retry and enhanced error handling
996
+ function __safeEdit__(editFn, retryCount, componentsForError) {
922
997
  retryCount = retryCount || 0;
923
998
  var maxRetries = 3;
924
999
 
925
1000
  return editFn().then(function(result) {
926
- console.log("[DBI-Svelte DEBUG] __safeEdit__ success");
927
1001
  return result;
928
1002
  }).catch(function(err) {
929
- console.error("[DBI-Svelte DEBUG] __safeEdit__ error:", err.message || err);
930
1003
  // Check for rate limit (429)
931
1004
  if (err.status === 429 || (err.message && err.message.includes('rate limit'))) {
932
1005
  var retryAfter = err.retry_after || err.retryAfter || 1;
@@ -937,14 +1010,59 @@ function createHandlerContext(scriptContent, initialData = {}, component, ctx, s
937
1010
  setTimeout(function() {
938
1011
  __isRateLimited__ = false;
939
1012
  if (retryCount < maxRetries) {
940
- resolve(__safeEdit__(editFn, retryCount + 1));
1013
+ resolve(__safeEdit__(editFn, retryCount + 1, componentsForError));
941
1014
  } else {
942
1015
  resolve();
943
1016
  }
944
1017
  }, retryAfter * 1000);
945
1018
  });
946
1019
  }
947
- // Log and re-throw non-rate-limit errors
1020
+
1021
+ // Check if it's a component validation error and enhance it
1022
+ if (err.code === 50035 || err.rawError?.code === 50035 ||
1023
+ (err.message && (err.message.includes('Invalid Form Body') || err.message.includes('BASE_TYPE')))) {
1024
+ // Create enhanced error message
1025
+ var errorMsg = err.message || '';
1026
+ var enhancedMsg = '\\n╔══════════════════════════════════════════════════════════════╗\\n';
1027
+ enhancedMsg += '║ Discord Components V2 Validation Error ║\\n';
1028
+ enhancedMsg += '╠══════════════════════════════════════════════════════════════╣\\n';
1029
+
1030
+ // Parse error paths from message
1031
+ var errorLines = errorMsg.split('\\n');
1032
+ for (var i = 0; i < errorLines.length; i++) {
1033
+ var line = errorLines[i];
1034
+ var match = line.match(/([\\w\\[\\]\\.]+)\\[([A-Z_]+)\\]:\\s*(.+)/);
1035
+ if (match) {
1036
+ var path = match[1];
1037
+ var code = match[2];
1038
+ var msg = match[3];
1039
+
1040
+ enhancedMsg += '║\\n';
1041
+ enhancedMsg += '║ ❌ Path: ' + path + '\\n';
1042
+ enhancedMsg += '║ ├─ Code: ' + code + '\\n';
1043
+ enhancedMsg += '║ ├─ Message: ' + msg + '\\n';
1044
+
1045
+ // Add helpful hints based on error code
1046
+ if (code === 'BASE_TYPE_REQUIRED') {
1047
+ enhancedMsg += '║ └─ Fix: This field is required - check if the element has all required attributes\\n';
1048
+ } else if (code === 'BASE_TYPE_BAD_LENGTH') {
1049
+ enhancedMsg += '║ └─ Fix: Wrong number of items - Discord has min/max limits for components\\n';
1050
+ } else if (code.includes('MAX_LENGTH') || code.includes('MIN_LENGTH')) {
1051
+ enhancedMsg += '║ └─ Fix: Content length issue - adjust text or number of items\\n';
1052
+ }
1053
+ }
1054
+ }
1055
+
1056
+ enhancedMsg += '║\\n';
1057
+ enhancedMsg += '╚══════════════════════════════════════════════════════════════╝';
1058
+
1059
+ var enhancedError = new Error('[DBI-Svelte] Component validation error:\\n' + enhancedMsg + '\\n\\nOriginal: ' + errorMsg);
1060
+ enhancedError.originalError = err;
1061
+ enhancedError.type = 'discord-component-validation';
1062
+ throw enhancedError;
1063
+ }
1064
+
1065
+ // Re-throw other errors
948
1066
  throw err;
949
1067
  });
950
1068
  }
@@ -987,28 +1105,16 @@ function createHandlerContext(scriptContent, initialData = {}, component, ctx, s
987
1105
 
988
1106
  // Actual render execution
989
1107
  async function __executeRender__() {
990
- console.log("[DBI-Svelte DEBUG] __executeRender__ called");
991
1108
  // Run pre-render callbacks (async, awaited)
992
1109
  await __runPreRender__();
993
1110
 
994
- console.log("[DBI-Svelte DEBUG] Calling toJSON with data:", JSON.stringify(__data__).substring(0, 200));
995
1111
  var components = await __component__.toJSON({ data: __data__ });
996
- console.log("[DBI-Svelte DEBUG] toJSON returned", components ? "components" : "null");
997
1112
  var result;
998
1113
 
999
1114
  // Try to use current interaction if available
1000
1115
  if (__ctx__ && __ctx__.interaction) {
1001
1116
  try {
1002
1117
  var i = __ctx__.interaction;
1003
- console.log("[DBI-Svelte DEBUG] interaction available, replied:", i.replied, "deferred:", i.deferred, "__interactionDeferred__:", __interactionDeferred__);
1004
- console.log("[DBI-Svelte DEBUG] components count:", components ? components.length : 0);
1005
- if (components && components[0]) {
1006
- console.log("[DBI-Svelte DEBUG] first component type:", components[0].type);
1007
- // Log first text content if it's a text_display
1008
- if (components[0].type === 10 && components[0].content) {
1009
- console.log("[DBI-Svelte DEBUG] first text content:", components[0].content.substring(0, 100));
1010
- }
1011
- }
1012
1118
 
1013
1119
  // Update last message reference
1014
1120
  if (i.message) {
@@ -1017,23 +1123,21 @@ function createHandlerContext(scriptContent, initialData = {}, component, ctx, s
1017
1123
 
1018
1124
  // Check if interaction was deferred (our flag) or Discord.js flags
1019
1125
  if (i.replied || i.deferred || __interactionDeferred__) {
1020
- console.log("[DBI-Svelte DEBUG] Using message.edit (deferred path)");
1021
1126
  // Already replied/deferred, use message.edit with rate limit handling
1022
1127
  result = await __safeEdit__(function() {
1023
1128
  return i.message.edit({
1024
1129
  components: components,
1025
1130
  flags: ["IsComponentsV2"],
1026
1131
  });
1027
- });
1132
+ }, 0, components);
1028
1133
  } else {
1029
- console.log("[DBI-Svelte DEBUG] Using interaction.update");
1030
1134
  // Not replied yet, use update with rate limit handling
1031
1135
  result = await __safeEdit__(function() {
1032
1136
  return i.update({
1033
1137
  components: components,
1034
1138
  flags: ["IsComponentsV2"],
1035
1139
  });
1036
- });
1140
+ }, 0, components);
1037
1141
  }
1038
1142
 
1039
1143
  // Run after-render callbacks
@@ -1052,7 +1156,7 @@ function createHandlerContext(scriptContent, initialData = {}, component, ctx, s
1052
1156
  components: components,
1053
1157
  flags: ["IsComponentsV2"],
1054
1158
  });
1055
- });
1159
+ }, 0, components);
1056
1160
 
1057
1161
  // Run after-render callbacks
1058
1162
  __runAfterRender__();
@@ -1185,10 +1289,8 @@ function createHandlerContext(scriptContent, initialData = {}, component, ctx, s
1185
1289
  // After the reply completes, flush any pending renders using throttled render
1186
1290
  if (result && typeof result.then === 'function') {
1187
1291
  result.then(function() {
1188
- console.log("[DBI-Svelte DEBUG] " + prop + " completed, hasDataChanges:", __hasDataChanges__, "autoRenderEnabled:", __autoRenderEnabled__);
1189
1292
  if (__hasDataChanges__ && __autoRenderEnabled__) {
1190
1293
  __hasDataChanges__ = false;
1191
- console.log("[DBI-Svelte DEBUG] Triggering render after " + prop);
1192
1294
  // Use throttled render which handles rate limits
1193
1295
  __throttledRender__(false);
1194
1296
  }