Skip to content

feat(svelte): Add Svelte 5 bindings for persistent text streaming#13

Open
stickerdaniel wants to merge 1 commit intoget-convex:mainfrom
stickerdaniel:feature/svelte-bindings
Open

feat(svelte): Add Svelte 5 bindings for persistent text streaming#13
stickerdaniel wants to merge 1 commit intoget-convex:mainfrom
stickerdaniel:feature/svelte-bindings

Conversation

@stickerdaniel
Copy link

@stickerdaniel stickerdaniel commented Nov 26, 2025

Adds useStream hook for Svelte 5, mirroring
the React implementation. Includes a svekte example app
demonstrating usage.

Resolves: #10


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Summary by CodeRabbit

  • New Features

    • Added a complete Svelte 5 chat application example with real-time messaging, AI-powered responses, and markdown rendering.
    • Introduced a streaming hook for building persistent text streaming features in Svelte applications.
  • Documentation

    • Added setup instructions and usage guide for the Svelte example project.

✏️ Tip: You can customize this high-level summary in your review settings.

Adds useStream hook for Svelte 5, mirroring
the React implementation. Includes a svekte example app
demonstrating usage.

Resolves: get-convex#10
@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

Walkthrough

A Svelte 5 hook for persistent-text-streaming orchestration is introduced alongside a comprehensive example application and package exports enabling Svelte developers to integrate streaming chat functionality with optional Convex dependencies.

Changes

Cohort / File(s) Change Summary
Svelte Hook Implementation
src/svelte/index.svelte.ts
New useStream hook managing persistent-text-streaming lifecycle with reactive state, supporting both persistence-layer and HTTP streaming modes, driving state checks, and error handling.
Package Configuration & Exports
package.json, tsconfig.build.json
Added dev:svelte script, new ./svelte export entry with types/svelte/default targets, peerDependencies for convex-svelte/svelte (marked optional), devDependencies for tooling, and excluded src/svelte/**/* from build.
Example Svelte Application - Core & Setup
example-svelte/README.md, example-svelte/package.json, example-svelte/index.html, example-svelte/svelte.config.js, example-svelte/tsconfig.json, example-svelte/vite.config.ts
Documentation, project metadata, HTML entry point, and build/type configurations for a Svelte 5 example with Vite, Tailwind, and TypeScript.
Example Svelte Application - Source Code
example-svelte/src/App.svelte, example-svelte/src/main.ts, example-svelte/src/app.css, example-svelte/src/vite-env.d.ts
Svelte app root component, TypeScript entry point, Tailwind styling, and Vite environment type declarations.
Example Svelte Application - Components
example-svelte/src/components/ChatWindow.svelte, example-svelte/src/components/MessageItem.svelte, example-svelte/src/components/ServerMessage.svelte
Chat UI components for message rendering (user/AI differentiation), streaming response handling, and form interaction with Convex API integration.
Example Svelte Application - Utilities
example-svelte/src/lib/utils.ts
Utility functions for className composition (cn) and Convex site URL derivation (getConvexSiteUrl).
Example Svelte Application - Backend Reference
example-svelte/convex
Relative path reference to backend Convex configuration.

Sequence Diagram(s)

sequenceDiagram
    participant UI as ChatWindow Component
    participant Hook as useStream Hook
    participant Persistence as Persistence Layer
    participant HTTP as HTTP Stream Endpoint
    
    UI->>Hook: Call useStream(query, url, getDriven, getStreamId)
    
    alt streamEnded is false OR not driven
        Hook->>Persistence: Return cached persistent body
        Persistence-->>Hook: StreamBody (if available)
    else Driven and streamId available
        Hook->>HTTP: POST /stream (startStreaming)
        HTTP-->>Hook: ReadableStream response
        
        par Stream Reading
            Hook->>Hook: Read chunks from stream
            Hook->>Hook: Update reactive streamBody state
        and State Management
            Hook->>Hook: Derive status (pending→streaming→done)
        end
        
        alt Stream Success
            Hook-->>UI: text + status: "done"
        else Stream Error
            Hook-->>UI: text + status: "error"
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • src/svelte/index.svelte.ts — Focus area: complex streaming logic with multiple reactive state transitions, HTTP handling, and error cases requires careful verification of state machine correctness.
  • example-svelte/src/components/ChatWindow.svelte — Moderate complexity: DOM lifecycle management, input focus handling, scroll-to-bottom logic, and Convex query/mutation wiring.
  • example-svelte/src/components/ServerMessage.svelte — Review streaming response rendering, markdown parsing with marked, and side-effect triggers.
  • Configuration files (package.json exports, vite.config.ts, tsconfig.json) — Verify path aliases and build settings are correct.

