Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion convex.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"node": {
"nodeVersion": "24"
"nodeVersion": "20"
},
"$schema": "./node_modules/convex/schemas/convex.schema.json"
}
44 changes: 39 additions & 5 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

/* eslint-disable */
/**
* Generated `api` utility.
*
Expand All @@ -8,8 +8,42 @@
* @module
*/

import type { AnyApi, AnyComponents } from "convex/server";
import type * as mastra_storage from "../mastra/storage.js";

import type {
ApiFromModules,
FilterApi,
FunctionReference,
} from "convex/server";

declare const fullApi: ApiFromModules<{
"mastra/storage": typeof mastra_storage;
}>;

/**
* A utility for referencing Convex functions in your app's public API.
*
* Usage:
* ```js
* const myFunctionReference = api.myModule.myFunction;
* ```
*/
export declare const api: FilterApi<
typeof fullApi,
FunctionReference<any, "public">
>;

/**
* A utility for referencing Convex functions in your app's internal API.
*
* Usage:
* ```js
* const myFunctionReference = internal.myModule.myFunction;
* ```
*/
export declare const internal: FilterApi<
typeof fullApi,
FunctionReference<any, "internal">
>;

export declare const api: AnyApi;
export declare const internal: AnyApi;
export declare const components: AnyComponents;
export declare const components: {};
4 changes: 2 additions & 2 deletions convex/mastra/storage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { mastraStorage } from '@mastra/convex/server'
import { mastraStorage } from '@mastra/convex/server';

export const handle = mastraStorage
export const handle = mastraStorage;
25 changes: 19 additions & 6 deletions convex/schema.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';
import {
mastraDocumentsTable,
mastraThreadsTable,
mastraMessagesTable,
mastraResourcesTable,
mastraScoresTable,
mastraThreadsTable,
mastraVectorIndexesTable,
mastraVectorsTable,
mastraWorkflowSnapshotsTable,
} from '@mastra/convex'
import { defineSchema } from 'convex/server'
mastraDocumentsTable,
} from '@mastra/convex/schema';

// Explicitly define mastra_workflow_snapshots to ensure the `id` field exists
// (some downstream Convex schema variants omit `id` which causes an invalid
// index declaration during Convex schema push).
const mastraWorkflowSnapshotsTable = defineTable({
id: v.string(),
workflow_name: v.string(),
run_id: v.string(),
resourceId: v.optional(v.string()),
snapshot: v.any(),
createdAt: v.string(),
updatedAt: v.string(),
}).index('by_record_id', ['id']).index('by_workflow_run', ['workflow_name', 'run_id']).index('by_workflow', ['workflow_name']).index('by_resource', ['resourceId']).index('by_created', ['createdAt']);

export default defineSchema({
mastra_threads: mastraThreadsTable,
Expand All @@ -19,4 +32,4 @@ export default defineSchema({
mastra_vector_indexes: mastraVectorIndexesTable,
mastra_vectors: mastraVectorsTable,
mastra_documents: mastraDocumentsTable,
})
});
11 changes: 6 additions & 5 deletions mdx-components.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { MDXComponents } from 'mdx/types'
import type { ReactNode } from 'react'
import Link from 'next/link'

// Custom components that work with MDX plugins
Expand Down Expand Up @@ -47,7 +48,7 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
// Links with hover effects
a: ({ href, children }) => (
<Link
href={href ?? '#'}
href={typeof href === 'string' ? href : '#'}
className="font-medium text-primary underline underline-offset-4 hover:text-primary/80 transition-colors"
>
{children}
Expand Down Expand Up @@ -75,8 +76,8 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
),

