@mostfeatured/dbi 0.2.9 → 0.2.11

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,17 @@ 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
+ // Pass data properties as top-level props (Svelte 5 expects flat props object)
54
+ const renderResult = render(Component, { props: data });
55
+ html = renderResult.body || "";
58
56
  } catch (error) {
59
- console.error("Error rendering Svelte component:", error);
60
57
  throw error;
61
58
  }
62
59
 
63
- console.log("Rendered HTML:", html);
64
-
65
60
  // For Svelte mode, inject state into interactive elements as a ref
66
61
  // Reuse existing ref if data already has one, otherwise create new
67
62
  if (data && Object.keys(data).length > 0) {
@@ -99,13 +94,9 @@ export async function renderSvelteComponent(
99
94
  });
100
95
  }
101
96
 
102
- console.log("HTML with state:", html);
103
-
104
97
  // Parse the rendered HTML to Discord components
105
98
  const components = parseHTMLComponentsV2(dbi, html, dbiName, { data, ttl });
106
99
 
107
- console.log("Parsed Components:", JSON.stringify(components, null, 2));
108
-
109
100
  // Create handler context (also captures $effect callbacks)
110
101
  const handlerContext = createHandlerContext(componentInfo.scriptContent, data);
111
102
  const handlers = new Map<string, { handlerFn: Function, context: any }>();