Poem

🐰 With Svelte's grace and streams that flow,
Persistent text now steals the show!
A hook that dances, state-driven and true,
Chat components render your conversations anew! ✨
From React to Svelte, the bindings now bind,
Streaming adventures awaiting your mind! 🌊

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding Svelte 5 bindings for persistent text streaming, which is the primary focus of the changeset.
Linked Issues check ✅ Passed The PR fulfills issue #10 requirements: provides Svelte bindings mirroring React functionality at src/svelte/index.svelte.ts, exports via package.json exports with ./svelte entry, and includes a complete example app.
Out of Scope Changes check ✅ Passed All changes align with issue #10 scope: Svelte hook implementation, package exports setup, example application, and necessary configuration files. No unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
src/svelte/index.svelte.ts (2)

164-177: Instantiate TextDecoder once outside the loop.

Creating a new TextDecoder instance on every chunk read is inefficient. Additionally, when done is true, value may be undefined, so decoding it on line 169 could produce an empty string unnecessarily.

+  const decoder = new TextDecoder();
   const reader = response.body.getReader();
   while (true) {
     try {
       const { done, value } = await reader.read();
       if (done) {
-        onUpdate(new TextDecoder().decode(value));
         return true;
       }
-      onUpdate(new TextDecoder().decode(value));
+      onUpdate(decoder.decode(value, { stream: true }));
     } catch (e) {
       console.error("Error reading stream", e);
       return false;
     }
   }

Note: The { stream: true } option ensures proper handling of multi-byte characters that may be split across chunks.


143-150: Consider adding error handling for network failures.

