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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 21 additions & 0 deletions .claude/rules/global.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,26 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
## Styling
Never update global styles. Keep all styling local to components.

## ID Generation
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`:

- `generateId()` — UUID v4, use by default
- `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers

Both use `crypto.getRandomValues()` under the hood and work in all contexts including non-secure (HTTP) browsers.

```typescript
// ✗ Bad
import { nanoid } from 'nanoid'
import { v4 as uuidv4 } from 'uuid'
const id = crypto.randomUUID()

// ✓ Good
import { generateId, generateShortId } from '@/lib/core/utils/uuid'
const uuid = generateId()
const shortId = generateShortId()
const tiny = generateShortId(8)
```

## Package Manager
Use `bun` and `bunx`, not `npm` and `npx`.
21 changes: 21 additions & 0 deletions .cursor/rules/global.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,26 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
## Styling
Never update global styles. Keep all styling local to components.

## ID Generation
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`:

- `generateId()` — UUID v4, use by default
- `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers

Both use `crypto.getRandomValues()` under the hood and work in all contexts including non-secure (HTTP) browsers.

```typescript
// ✗ Bad
import { nanoid } from 'nanoid'
import { v4 as uuidv4 } from 'uuid'
const id = crypto.randomUUID()

// ✓ Good
import { generateId, generateShortId } from '@/lib/core/utils/uuid'
const uuid = generateId()
const shortId = generateShortId()
const tiny = generateShortId(8)
```

## Package Manager
Use `bun` and `bunx`, not `npm` and `npx`.
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ You are a professional software engineer. All code must follow best practices: a
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
- **Styling**: Never update global styles. Keep all styling local to components
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`

## Architecture
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ You are a professional software engineer. All code must follow best practices: a
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
- **Styling**: Never update global styles. Keep all styling local to components
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`

## Architecture
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/a2a/agents/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { a2aAgent, workflow } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, isNull, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { generateSkillsFromWorkflow } from '@/lib/a2a/agent-card'
import { A2A_DEFAULT_CAPABILITIES } from '@/lib/a2a/constants'
import { sanitizeAgentName } from '@/lib/a2a/utils'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateId } from '@/lib/core/utils/uuid'
import { captureServerEvent } from '@/lib/posthog/server'
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
import { hasValidStartBlockInState } from '@/lib/workflows/triggers/trigger-utils'
Expand Down Expand Up @@ -173,7 +173,7 @@ export async function POST(request: NextRequest) {
skillTags
)

const agentId = uuidv4()
const agentId = generateId()
const agentName = name || sanitizeAgentName(wf.name)

