@mostfeatured/dbi 0.2.8 → 0.2.10

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.
@@ -4,6 +4,7 @@ import { NamespaceEnums } from "../../../../generated/namespaceData";
4
4
  import { parseHTMLComponentsV2 } from "./parser";
5
5
  import { parseSvelteComponent, createHandlerContext, SvelteComponentInfo, HandlerContextResult } from "./svelteParser";
6
6
  import * as stuffs from "stuffs";
7
+ import * as vm from "vm";
7
8
 
8
9
  export interface SvelteRenderOptions {
9
10
  data?: Record<string, any>;
@@ -34,7 +35,7 @@ export async function renderSvelteComponent(
34
35
  // Use the processed source (with auto-generated names injected)
35
36
  const processedSource = componentInfo.processedSource;
36
37
 
37
- // Compile the Svelte component for SSR
38
+ // Compile the Svelte component for SSR (Svelte 5)
38
39
  const compiled = compile(processedSource, {
39
40
  generate: "server",
40
41
  css: "injected",
@@ -45,23 +46,16 @@ export async function renderSvelteComponent(
45
46
  let html = "";
46
47
  try {
47
48
  const moduleContext = createModuleContext(dbi, data, ttl);
48
- const componentModule = evaluateCompiledComponent(compiled.js.code, moduleContext);
49
+ const Component = evaluateCompiledComponent(compiled.js.code, moduleContext);
49
50
 
50
- // Render the component with props (include data in props)
51
- const renderResult = componentModule.render({ ...data, data });
52
- html = renderResult.html || renderResult.body || "";
53
-
54
- // Debug: Log rendered HTML
55
- if (process.env.DEBUG_SVELTE) {
56
- console.log("Rendered HTML:", html);
57
- }
51
+ // Svelte 5 SSR: Use render from svelte/server
52
+ const { render } = require("svelte/server");
53
+ const renderResult = render(Component, { props: { ...data, data } });
54
+ html = renderResult.body || "";
58
55
  } catch (error) {
59
- console.error("Error rendering Svelte component:", error);
60
56
  throw error;
61
57
  }
62
58
 
63
- console.log("Rendered HTML:", html);
64
-
65
59
  // For Svelte mode, inject state into interactive elements as a ref
66
60
  // Reuse existing ref if data already has one, otherwise create new
67
61
  if (data && Object.keys(data).length > 0) {
@@ -99,13 +93,9 @@ export async function renderSvelteComponent(
99
93
  });
100
94
  }
101
95
 
102
- console.log("HTML with state:", html);
103
-
104
96
  // Parse the rendered HTML to Discord components
105
97
  const components = parseHTMLComponentsV2(dbi, html, dbiName, { data, ttl });
106
98
 
107
- console.log("Parsed Components:", JSON.stringify(components, null, 2));
108
-
109
99
  // Create handler context (also captures $effect callbacks)
110
100
  const handlerContext = createHandlerContext(componentInfo.scriptContent, data);
111
101
  const handlers = new Map<string, { handlerFn: Function, context: any }>();
@@ -158,161 +148,145 @@ function createModuleContext(dbi: DBI<NamespaceEnums>, data: Record<string, any>
158
148
  */
159
149
  function evaluateCompiledComponent(code: string, context: Record<string, any>): any {
160
150
  try {
161
- // Aggressively strip ALL import statements
162
- // Svelte 5 generates imports with special chars like $ that need careful handling
151
+ // Load Svelte 5 internal runtime
152
+ const svelteInternal = require("svelte/internal/server");
153
+
154
+ // Process the code to work in our context
163
155
  let processedCode = code;
164
156
 
165
- // Remove all lines that start with 'import' (most reliable approach)
157
+ // Collect external modules to inject into sandbox
158
+ const externalModules: Record<string, any> = {};
159
+
160
+ // Replace svelte internal imports
161
+ processedCode = processedCode.replace(
162
+ /import\s*\*\s*as\s*(\w+)\s*from\s*["']svelte\/internal\/server["'];?/g,
163
+ 'const $1 = __svelteInternal;'
164
+ );
165
+ processedCode = processedCode.replace(
166
+ /import\s*\{([^}]+)\}\s*from\s*["']svelte\/internal\/server["'];?/g,
167
+ (match, imports) => {
168
+ const importList = imports.split(',').map((i: string) => i.trim());
169
+ return `const { ${importList.join(', ')} } = __svelteInternal;`;
170
+ }
171
+ );
172
+
173
+ // Handle external module imports (default imports)
174
+ processedCode = processedCode.replace(
175
+ /import\s+(\w+)\s+from\s*["']([^"']+)["'];?/g,
176
+ (match, varName, modulePath) => {
177
+ // Skip svelte imports
178
+ if (modulePath.startsWith('svelte')) return '';
179
+ try {
180
+ const mod = require(modulePath);
181
+ externalModules[varName] = mod.default || mod;
182
+ return `const ${varName} = __externalModules.${varName};`;
183
+ } catch (e) {
184
+ return '';
185
+ }
186
+ }
187
+ );
188
+
189
+ // Handle named imports from external modules
190
+ processedCode = processedCode.replace(
191
+ /import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["'];?/g,
192
+ (match, imports, modulePath) => {
193
+ // Skip svelte imports
194
+ if (modulePath.startsWith('svelte')) return '';
195
+ try {
196
+ const mod = require(modulePath);
197
+ const importList = imports.split(',').map((i: string) => i.trim());
198
+ importList.forEach((importName: string) => {
199
+ const [name, alias] = importName.split(' as ').map(s => s.trim());
200
+ externalModules[alias || name] = mod[name] || mod.default?.[name];
201
+ });
202
+ return importList.map((importName: string) => {
203
+ const [name, alias] = importName.split(' as ').map(s => s.trim());
204
+ return `const ${alias || name} = __externalModules.${alias || name};`;
205
+ }).join('\n');
206
+ } catch (e) {
207
+ return '';
208
+ }
209
+ }
210
+ );
211
+
212
+ // Handle namespace imports from external modules
213
+ processedCode = processedCode.replace(
214
+ /import\s*\*\s*as\s*(\w+)\s*from\s*["']([^"']+)["'];?/g,
215
+ (match, varName, modulePath) => {
216
+ // Skip svelte imports
217
+ if (modulePath.startsWith('svelte')) return '';
218
+ try {
219
+ const mod = require(modulePath);
220
+ externalModules[varName] = mod;
221
+ return `const ${varName} = __externalModules.${varName};`;
222
+ } catch (e) {
223
+ return '';
224
+ }
225
+ }
226
+ );
227
+
228
+ // Remove any remaining import statements
166
229
  processedCode = processedCode
167
230
  .split('\n')
168
231
  .filter(line => !line.trim().startsWith('import '))
169
232
  .join('\n');
170
233
 
171
- // Also remove 'export default' and replace with assignment
172
- processedCode = processedCode.replace(/export\s+default\s+/g, 'module.exports = ');
173
-
174
- // Create exports object
175
- const exports: any = {};
176
- const module = { exports };
177
-
178
- // Provide Svelte 5 server runtime functions
179
- // The compiled code references these via the $ namespace
180
- const $: any = {
181
- // Escape functions
182
- escape: (str: any) => String(str)
183
- .replace(/&/g, "&amp;")
184
- .replace(/</g, "&lt;")
185
- .replace(/>/g, "&gt;")
186
- .replace(/"/g, "&quot;")
187
- .replace(/'/g, "&#39;"),
188
- escape_text: (str: any) => String(str)
189
- .replace(/&/g, "&amp;")
190
- .replace(/</g, "&lt;")
191
- .replace(/>/g, "&gt;"),
192
- escape_attribute_value: (str: any) => String(str)
193
- .replace(/"/g, "&quot;")
194
- .replace(/'/g, "&#39;"),
195
- // Array helpers for {#each}
196
- ensure_array_like: (value: any) => {
197
- if (Array.isArray(value)) return value;
198
- if (value == null) return [];
199
- if (typeof value === 'object' && Symbol.iterator in value) {
200
- return Array.from(value);
201
- }
202
- return [value];
203
- },
204
- // Renderer methods
205
- component: (fn: Function) => fn,
206
- push: (content: string) => content,
207
- pop: () => { },
208
- attr: (name: string, value: any, is_boolean?: boolean) => {
209
- if (is_boolean && !value) return '';
210
- if (value == null) return '';
211
- return ` ${name}="${String(value)}"`;
212
- },
213
- spread_attributes: (attrs: Record<string, any>) => {
214
- return Object.entries(attrs)
215
- .map(([key, value]) => ` ${key}="${String(value)}"`)
216
- .join('');
217
- },
218
- spread_props: (props: Record<string, any>) => props,
219
- bind_props: (props: Record<string, any>, names: string[]) => {
220
- const result: any = {};
221
- names.forEach(name => {
222
- if (name in props) result[name] = props[name];
223
- });
224
- return result;
225
- },
226
- stringify: (value: any) => JSON.stringify(value),
227
- store_get: (store: any) => store,
228
- unsubscribe_stores: () => { },
229
- // Control flow
230
- each: (items: any[], fn: Function) => {
231
- return items.map((item, index) => fn(item, index)).join('');
232
- },
233
- // Lifecycle
234
+ // Replace 'export default' with assignment
235
+ processedCode = processedCode.replace(/export\s+default\s+/g, '__exports.default = ');
236
+ // Replace 'export function' with assignment
237
+ processedCode = processedCode.replace(/export\s+function\s+(\w+)/g, '__exports.$1 = function $1');
238
+ // Replace 'export const' with assignment
239
+ processedCode = processedCode.replace(/export\s+const\s+(\w+)/g, '__exports.$1 = ');
240
+ // Replace 'export let' with assignment
241
+ processedCode = processedCode.replace(/export\s+let\s+(\w+)/g, '__exports.$1 = ');
242
+
243
+ // Create the sandbox context
244
+ const __exports: any = {};
245
+ // Svelte lifecycle functions - no-ops for SSR
246
+ const svelteLifecycle = {
247
+ onMount: () => () => { }, // Returns cleanup function
248
+ onDestroy: () => { },
249
+ beforeUpdate: () => { },
250
+ afterUpdate: () => { },
234
251
  tick: () => Promise.resolve(),
235
- flush: () => { },
236
- // Validation
237
- validate_component: (component: any) => component,
238
- validate_store: (store: any) => store,
239
- // Misc
240
- noop: () => { },
241
- run: (fn: Function) => fn(),
242
- run_all: (fns: Function[]) => fns.forEach(f => f()),
243
- is_promise: (value: any) => value && typeof value.then === 'function',
244
- missing_component: { $$render: () => '' },
245
- };
246
-
247
- // Create renderer object that accumulates HTML
248
- let html = '';
249
- const $$renderer: any = {
250
- out: '',
251
- head: '',
252
- component: (fn: Function) => {
253
- return fn($$renderer);
254
- },
255
- push: (content: string) => {
256
- html += content;
257
- return $$renderer;
258
- },
259
- pop: () => $$renderer,
260
- element: (tag: string, fn: Function) => {
261
- html += `<${tag}`;
262
- fn();
263
- html += `>`;
264
- return $$renderer;
265
- },
266
- close: (tag: string) => {
267
- html += `</${tag}>`;
268
- return $$renderer;
269
- },
252
+ untrack: (fn: () => any) => fn(),
253
+ createEventDispatcher: () => () => { },
270
254
  };
271
255
 
272
- // Create a function wrapper with all context and Svelte runtime
273
- // Include lifecycle stubs - these are no-ops in SSR but need to be defined
274
- const allContext = {
256
+ // Note: Svelte 5 runes ($state, $derived, etc.) are compile-time features
257
+ // The compiler transforms them, so we don't need runtime implementations.
258
+ // The `$` variable is used by compiled code as the svelte/internal/server namespace.
259
+
260
+ const sandbox = {
261
+ __svelteInternal: svelteInternal,
262
+ __externalModules: externalModules,
263
+ $: svelteInternal, // Direct alias for compiled Svelte code that uses `$`
264
+ __exports,
265
+ console,
266
+ ...svelteLifecycle,
275
267
  ...context,
276
- $,
277
- $$renderer,
278
- module,
279
- exports,
280
- // Lifecycle stubs for SSR (actual lifecycle runs in handler context)
281
- onMount: (fn: Function) => { /* SSR no-op, real onMount runs in handler context */ },
282
- onDestroy: (fn: Function) => { /* SSR no-op, real onDestroy runs in handler context */ },
283
268
  };
284
269
 
285
- const contextKeys = Object.keys(allContext);
286
- const contextValues = Object.values(allContext);
287
-
288
- // Wrap in IIFE
270
+ // Wrap code in IIFE
289
271
  const wrappedCode = `
290
- (function(${contextKeys.join(", ")}) {
272
+ (function() {
291
273
  ${processedCode}
292
- return module.exports;
293
- })
274
+ })();
294
275
  `;
295
276
 
296
- // Evaluate the code
297
- const func = eval(wrappedCode);
298
- const component = func(...contextValues);
277
+ // Run in VM context for better isolation
278
+ vm.createContext(sandbox);
279
+ vm.runInContext(wrappedCode, sandbox);
299
280
 
300
- // Return a render function
301
- return {
302
- render: (props: any) => {
303
- html = '';
304
- try {
305
- component($$renderer, props);
306
- return { html, head: '', css: { code: '', map: null } };
307
- } catch (e) {
308
- console.error('Component render error:', e);
309
- throw e;
310
- }
311
- }
312
- };
281
+ // Return the component
282
+ const Component = sandbox.__exports.default;
283
+
284
+ if (!Component) {
285
+ throw new Error("Svelte component did not export a default component");
286
+ }
287
+
288
+ return Component;
313
289
  } catch (error) {
314
- console.error("Error evaluating compiled component:", error);
315
- console.error("Code preview (first 500 chars):", code.substring(0, 500));
316
290
  throw error;
317
291
  }
318
292
  }
@@ -5,7 +5,7 @@ import path from "path";
5
5
  * @example
6
6
  * await recursiveImport("./src", [".js"], [".d.ts"])
7
7
  */
8
- export async function recursiveImport(folderPath: string, exts: string[] = [".js"], ignore: string[] = [".d.ts",".js.map",".d.ts.map"]): Promise<any> {
8
+ export async function recursiveImport(folderPath: string, exts: string[] = [".js"], ignore: string[] = [".d.ts", ".js.map", ".d.ts.map"]): Promise<any> {
9
9
  let files = await fs.promises.readdir(folderPath, { withFileTypes: true });
10
10
  let dirName = __dirname;
11
11
 
@@ -24,8 +24,8 @@ export async function recursiveImport(folderPath: string, exts: string[] = [".js
24
24
  } catch (e: any) {
25
25
  // Ignore "Missing 'default' export" errors in Bun runtime
26
26
  // The module's side effects still execute before the error is thrown
27
- if (!e.message?.includes("Missing 'default' export") &&
28
- !e.message?.includes("does not provide an export named 'default'")) {
27
+ if (!e.message?.includes("Missing 'default' export") &&
28
+ !e.message?.includes("does not provide an export named 'default'")) {
29
29
  throw e;
30
30
  }
31
31
  }
package/test/index.ts CHANGED
@@ -3,7 +3,7 @@ import path from "path";
3
3
 
4
4
  const dbi = createDBI("svelte", {
5
5
  discord: {
6
- token: "YOUR_BOT_TOKEN_HERE",
6
+ token: "ODI0MjEwMTMyMzUwMDA5MzY2.GFvvpD.FsVgZH6oNtUYTapngDjurqCBovx5cpQ4ErwsKg",
7
7
  options: {
8
8
  intents: [
9
9
  "GuildMessages",
@@ -26,10 +26,7 @@ dbi.register(({ ChatInput, HTMLComponentsV2 }) => {
26
26
  HTMLComponentsV2({
27
27
  name: "product-showcase",
28
28
  mode: 'svelte',
29
- file: path.join(__dirname, "product-showcase.svelte"),
30
- onExecute(ctx) {
31
- console.log("Product showcase interaction:", ctx.data[0]);
32
- }
29
+ file: path.join(__dirname, "product-showcase.svelte")
33
30
  });
34
31
 
35
32
  // Test command
package/tsconfig.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "noUncheckedIndexedAccess": true,
13
13
  // Modules
14
14
  "module": "commonjs",
15
- "moduleResolution": "Node",
15
+ "moduleResolution": "node",
16
16
  "resolveJsonModule": true,
17
17
  // Emit
18
18
  "declaration": true,