The fetch call can throw on network errors (e.g., DNS failure, connection refused). Currently, any exception would propagate uncaught from the async IIFE on line 74, potentially leaving streamEnded as null indefinitely.

 async function startStreaming(
   url: URL,
   streamId: StreamId,
   onUpdate: (text: string) => void,
   headers: Record<string, string>,
 ) {
+  let response: Response;
+  try {
-  const response = await fetch(url, {
+    response = await fetch(url, {
       method: "POST",
       body: JSON.stringify({
         streamId: streamId,
       }),
       headers: { "Content-Type": "application/json", ...headers },
     });
+  } catch (e) {
+    console.error("Network error reaching streaming endpoint", e);
+    return false;
+  }
example-svelte/src/App.svelte (1)

5-6: Add validation for the required environment variable.

If VITE_CONVEX_URL is not set, setupConvex will receive undefined, which could cause cryptic runtime errors. Consider adding a guard or throwing a helpful error message.

   const convexUrl = import.meta.env.VITE_CONVEX_URL;
+  if (!convexUrl) {
+    throw new Error("VITE_CONVEX_URL environment variable is not set");
+  }
   setupConvex(convexUrl);
example-svelte/src/components/ServerMessage.svelte (1)

33-37: Simplify effect logic to avoid redundant checks.

The effect checks both !isDriven and !isCurrentlyStreaming, but isCurrentlyStreaming already incorporates the isDriven check. This creates redundancy.

Consider this simpler version:

  // Stop streaming when done
  $effect(() => {
-    if (!isDriven) return;
-    if (isCurrentlyStreaming) return;
-    stopStreaming();
+    if (isDriven && !isCurrentlyStreaming) {
+      stopStreaming();
+    }
  });
example-svelte/src/components/ChatWindow.svelte (2)

51-53: Consider performance impact of object spread on every message.

Each message submission creates a new drivenIds object via spread operator. While this ensures Svelte reactivity, it could impact performance with many messages since it copies all existing entries.

Consider using a Map for better performance with large chat histories:

-  let drivenIds = $state<Record<string, boolean>>({});
+  let drivenIds = $state(new Map<string, boolean>());

  function isDriven(id: string): boolean {
-    return drivenIds[id] === true;
+    return drivenIds.get(id) === true;
  }

  async function handleSubmit(e: SubmitEvent) {
    // ...
-    drivenIds = { ...drivenIds, [chatId]: true };
+    drivenIds.set(chatId, true);
+    drivenIds = drivenIds; // Trigger reactivity
    isStreaming = true;
  }

118-125: Clarify the minimum message requirement for Clear Chat button.

The Clear Chat button requires at least 2 messages (messages.data.length < 2), which seems arbitrary. Typically, a clear button would be enabled whenever messages exist.

Consider simplifying the logic:

          <button
            type="button"
-            disabled={!messages.data || messages.data.length < 2 || isStreaming}
+            disabled={!messages.data || messages.data.length === 0 || isStreaming}
            onclick={clearAllMessages}

Or if the 2-message minimum is intentional (e.g., one user + one AI response), add a comment explaining the rationale.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26db03d and 40cf46c.

⛔ Files ignored due to path filters (1)
  • example-svelte/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • example-svelte/README.md (1 hunks)
  • example-svelte/convex (1 hunks)
  • example-svelte/index.html (1 hunks)
  • example-svelte/package.json (1 hunks)
  • example-svelte/src/App.svelte (1 hunks)
  • example-svelte/src/app.css (1 hunks)
  • example-svelte/src/components/ChatWindow.svelte (1 hunks)
  • example-svelte/src/components/MessageItem.svelte (1 hunks)
  • example-svelte/src/components/ServerMessage.svelte (1 hunks)
  • example-svelte/src/lib/utils.ts (1 hunks)
  • example-svelte/src/main.ts (1 hunks)
  • example-svelte/src/vite-env.d.ts (1 hunks)
  • example-svelte/svelte.config.js (1 hunks)
  • example-svelte/tsconfig.json (1 hunks)
  • example-svelte/vite.config.ts (1 hunks)
  • package.json (4 hunks)
  • src/svelte/index.svelte.ts (1 hunks)
  • tsconfig.build.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/svelte/index.svelte.ts (2)
src/client/index.ts (2)
  • StreamBody (15-18)
  • StreamId (13-13)
src/component/schema.ts (1)
  • StreamStatus (11-11)
🔇 Additional comments (17)
example-svelte/src/vite-env.d.ts (1)

1-10: LGTM!

Standard Vite environment type declarations. The VITE_CONVEX_URL typing aligns with its usage in utils.ts.

example-svelte/index.html (1)

1-13: LGTM!

Standard Vite HTML entry point with proper meta tags and module script loading.

example-svelte/src/lib/utils.ts (1)

4-6: LGTM!

Standard cn utility combining clsx with twMerge for Tailwind class composition.

tsconfig.build.json (1)

4-4: Verify that excluding src/svelte/**/* is intentional.

Excluding src/svelte/**/* from the build will prevent the Svelte hook from being compiled and included in the published package. According to issue #10, the Svelte implementation should be exported as @convex-dev/persistent-text-streaming/svelte.

If the Svelte source requires separate compilation (e.g., via a different build step for Svelte components), this is fine. Otherwise, this exclusion may prevent the Svelte bindings from being available to consumers. This requires verification of:

  • Whether src/svelte/ contains source files meant for publication
  • How the package exports are configured in package.json
  • Whether there's a separate build configuration for Svelte bindings
src/svelte/index.svelte.ts (3)

1-7: LGTM!

Imports are clean and correctly reference the component schema, convex-svelte, and client types.


25-41: LGTM!

Well-documented function signature with clear JSDoc explaining each parameter. The use of getter functions (getDriven, getStreamId) is appropriate for Svelte 5's reactivity model.


65-91: I'll help you verify the review comment about cleanup behavior in the Svelte component. Let me start by examining the actual code and understanding the Svelte 5 $effect cleanup patterns.
<function_calls>


#!/bin/bash

First, let's examine the file in question

head -100 src/svelte/index.svelte.ts | cat -n


</function_calls>
<function_calls>


Svelte 5 $effect cleanup function abort controller pattern documentation


</function_calls>

example-svelte/src/app.css (1)

1-16: LGTM!

Clean CSS setup with Tailwind v4 import syntax. The root styles establish a sensible dark theme default with good font rendering optimizations.

example-svelte/package.json (1)

1-27: LGTM!

Package configuration is well-structured for an example application. The file:.. reference correctly links to the parent package for local development.

example-svelte/svelte.config.js (1)

1-5: LGTM!

Standard minimal Svelte configuration with vitePreprocess for TypeScript support.

example-svelte/tsconfig.json (1)

1-22: LGTM!

The TypeScript configuration is appropriate for a Svelte 5 project with strict type checking, proper module resolution, and path aliases that align with the Vite configuration.

example-svelte/README.md (1)

1-59: LGTM!

The documentation clearly explains the setup process, features, and usage patterns. The code example accurately demonstrates the useStream hook API, and the note about getter functions for Svelte reactivity is helpful.

example-svelte/vite.config.ts (1)

1-15: LGTM!

The Vite configuration is appropriate for a Svelte 5 + Tailwind CSS v4 project. The envDir pointing to the parent directory allows sharing environment variables with the main example, and the alias configuration matches the TypeScript paths.

example-svelte/src/components/MessageItem.svelte (1)

1-53: LGTM!

The component correctly uses Svelte 5 features ($props, $derived, Snippet) and provides a clean message display layout. The conditional rendering and styling logic is appropriate.

package.json (1)

77-89: Good practice: marking framework dependencies as optional.

The peerDependenciesMeta correctly marks all framework-specific dependencies (React, React DOM, Svelte, convex-svelte) as optional, allowing users to install only the frameworks they need. This is the right approach for a multi-framework library.

example-svelte/src/components/ChatWindow.svelte (1)

31-37: LGTM on resize listener cleanup.

The window resize listener effect correctly returns a cleanup function to remove the event listener, preventing memory leaks.

example-svelte/src/components/ServerMessage.svelte (1)

46-48: I'll help you verify the review comment about XSS safety and marked.parse. Let me investigate the actual code and the marked library behavior.
<function_calls>


#!/bin/bash

First, let's read the ServerMessage.svelte file to see the actual code

cat -n example-svelte/src/components/ServerMessage.svelte


</function_calls>
<function_calls>


#!/bin/bash

Check the package.json to see marked version

cat example-svelte/package.json | grep -A 5 -B 5 '"marked"'


</function_calls>
<function_calls>

marked.parse promise return value synchronous async

</function_calls>
<function_calls>


#!/bin/bash

Search for DOMPurify usage in the codebase

rg -i "dompurify" -n --type ts --type js --type svelte


</function_calls>

@@ -0,0 +1 @@
../example/convex No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Script executed:

#!/bin/bash
# Check if the symlink exists and its target
echo "=== Checking symlink ==="
ls -la example-svelte/convex 2>/dev/null || echo "Symlink not found"

echo ""
echo "=== Git symlink configuration ==="
git config --get core.symlinks

echo ""
echo "=== Verify target directory exists ==="
if [ -d "example/convex" ]; then
  echo "Target directory exists"
  echo "Target contents:"
  ls example/convex/ 2>/dev/null || echo "Cannot list target"
else
  echo "Target directory 'example/convex' not found"
fi

echo ""
echo "=== Check Git tracking of symlink ==="
git ls-files example-svelte/convex

echo ""
echo "=== Check if example-svelte directory exists ==="
ls -la example-svelte/ 2>/dev/null | head -20

Repository: get-convex/persistent-text-streaming


<shell_result>
Repository: get-convex/persistent-text-streaming
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository get-convex/persistent-text-streaming: Clone operation failed
</shell_result>


symlinks git repository cross-platform windows core.symlinks


<web_search_result>
Short answer

  • Git stores symlinks in the repo as special blob entries; whether the working tree contains real OS symlinks is controlled by core.symlinks. [1]
  • On Windows Git will often set core.symlinks = false for a new repo (or a client may ignore your global setting), so symlinks are checked out as small text files containing the link target. [1][2][3]

What to do (practical steps)

  1. Ensure Windows allows creating symlinks:
    • Enable Developer Mode (Windows Settings → For developers) or use an elevated prompt on older Windows. [2][3]
  2. Install / re-install Git for Windows with the "Enable symbolic links" option (if present) and/or use the system Git (not an embedded Git in a GUI) so symlink support is available. [2]
  3. In the repo set core.symlinks = true (global or per-repo):
    • git config --global core.symlinks true
    • or inside the repo: git config core.symlinks true
  4. If you change core.symlinks after clone, recreate the worktree links:
    • git reset --hard
    • (or reclone after changing the setting) — some clients (e.g., GitHub Desktop) may ignore global settings when creating a repo, so reclone with the correct Git/client is easiest. [2][3]

Notes / caveats

  • core.symlinks is both a request and a cached capability flag: git-clone/git-init probe the environment and may write symlinks=false into the repo config if symlinks look unsupported. Manually overriding may be necessary but some GUIs still create repos with symlinks disabled. [1][2]
  • If teammates use different OSes or clients, prefer avoiding repository symlink reliance when possible (or document/standardize the required Windows settings). [1][3]

Sources

  • git-config (core.symlinks) documentation. [1]
  • Explanation and behavior on Windows / why clone may set symlinks=false (Stack Overflow). [2]
  • Reports of GUI clients ignoring global symlink settings (GitHub Desktop issues / discussions). [3]
    </web_search_result>

Symlink for sharing Convex backend — works but requires cross-platform setup documentation.

This symlink allows the Svelte example to reuse the existing Convex backend from the React example, avoiding duplication. However, symlinks have documented cross-platform issues:

  • On Windows, Git often defaults to core.symlinks=false, causing symlinks to be checked out as text files rather than actual symlinks
  • GitHub Desktop and some other GUI clients ignore global symlink settings and may create repositories with symlink support disabled
  • Team members on different operating systems may have inconsistent experiences unless setup is standardized

Recommendation: Document the symlink requirement in a setup guide or README, specifying that Windows developers should enable Developer Mode and configure git config core.symlinks true, or consider providing a non-symlink alternative (copy or build step) for better cross-platform compatibility.

🤖 Prompt for AI Agents
In example-svelte/convex around line 1, the repository uses a filesystem symlink
to ../example/convex which can break on Windows and other environments; update
project setup to avoid platform inconsistencies by either (a) adding clear
documentation in example-svelte/README.md indicating the symlink requirement and
exact steps for Windows (enable Developer Mode and run git config core.symlinks
true) and any GUI client caveats, or (b) replace the symlink with a
cross-platform alternative such as copying the Convex backend into the example
during a setup script or adding a build step that links/duplicates the backend
content at install time; implement one of these options and ensure README lists
the chosen approach and commands to reproduce the link locally.

Comment on lines +8 to +21
export function getConvexSiteUrl() {
let convexSiteUrl;
if (import.meta.env.VITE_CONVEX_URL.includes(".cloud")) {
convexSiteUrl = import.meta.env.VITE_CONVEX_URL.replace(
/\.cloud$/,
".site",
);
} else {
const url = new URL(import.meta.env.VITE_CONVEX_URL);
url.port = String(Number(url.port) + 1);
convexSiteUrl = url.toString();
}
return convexSiteUrl;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential edge case with implicit ports.

On line 17, if the URL uses an implicit port (e.g., https://example.com without an explicit port), url.port will be an empty string, causing Number("") + 1 = 1, resulting in an invalid port.

This is likely fine since local Convex development URLs typically have explicit ports, but worth noting if this utility is used more broadly.

 export function getConvexSiteUrl() {
   let convexSiteUrl;
   if (import.meta.env.VITE_CONVEX_URL.includes(".cloud")) {
     convexSiteUrl = import.meta.env.VITE_CONVEX_URL.replace(
       /\.cloud$/,
       ".site",
     );
   } else {
     const url = new URL(import.meta.env.VITE_CONVEX_URL);
-    url.port = String(Number(url.port) + 1);
+    const currentPort = url.port || (url.protocol === "https:" ? "443" : "80");
+    url.port = String(Number(currentPort) + 1);
     convexSiteUrl = url.toString();
   }
   return convexSiteUrl;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getConvexSiteUrl() {
let convexSiteUrl;
if (import.meta.env.VITE_CONVEX_URL.includes(".cloud")) {
convexSiteUrl = import.meta.env.VITE_CONVEX_URL.replace(
/\.cloud$/,
".site",
);
} else {
const url = new URL(import.meta.env.VITE_CONVEX_URL);
url.port = String(Number(url.port) + 1);
convexSiteUrl = url.toString();
}
return convexSiteUrl;
}
export function getConvexSiteUrl() {
let convexSiteUrl;
if (import.meta.env.VITE_CONVEX_URL.includes(".cloud")) {
convexSiteUrl = import.meta.env.VITE_CONVEX_URL.replace(
/\.cloud$/,
".site",
);
} else {
const url = new URL(import.meta.env.VITE_CONVEX_URL);
const currentPort = url.port || (url.protocol === "https:" ? "443" : "80");
url.port = String(Number(currentPort) + 1);
convexSiteUrl = url.toString();
}
return convexSiteUrl;
}
🤖 Prompt for AI Agents
In example-svelte/src/lib/utils.ts around lines 8 to 21, the code increments
url.port without handling the case where url.port is an empty string (implicit
port), which makes Number("") produce 0 leading to an incorrect port; change the
logic to detect an empty or non-numeric port, pick a sensible default based on
url.protocol (use 443 for https:, 80 for http:), convert that to a Number, add
1, and assign the resulting value to url.port as a string; also guard against
NaN by falling back to a safe default (e.g., 443 for https, 80 for http) before
incrementing.

Comment on lines +5 to +7
const app = mount(App, {
target: document.getElementById("app")!,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add defensive check for missing mount target.

The non-null assertion operator (!) assumes the #app element always exists. If the element is missing from the HTML, this will throw a runtime error.

Apply this diff to add a defensive check:

-const app = mount(App, {
-  target: document.getElementById("app")!,
-});
+const appElement = document.getElementById("app");
+if (!appElement) {
+  throw new Error("Could not find element with id 'app'");
+}
+
+const app = mount(App, {
+  target: appElement,
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const app = mount(App, {
target: document.getElementById("app")!,
});
const appElement = document.getElementById("app");
if (!appElement) {
throw new Error("Could not find element with id 'app'");
}
const app = mount(App, {
target: appElement,
});
🤖 Prompt for AI Agents
In example-svelte/src/main.ts around lines 5 to 7, the mount call uses a
non-null assertion on document.getElementById("app") which will throw if the
element is missing; replace it with a defensive check: retrieve the element into
a variable, if it's null either throw a clear Error("Mount target '#app' not
found") or create/append a fallback container, then pass that validated target
to mount(App, { target }). Ensure the message is descriptive and avoid using the
non-null assertion operator.

Comment on lines +52 to +56
"./svelte": {
"types": "./src/svelte/index.svelte.ts",
"svelte": "./src/svelte/index.svelte.ts",
"default": "./src/svelte/index.svelte.ts"
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Script executed:

#!/bin/bash
# First, let's read the package.json to see the complete export structure
cat -n package.json | head -100

Repository: get-convex/persistent-text-streaming


<shell_result>
Repository: get-convex/persistent-text-streaming
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository get-convex/persistent-text-streaming: Clone operation failed
</shell_result>


Svelte library packaging conventions .svelte.ts exports


<web_search_result>
Short summary — key conventions for packaging Svelte libraries (including .svelte.ts / .svelte.js module files)

  • Package exports: use package.json "exports" to map entry paths to files and conditional targets. For Svelte components use the "svelte" export condition and provide a "types" condition for TypeScript definitions. Example root export:
    {
    "exports": {
    ".": { "types": "./dist/index.d.ts", "svelte": "./dist/index.js" }
    }
    }
    This also supports per-file exports (e.g. "./Foo.svelte"). [1]

  • What to ship: prefer shipping uncompiled .svelte component files (so Svelte-aware tooling can compile them for consumers). JS/TS module helpers may be ESM. Ensure any JS is ESM and file extensions obey Node/Esm rules. [1][2]

  • .svelte.ts / .svelte.js files: Svelte treats these like normal .ts/.js modules but with Svelte "runes" support — they behave as modules (useful for shared reactive logic). They can be included in your src and packaged; when built, .ts files should produce corresponding .js and .d.ts outputs that your exports map points to. [2]

  • Types: publish type definitions and point the "types" export condition to the generated .d.ts files. Be aware TypeScript resolves some deep imports differently — for non-root exports you may need extra care so consumers can find the .d.ts (see SvelteKit packaging notes). [1]

  • Practical rules:

    • Add "svelte" export condition and keep the legacy "svelte" package.json field for older tooling if needed. [1]
    • Include dist (and optionally src for declaration maps) in "files".
    • Ensure correct extension and module type (".mjs"/".cjs" or "type":"module") and fully‑specified relative imports in output. [1]

Sources

  • SvelteKit packaging (exports, types, shipping .svelte files, declaration maps). [1]
  • .svelte.js and .svelte.ts docs (behavior of .svelte.ts/.svelte.js module files). [2]

References
[1] SvelteKit — Packaging / Anatomy of package.json (exports, types, svelte, declaration maps).
[2] Svelte docs — .svelte.js and .svelte.ts files.
</web_search_result>


Source file exports for Svelte are unconventional; exports should point to compiled dist/ files.

The search confirms that standard Svelte packaging practice uses the "svelte" export condition pointing to compiled .js files in dist/, not source .svelte.ts files. The web search states:

For Svelte components use the "svelte" export condition and provide a "types" condition for TypeScript definitions...when built, .ts files should produce corresponding .js and .d.ts outputs that your exports map points to.

The review comment is correct: lines 53–55 should export compiled output from dist/svelte/ rather than source files from src/svelte/. This forces consumers to handle TypeScript compilation themselves and deviates from the React export pattern (which uses dist/).

Suggested fix:

    "./svelte": {
-      "types": "./src/svelte/index.svelte.ts",
-      "svelte": "./src/svelte/index.svelte.ts",
-      "default": "./src/svelte/index.svelte.ts"
+      "types": "./dist/svelte/index.svelte.d.ts",
+      "svelte": "./dist/svelte/index.js",
+      "default": "./dist/svelte/index.js"
    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"./svelte": {
"types": "./src/svelte/index.svelte.ts",
"svelte": "./src/svelte/index.svelte.ts",
"default": "./src/svelte/index.svelte.ts"
},
"./svelte": {
"types": "./dist/svelte/index.svelte.d.ts",
"svelte": "./dist/svelte/index.js",
"default": "./dist/svelte/index.js"
},
🤖 Prompt for AI Agents
In package.json around lines 52 to 56, the Svelte export entries point to source
files under src/svelte (./src/svelte/index.svelte.ts) instead of compiled
outputs; update those exports to reference the compiled artifacts in dist/svelte
— set "svelte" and "default" to the built .js entry in dist/svelte (e.g.
./dist/svelte/index.js) and set "types" to the generated .d.ts (e.g.
./dist/svelte/index.d.ts); ensure the build produces those files and update
package.json exports to match the dist paths.

},
"peerDependencies": {
"convex": "^1.24.8",
"convex-svelte": "^0.0.12",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

convex-svelte npm package latest version 0.0.12 production-ready stable


<web_search_result>
I couldn't find any convex-svelte release 0.0.12 — the npm registry shows latest published version 0.0.11. [1]

  • Latest published version: 0.0.11. [1]
  • Source / repo: get-convex/convex-svelte on GitHub. [2]
  • Context: Convex core reached 1.0, but convex-svelte itself is still in 0.x (pre‑1.0) and therefore may not carry the same stability/semver guarantees as Convex core. Use caution for production use and test before deploying. [3][2]

If you want, I can watch the package and notify you if 0.0.12 is published, or check specific compatibility details with your Svelte/SvelteKit version.

Sources:
[1] npm package page for convex-svelte.
[2] get-convex/convex-svelte GitHub repository.
[3] Convex 1.0 announcement / docs.
</web_search_result>


Version 0.0.12 of convex-svelte does not exist in the npm registry.

The latest published version is 0.0.11. The package.json declares "convex-svelte": "^0.0.12", which npm will fail to resolve since no version ≥0.0.12 exists. Additionally, convex-svelte remains in pre-1.0 (currently 0.0.11), meaning it lacks production-level stability guarantees despite Convex core reaching 1.0. Update to "^0.0.11" or verify the correct version target.

🤖 Prompt for AI Agents
In package.json around line 72, the dependency "convex-svelte": "^0.0.12"
references a non-existent npm version; change it to a valid published version
(for example "^0.0.11") or the intended version, update the package.json entry
accordingly, then run your package manager (npm install / yarn install) to
refresh package-lock.json or yarn.lock and commit the lockfile changes so CI can
resolve the dependency.

Comment on lines +11 to +28
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tailwindcss/vite": "^4.1.0",
"svelte": "^5.33.0",
"tailwindcss": "^4.1.0",
"typescript": "^5.7.0",
"vite": "^6.2.0"
},
"dependencies": {
"@convex-dev/persistent-text-streaming": "file:..",
"clsx": "^2.1.1",
"convex": "^1.29.0",
"convex-svelte": "^0.0.12",
"marked": "^15.0.0",
"tailwind-merge": "^3.3.0"
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to put these dependencies in the parent so there's only one node_modules folder to deal with?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] Add Svelte bindings for persistent-text-streaming

2 participants