// Code blocks and inline code - these work with rehype-highlight
code: ({ children, className }) => {
const isInline = !className?.includes('language-')
code: ({ children, className }: { children?: ReactNode; className?: string }) => {
const isInline = typeof className !== 'string' || !className.includes('language-')
return isInline ? (
<code className="relative rounded bg-muted px-1.5 py-0.5 font-mono text-sm text-foreground">
{children}
Expand Down Expand Up @@ -116,9 +117,9 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
),

// Images with responsive design
img: ({ src, alt, title }) => (
img: ({ src, alt, title }: { src?: string; alt?: string; title?: string }) => (
<img
src={src}
src={src ?? undefined}
alt={alt ?? ''}
title={title}
className="my-4 rounded-lg border border-border"
Expand Down
50 changes: 48 additions & 2 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import createMDX from '@next/mdx'
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'
import type { NextConfig } from 'next'
import type { Configuration } from 'webpack'

const nextConfig: NextConfig = {
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
Expand Down Expand Up @@ -38,7 +39,7 @@ const nextConfig: NextConfig = {
typedRoutes: false,
reactStrictMode: true,
distDir: '.next',
webpack: (config, { isServer }) => {
webpack: (config: Configuration, { isServer }) => {
if (!isServer) {
config.plugins = config.plugins ?? []
config.plugins.push(
Expand All @@ -52,10 +53,55 @@ const nextConfig: NextConfig = {
],
})
)

// Sanitize resolve.alias so values are serializable for Turbopack/worker cloning
// Ensure resolve exists and normalize alias values to strings/arrays of strings
const existingResolve = config.resolve ?? {}
const aliasObj = (existingResolve as unknown as Record<string, unknown>).alias ?? {}
if (typeof aliasObj === 'object' && aliasObj !== null) {
const normalized: Record<string, string | string[]> = {}
for (const [k, v] of Object.entries(aliasObj as Record<string, unknown>)) {
if (Array.isArray(v)) {
normalized[k] = v.map((x) => String(x))
} else if (typeof v === 'object' && v !== null) {
// For objects, stringify to avoid '[object Object]' implicit string coercion
try {
normalized[k] = JSON.stringify(v)
} catch {
normalized[k] = ''
}
} else if (typeof v === 'function') {
// For functions, prefer the function name to avoid serializing the entire function
// Narrow to an object with an optional name property to avoid using the broad Function type
const fnName = (v as { name?: string })?.name ?? ''
normalized[k] = fnName
Comment on lines +76 to +77
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Normalizing function aliases to their function name string may break webpack resolution. Function values in webpack aliases typically need special handling, and converting them to strings (especially empty strings when name is unavailable) could cause module resolution failures.

Copilot uses AI. Check for mistakes.
} else if (v === null || v === undefined) {
// Keep null/undefined normalized to an empty string
normalized[k] = ''
} else {
// At this point, expect primitives (string/number/boolean/symbol/bigint).
// Guard against objects to avoid default Object stringification '[object Object]'.
const t = typeof v
if (t === 'string' || t === 'number' || t === 'boolean' || t === 'symbol' || t === 'bigint') {
// Narrow the type for the linter to avoid base-to-string coercion warnings
normalized[k] = String(v as string | number | boolean | symbol | bigint)
} else {
// Fallback for unexpected non-serializable values
try {
normalized[k] = JSON.stringify(v)
} catch {
normalized[k] = ''
}
}
}
}
config.resolve = { ...existingResolve, alias: normalized } as Configuration['resolve']
}
Comment on lines +56 to +99
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Alias normalization may break legitimate webpack alias functionality.

The sanitization logic converts all alias values to strings, but this approach has several issues:

  1. Object aliases (lines 66-72): Webpack supports object aliases with { name, alias, onlyModule } structure. Converting these to JSON strings will break the resolution.

  2. Function aliases (lines 73-77): Webpack uses function aliases for custom resolution logic. Replacing them with the function name string removes this functionality entirely.

  3. Empty string fallbacks (lines 78-80, 92-94): Setting aliases to empty strings may cause webpack to attempt resolving empty paths, leading to unexpected errors.

  4. Potential silent failures: Legitimate aliases might be silently converted to non-functional values without warnings.

Consider a more targeted approach that only removes truly non-serializable values while preserving valid string/path aliases:

💡 Suggested safer approach
             if (typeof aliasObj === 'object' && aliasObj !== null) {
-                const normalized: Record<string, string | string[]> = {}
+                const normalized: Record<string, string | string[] | false> = {}
                 for (const [k, v] of Object.entries(aliasObj as Record<string, unknown>)) {
                     if (Array.isArray(v)) {
-                        normalized[k] = v.map((x) => String(x))
+                        // Only include if all elements are strings
+                        if (v.every((x) => typeof x === 'string')) {
+                            normalized[k] = v as string[]
+                        }
+                        // Skip non-string arrays (non-serializable)
-                    } else if (typeof v === 'object' && v !== null) {
-                        // For objects, stringify to avoid '[object Object]' implicit string coercion
-                        try {
-                            normalized[k] = JSON.stringify(v)
-                        } catch {
-                            normalized[k] = ''
-                        }
-                    } else if (typeof v === 'function') {
-                        // For functions, prefer the function name to avoid serializing the entire function
-                        // Narrow to an object with an optional name property to avoid using the broad Function type
-                        const fnName = (v as { name?: string })?.name ?? ''
-                        normalized[k] = fnName
-                    } else if (v === null || v === undefined) {
-                        // Keep null/undefined normalized to an empty string
-                        normalized[k] = ''
-                    } else {
-                        // At this point, expect primitives (string/number/boolean/symbol/bigint).
-                        // Guard against objects to avoid default Object stringification '[object Object]'.
-                        const t = typeof v
-                        if (t === 'string' || t === 'number' || t === 'boolean' || t === 'symbol' || t === 'bigint') {
-                            // Narrow the type for the linter to avoid base-to-string coercion warnings
-                            normalized[k] = String(v as string | number | boolean | symbol | bigint)
-                        } else {
-                            // Fallback for unexpected non-serializable values
-                            try {
-                                normalized[k] = JSON.stringify(v)
-                            } catch {
-                                normalized[k] = ''
-                            }
-                        }
+                    } else if (typeof v === 'string') {
+                        normalized[k] = v
+                    } else if (v === false) {
+                        // `false` is valid in webpack to ignore a module
+                        normalized[k] = false
                     }
+                    // Skip functions, objects, and other non-serializable values
+                    // Log warning for skipped aliases in development
                 }
                 config.resolve = { ...existingResolve, alias: normalized } as Configuration['resolve']
             }
🤖 Prompt for AI Agents
In `@next.config.ts` around lines 56 - 99, The current alias sanitization in the
config.resolve block (aliasObj -> normalized -> config.resolve) indiscriminately
converts object and function aliases to strings and replaces null/invalid
entries with empty strings, which breaks valid webpack alias types; instead,
update the loop that builds normalized to: detect and preserve valid webpack
alias shapes (keep objects that match { name, alias, onlyModule } or other
object aliases), keep functions (do not stringify) but mark them as
non-cloneable so Turbopack gets a placeholder/removed entry, and only remove or
replace truly non-serializable values (e.g., circular objects) by deleting the
key or setting it to undefined; also emit a warning (console.warn or
processLogger.warn) mentioning the alias key when you drop/replace an alias so
failures aren’t silent. Ensure you update the code paths referencing aliasObj,
normalized, and config.resolve accordingly so only unsupported values are
stripped and valid objects/functions are preserved.

}

return config
},

typescript: {
ignoreBuildErrors: true,
tsconfigPath: './tsconfig.json',
Expand Down Expand Up @@ -92,7 +138,7 @@ const nextConfig: NextConfig = {
// optimizeCss: true,
esmExternals: true,
scrollRestoration: true,
// cpus: 16,
// cpus: 16,
// cssChunking: true,
// craCompat: true,
// validateRSCRequestHeaders: true,
Expand Down
Loading
Loading