@@ -158,161 +149,145 @@ function createModuleContext(dbi: DBI<NamespaceEnums>, data: Record<string, any>
158
149
  */
159
150
  function evaluateCompiledComponent(code: string, context: Record<string, any>): any {
160
151
  try {
161
- // Aggressively strip ALL import statements
162
- // Svelte 5 generates imports with special chars like $ that need careful handling
152
+ // Load Svelte 5 internal runtime
153
+ const svelteInternal = require("svelte/internal/server");
154
+
155
+ // Process the code to work in our context
163
156
  let processedCode = code;
164
157
 
165
- // Remove all lines that start with 'import' (most reliable approach)
158
+ // Collect external modules to inject into sandbox
159
+ const externalModules: Record<string, any> = {};
160
+
161
+ // Replace svelte internal imports
162
+ processedCode = processedCode.replace(
163
+ /import\s*\*\s*as\s*(\w+)\s*from\s*["']svelte\/internal\/server["'];?/g,
164
+ 'const $1 = __svelteInternal;'
165
+ );
166
+ processedCode = processedCode.replace(
167
+ /import\s*\{([^}]+)\}\s*from\s*["']svelte\/internal\/server["'];?/g,
168
+ (match, imports) => {
169
+ const importList = imports.split(',').map((i: string) => i.trim());
170
+ return `const { ${importList.join(', ')} } = __svelteInternal;`;
171
+ }
172
+ );
173
+
174
+ // Handle external module imports (default imports)
175
+ processedCode = processedCode.replace(
176
+ /import\s+(\w+)\s+from\s*["']([^"']+)["'];?/g,
177
+ (match, varName, modulePath) => {
178
+ // Skip svelte imports
179
+ if (modulePath.startsWith('svelte')) return '';
180
+ try {
181
+ const mod = require(modulePath);
182
+ externalModules[varName] = mod.default || mod;
183
+ return `const ${varName} = __externalModules.${varName};`;
184
+ } catch (e) {
185
+ return '';
186
+ }
187
+ }
188
+ );
189
+
190
+ // Handle named imports from external modules
191
+ processedCode = processedCode.replace(
192
+ /import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["'];?/g,
193
+ (match, imports, modulePath) => {
194
+ // Skip svelte imports
195
+ if (modulePath.startsWith('svelte')) return '';
196
+ try {
197
+ const mod = require(modulePath);
198
+ const importList = imports.split(',').map((i: string) => i.trim());
199
+ importList.forEach((importName: string) => {
200
+ const [name, alias] = importName.split(' as ').map(s => s.trim());
201
+ externalModules[alias || name] = mod[name] || mod.default?.[name];
202
+ });
203
+ return importList.map((importName: string) => {
204
+ const [name, alias] = importName.split(' as ').map(s => s.trim());
205
+ return `const ${alias || name} = __externalModules.${alias || name};`;
206
+ }).join('\n');
207
+ } catch (e) {
208
+ return '';
209
+ }
210
+ }
211
+ );
212
+
213
+ // Handle namespace imports from external modules
214
+ processedCode = processedCode.replace(
215
+ /import\s*\*\s*as\s*(\w+)\s*from\s*["']([^"']+)["'];?/g,
216
+ (match, varName, modulePath) => {
217
+ // Skip svelte imports
218
+ if (modulePath.startsWith('svelte')) return '';
219
+ try {
220
+ const mod = require(modulePath);
221
+ externalModules[varName] = mod;
222
+ return `const ${varName} = __externalModules.${varName};`;
223
+ } catch (e) {
224
+ return '';
225
+ }
226
+ }
227
+ );
228
+
229
+ // Remove any remaining import statements
166
230
  processedCode = processedCode
167
231
  .split('\n')
168
232
  .filter(line => !line.trim().startsWith('import '))
169
233
  .join('\n');
170
234
 
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
235
+ // Replace 'export default' with assignment
236
+ processedCode = processedCode.replace(/export\s+default\s+/g, '__exports.default = ');
237
+ // Replace 'export function' with assignment
238
+ processedCode = processedCode.replace(/export\s+function\s+(\w+)/g, '__exports.$1 = function $1');
239
+ // Replace 'export const' with assignment
240
+ processedCode = processedCode.replace(/export\s+const\s+(\w+)/g, '__exports.$1 = ');
241
+ // Replace 'export let' with assignment
242
+ processedCode = processedCode.replace(/export\s+let\s+(\w+)/g, '__exports.$1 = ');
243
+
244
+ // Create the sandbox context
245
+ const __exports: any = {};
246
+ // Svelte lifecycle functions - no-ops for SSR
247
+ const svelteLifecycle = {
248
+ onMount: () => () => { }, // Returns cleanup function
249
+ onDestroy: () => { },
250
+ beforeUpdate: () => { },
251
+ afterUpdate: () => { },
234
252
  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
- },
253
+ untrack: (fn: () => any) => fn(),
254
+ createEventDispatcher: () => () => { },
270
255
  };
271
256
 
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 = {
257
+ // Note: Svelte 5 runes ($state, $derived, etc.) are compile-time features
258
+ // The compiler transforms them, so we don't need runtime implementations.
259
+ // The `$` variable is used by compiled code as the svelte/internal/server namespace.
260
+
261
+ const sandbox = {
262
+ __svelteInternal: svelteInternal,
263
+ __externalModules: externalModules,
264
+ $: svelteInternal, // Direct alias for compiled Svelte code that uses `$`
265
+ __exports,
266
+ console,
267
+ ...svelteLifecycle,
275
268
  ...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
269
  };
284
270
 
285
- const contextKeys = Object.keys(allContext);
286
- const contextValues = Object.values(allContext);
287
-
288
- // Wrap in IIFE
271
+ // Wrap code in IIFE
289
272
  const wrappedCode = `
290
- (function(${contextKeys.join(", ")}) {
273
+ (function() {
291
274
  ${processedCode}
292
- return module.exports;
293
- })
275
+ })();
294
276
  `;
295
277
 
296
- // Evaluate the code
297
- const func = eval(wrappedCode);
298
- const component = func(...contextValues);
278
+ // Run in VM context for better isolation
279
+ vm.createContext(sandbox);
280
+ vm.runInContext(wrappedCode, sandbox);
299
281
 
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
- };
282
+ // Return the component
283
+ const Component = sandbox.__exports.default;
284
+
285
+ if (!Component) {
286
+ throw new Error("Svelte component did not export a default component");
287
+ }
288
+
289
+ return Component;
313
290
  } catch (error) {
314
- console.error("Error evaluating compiled component:", error);
315
- console.error("Code preview (first 500 chars):", code.substring(0, 500));
316
291
  throw error;
317
292
  }
318
293
  }
package/test/index.ts CHANGED
@@ -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