-
Notifications
You must be signed in to change notification settings - Fork 3.6k
feat(realtime): sockets + normalized tables + deprecate sync #523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b19a3dd
17b3eb2
98da771
838695f
540241d
8959327
5a594df
a9ab750
f89d531
f1a0d89
ffecd60
6a6c41d
7e774aa
c8359f2
4d84fa1
e8caad0
d0d0051
5eba967
66ab790
efdb893
1b3aab1
e6d6a44
05be333
5be3b2a
5edf105
5347abb
bc72199
7a8c049
09b12b3
c9f0d6d
dc47334
af1f61d
e0b40c7
d8dceac
9de5a34
dbb133f
af4e5ff
5fe1576
01ea300
511da56
2b9b667
10854e3
3ac202c
76f827a
7aff198
56d48e7
4203376
fdca2f9
5b3c583
868b0f2
835387e
8b76f41
cfe7aab
46c3936
7f4c740
89909f3
5bbb1cc
c08d16e
7e533c1
f4e8811
e65396e
8924c83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,13 +44,7 @@ const mobileSheetContainerVariants = { | |
|
|
||
| const mobileNavItemsContainerVariants = { | ||
| hidden: { opacity: 0 }, | ||
| visible: { | ||
| opacity: 1, | ||
| transition: { | ||
| delayChildren: 0.1, // Delay before starting stagger | ||
| staggerChildren: 0.08, // Stagger delay between items | ||
| }, | ||
| }, | ||
| visible: { opacity: 1 }, | ||
| } | ||
|
|
||
| const mobileNavItemVariants = { | ||
|
|
@@ -217,6 +211,7 @@ export default function NavClient({ | |
| variants={desktopNavContainerVariants} | ||
| initial='hidden' | ||
| animate='visible' | ||
| transition={{ delay: 0.2, duration: 0.3, ease: 'easeOut' }} | ||
| > | ||
| <NavLinks currentPath={currentPath} onContactClick={onContactClick} /> | ||
| </motion.div> | ||
|
|
@@ -265,6 +260,7 @@ export default function NavClient({ | |
| initial='hidden' | ||
| animate='visible' | ||
| exit='exit' | ||
| transition={{ duration: 0.3, ease: 'easeInOut' }} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Mobile sheet animation transition config could be extracted to a shared constant to maintain consistency with other similar animations. |
||
| className='fixed inset-y-0 right-0 z-50' | ||
| > | ||
| <SheetContent | ||
|
|
@@ -281,6 +277,10 @@ export default function NavClient({ | |
| variants={mobileNavItemsContainerVariants} | ||
| initial='hidden' | ||
| animate='visible' | ||
| transition={{ | ||
| delayChildren: 0.1, | ||
| staggerChildren: 0.08, | ||
| }} | ||
| > | ||
| <NavLinks | ||
| mobile | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { headers } from 'next/headers' | ||
| import { NextResponse } from 'next/server' | ||
| import { auth } from '@/lib/auth' | ||
|
|
||
| export async function POST() { | ||
| try { | ||
| const response = await auth.api.generateOneTimeToken({ | ||
| headers: await headers(), | ||
| }) | ||
|
|
||
| if (!response) { | ||
| return NextResponse.json({ error: 'Failed to generate token' }, { status: 500 }) | ||
| } | ||
|
|
||
| return NextResponse.json({ token: response.token }) | ||
| } catch (error) { | ||
| console.error('Error generating one-time token:', error) | ||
| return NextResponse.json({ error: 'Failed to generate token' }, { status: 500 }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid' | |
| import { createLogger } from '@/lib/logs/console-logger' | ||
| import { generateApiKey } from '@/lib/utils' | ||
| import { db } from '@/db' | ||
| import { apiKey, workflow } from '@/db/schema' | ||
| import { apiKey, workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@/db/schema' | ||
| import { validateWorkflowAccess } from '../../middleware' | ||
| import { createErrorResponse, createSuccessResponse } from '../../utils' | ||
|
|
||
|
|
@@ -126,7 +126,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ | |
| return createErrorResponse(validation.error.message, validation.error.status) | ||
| } | ||
|
|
||
| // Get the workflow to find the user and current state | ||
| // Get the workflow to find the user | ||
| const workflowData = await db | ||
| .select({ | ||
| userId: workflow.userId, | ||
|
|
@@ -142,8 +142,92 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ | |
| } | ||
|
|
||
| const userId = workflowData[0].userId | ||
| const currentState = workflowData[0].state | ||
|
|
||
| // Get the current live state from normalized tables instead of stale JSON | ||
| logger.debug(`[${requestId}] Getting current workflow state for deployment`) | ||
|
|
||
| // Get blocks from normalized table | ||
| const blocks = await db.select().from(workflowBlocks).where(eq(workflowBlocks.workflowId, id)) | ||
|
|
||
| // Get edges from normalized table | ||
| const edges = await db.select().from(workflowEdges).where(eq(workflowEdges.workflowId, id)) | ||
|
|
||
| // Get subflows from normalized table | ||
| const subflows = await db | ||
| .select() | ||
| .from(workflowSubflows) | ||
| .where(eq(workflowSubflows.workflowId, id)) | ||
|
|
||
| // Build current state from normalized data | ||
| const blocksMap: Record<string, any> = {} | ||
| const loops: Record<string, any> = {} | ||
| const parallels: Record<string, any> = {} | ||
|
|
||
| // Process blocks | ||
| blocks.forEach((block) => { | ||
| blocksMap[block.id] = { | ||
| id: block.id, | ||
| type: block.type, | ||
| name: block.name, | ||
| position: { x: Number(block.positionX), y: Number(block.positionY) }, | ||
| data: block.data, | ||
| enabled: block.enabled, | ||
| subBlocks: block.subBlocks || {}, | ||
| } | ||
| }) | ||
|
|
||
| // Process subflows (loops and parallels) | ||
| subflows.forEach((subflow) => { | ||
| const config = (subflow.config as any) || {} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Type assertion to 'any' should be avoided. Consider defining proper interface for config |
||
| if (subflow.type === 'loop') { | ||
| loops[subflow.id] = { | ||
| nodes: config.nodes || [], | ||
| iterationCount: config.iterationCount || 1, | ||
| iterationType: config.iterationType || 'fixed', | ||
| collection: config.collection || '', | ||
| } | ||
| } else if (subflow.type === 'parallel') { | ||
| parallels[subflow.id] = { | ||
| nodes: config.nodes || [], | ||
| parallelCount: config.parallelCount || 2, | ||
| collection: config.collection || '', | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| // Convert edges to the expected format | ||
| const edgesArray = edges.map((edge) => ({ | ||
| id: edge.id, | ||
| source: edge.sourceBlockId, | ||
| target: edge.targetBlockId, | ||
| sourceHandle: edge.sourceHandle, | ||
| targetHandle: edge.targetHandle, | ||
| type: 'default', | ||
| data: {}, | ||
| })) | ||
|
|
||
| const currentState = { | ||
| blocks: blocksMap, | ||
| edges: edgesArray, | ||
| loops, | ||
| parallels, | ||
| lastSaved: Date.now(), | ||
| } | ||
|
|
||
| logger.debug(`[${requestId}] Current state retrieved from normalized tables:`, { | ||
| blocksCount: Object.keys(blocksMap).length, | ||
| edgesCount: edgesArray.length, | ||
| loopsCount: Object.keys(loops).length, | ||
| parallelsCount: Object.keys(parallels).length, | ||
| }) | ||
|
|
||
| if (!currentState || !currentState.blocks) { | ||
| logger.error(`[${requestId}] Invalid workflow state retrieved`, { currentState }) | ||
| throw new Error('Invalid workflow state: missing blocks') | ||
| } | ||
|
|
||
| const deployedAt = new Date() | ||
| logger.debug(`[${requestId}] Proceeding with deployment at ${deployedAt.toISOString()}`) | ||
|
|
||
| // Check if the user already has an API key | ||
| const userApiKey = await db | ||
|
|
@@ -191,7 +275,13 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ | |
| logger.info(`[${requestId}] Workflow deployed successfully: ${id}`) | ||
| return createSuccessResponse({ apiKey: userKey, isDeployed: true, deployedAt }) | ||
| } catch (error: any) { | ||
| logger.error(`[${requestId}] Error deploying workflow: ${id}`, error) | ||
| logger.error(`[${requestId}] Error deploying workflow: ${id}`, { | ||
| error: error.message, | ||
| stack: error.stack, | ||
| name: error.name, | ||
| cause: error.cause, | ||
| fullError: error, | ||
| }) | ||
| return createErrorResponse(error.message || 'Failed to deploy workflow', 500) | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Transition config moved here from variant object. Consider extracting shared transition configs into constants for reuse.