const [agent] = await db
Expand Down
12 changes: 6 additions & 6 deletions apps/sim/app/api/a2a/serve/[agentId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { a2aAgent, a2aPushNotificationConfig, a2aTask, workflow } from '@sim/db/
import { createLogger } from '@sim/logger'
import { and, eq, isNull } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { A2A_DEFAULT_TIMEOUT, A2A_MAX_HISTORY_LENGTH } from '@/lib/a2a/constants'
import { notifyTaskStateChange } from '@/lib/a2a/push-notifications'
import {
Expand All @@ -18,6 +17,7 @@ import { acquireLock, getRedisClient, releaseLock } from '@/lib/core/config/redi
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
import { SSE_HEADERS } from '@/lib/core/utils/sse'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { generateId } from '@/lib/core/utils/uuid'
import { markExecutionCancelled } from '@/lib/execution/cancellation'
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
Expand Down Expand Up @@ -400,11 +400,11 @@ async function handleMessageSend(

const message = params.message
const taskId = message.taskId || generateTaskId()
const contextId = message.contextId || uuidv4()
const contextId = message.contextId || generateId()

// Distributed lock to prevent concurrent task processing
const lockKey = `a2a:task:${taskId}:lock`
const lockValue = uuidv4()
const lockValue = generateId()
const acquired = await acquireLock(lockKey, lockValue, 60)

if (!acquired) {
Expand Down Expand Up @@ -628,12 +628,12 @@ async function handleMessageStream(
}

const message = params.message
const contextId = message.contextId || uuidv4()
const contextId = message.contextId || generateId()
const taskId = message.taskId || generateTaskId()

// Distributed lock to prevent concurrent task processing
const lockKey = `a2a:task:${taskId}:lock`
const lockValue = uuidv4()
const lockValue = generateId()
const acquired = await acquireLock(lockKey, lockValue, 300)

if (!acquired) {
Expand Down Expand Up @@ -1427,7 +1427,7 @@ async function handlePushNotificationSet(
.where(eq(a2aPushNotificationConfig.id, existingConfig.id))
} else {
await db.insert(a2aPushNotificationConfig).values({
id: uuidv4(),
id: generateId(),
taskId: params.id,
url: config.url,
token: config.token || null,
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/a2a/serve/[agentId]/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Artifact, Message, PushNotificationConfig, Task, TaskState } from '@a2a-js/sdk'
import { v4 as uuidv4 } from 'uuid'
import { generateInternalToken } from '@/lib/auth/internal'
import { getInternalApiBaseUrl } from '@/lib/core/utils/urls'
import { generateId } from '@/lib/core/utils/uuid'

/** A2A v0.3 JSON-RPC method names */
export const A2A_METHODS = {
Expand Down Expand Up @@ -85,7 +85,7 @@ export function isJSONRPCRequest(obj: unknown): obj is JSONRPCRequest {
}

export function generateTaskId(): string {
return uuidv4()
return generateId()
}

export function createTaskStatus(state: TaskState): { state: TaskState; timestamp: string } {
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/academy/certificates/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { db } from '@sim/db'
import { academyCertificate, user } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { nanoid } from 'nanoid'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getCourseById } from '@/lib/academy/content'
import type { CertificateMetadata } from '@/lib/academy/types'
import { getSession } from '@/lib/auth'
import type { TokenBucketConfig } from '@/lib/core/rate-limiter'
import { RateLimiter } from '@/lib/core/rate-limiter'
import { generateShortId } from '@/lib/core/utils/uuid'

const logger = createLogger('AcademyCertificatesAPI')

Expand Down Expand Up @@ -106,7 +106,7 @@ export async function POST(req: NextRequest) {
const [certificate] = await db
.insert(academyCertificate)
.values({
id: nanoid(),
id: generateShortId(),
userId: session.user.id,
courseId,
status: 'active',
Expand Down Expand Up @@ -211,5 +211,5 @@ export async function GET(req: NextRequest) {
/** Generates a human-readable certificate number, e.g. SIM-2026-A3K9XZ2P */
function generateCertificateNumber(): string {
const year = new Date().getFullYear()
return `SIM-${year}-${nanoid(8).toUpperCase()}`
return `SIM-${year}-${generateShortId(8).toUpperCase()}`
}
3 changes: 2 additions & 1 deletion apps/sim/app/api/auth/shopify/authorize/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { env } from '@/lib/core/config/env'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { generateId } from '@/lib/core/utils/uuid'

const logger = createLogger('ShopifyAuthorize')

Expand Down Expand Up @@ -161,7 +162,7 @@ export async function GET(request: NextRequest) {
const baseUrl = getBaseUrl()
const redirectUri = `${baseUrl}/api/auth/oauth2/callback/shopify`

const state = crypto.randomUUID()
const state = generateId()

const oauthUrl =
`https://${cleanShop}/admin/oauth/authorize?` +
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/app/api/chat/[identifier]/otp/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { randomInt, randomUUID } from 'crypto'
import { randomInt } from 'crypto'
import { db } from '@sim/db'
import { chat, verification } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
Expand All @@ -10,6 +10,7 @@ import { getRedisClient } from '@/lib/core/config/redis'
import { addCorsHeaders, isEmailAllowed } from '@/lib/core/security/deployment'
import { getStorageMethod } from '@/lib/core/storage'
import { generateRequestId } from '@/lib/core/utils/request'
import { generateId } from '@/lib/core/utils/uuid'
import { sendEmail } from '@/lib/messaging/email/mailer'
import { setChatAuthCookie } from '@/app/api/chat/utils'
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
Expand Down Expand Up @@ -61,7 +62,7 @@ async function storeOTP(email: string, chatId: string, otp: string): Promise<voi
await db.transaction(async (tx) => {
await tx.delete(verification).where(eq(verification.identifier, identifier))
await tx.insert(verification).values({
id: randomUUID(),
id: generateId(),
identifier,
value,
expiresAt,
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/chat/[identifier]/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import { chat, workflow } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
Expand All @@ -7,6 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { addCorsHeaders, validateAuthToken } from '@/lib/core/security/deployment'
import { generateRequestId } from '@/lib/core/utils/request'
import { generateId } from '@/lib/core/utils/uuid'
import { preprocessExecution } from '@/lib/execution/preprocessing'
import { LoggingSession } from '@/lib/logs/execution/logging-session'
import { ChatFiles } from '@/lib/uploads'
Expand Down Expand Up @@ -103,7 +103,7 @@ export async function POST(
)
}

const executionId = randomUUID()
const executionId = generateId()
const loggingSession = new LoggingSession(
deployment.workflowId,
executionId,
Expand Down Expand Up @@ -150,7 +150,7 @@ export async function POST(
return addCorsHeaders(createErrorResponse('No input provided', 400), request)
}

const executionId = randomUUID()
const executionId = generateId()

const loggingSession = new LoggingSession(deployment.workflowId, executionId, 'chat', requestId)

Expand Down
15 changes: 8 additions & 7 deletions apps/sim/app/api/copilot/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
createRequestTracker,
createUnauthorizedResponse,
} from '@/lib/copilot/request-helpers'
import { generateId } from '@/lib/core/utils/uuid'
import { captureServerEvent } from '@/lib/posthog/server'
import {
authorizeWorkflowByWorkspacePermission,
Expand Down Expand Up @@ -205,7 +206,7 @@ export async function POST(req: NextRequest) {
}
)

const userMessageIdToUse = userMessageId || crypto.randomUUID()
const userMessageIdToUse = userMessageId || generateId()
const reqLogger = logger.withMetadata({
requestId: tracker.requestId,
messageId: userMessageIdToUse,
Expand Down Expand Up @@ -406,8 +407,8 @@ export async function POST(req: NextRequest) {
}

if (stream) {
const executionId = crypto.randomUUID()
const runId = crypto.randomUUID()
const executionId = generateId()
const runId = generateId()
const sseStream = createSSEStream({
requestPayload,
userId: authenticatedUserId,
Expand Down Expand Up @@ -437,7 +438,7 @@ export async function POST(req: NextRequest) {
if (!result.success) return

const assistantMessage: Record<string, unknown> = {
id: crypto.randomUUID(),
id: generateId(),
role: 'assistant' as const,
content: result.content,
timestamp: new Date().toISOString(),
Expand Down Expand Up @@ -515,8 +516,8 @@ export async function POST(req: NextRequest) {
return new Response(sseStream, { headers: SSE_RESPONSE_HEADERS })
}

const nsExecutionId = crypto.randomUUID()
const nsRunId = crypto.randomUUID()
const nsExecutionId = generateId()
const nsRunId = generateId()

if (actualChatId) {
await createRunSegment({
Expand Down Expand Up @@ -576,7 +577,7 @@ export async function POST(req: NextRequest) {
}

const assistantMessage = {
id: crypto.randomUUID(),
id: generateId(),
role: 'assistant',
content: responseData.content,
timestamp: new Date().toISOString(),
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/creators/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { member, templateCreators } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, or } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { generateId } from '@/lib/core/utils/uuid'
import type { CreatorProfileDetails } from '@/app/_types/creator-profile'

const logger = createLogger('CreatorProfilesAPI')
Expand Down Expand Up @@ -147,7 +147,7 @@ export async function POST(request: NextRequest) {
}

// Create the profile
const profileId = uuidv4()
const profileId = generateId()
const now = new Date()

const details: CreatorProfileDetails = {}
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/app/api/credential-sets/[id]/invite/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasCredentialSetsAccess } from '@/lib/billing'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { generateId } from '@/lib/core/utils/uuid'
import { sendEmail } from '@/lib/messaging/email/mailer'

const logger = createLogger('CredentialSetInvite')
Expand Down Expand Up @@ -105,12 +106,12 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
const body = await req.json()
const { email } = createInviteSchema.parse(body)

const token = crypto.randomUUID()
const token = generateId()
const expiresAt = new Date()
expiresAt.setDate(expiresAt.getDate() + 7)

const invitation = {
id: crypto.randomUUID(),
id: generateId(),
credentialSetId: id,
email: email || null,
token,
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/api/credential-sets/[id]/members/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasCredentialSetsAccess } from '@/lib/billing'
import { generateId } from '@/lib/core/utils/uuid'
import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server'

const logger = createLogger('CredentialSetMembers')
Expand Down Expand Up @@ -167,7 +168,7 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
return NextResponse.json({ error: 'Member not found' }, { status: 404 })
}

const requestId = crypto.randomUUID().slice(0, 8)
const requestId = generateId().slice(0, 8)

// Use transaction to ensure member deletion + webhook sync are atomic
await db.transaction(async (tx) => {
Expand Down
Loading
Loading