function-creator

安装量: 550
排名: #1993

安装

npx skills add https://github.com/get-convex/agent-skills --skill function-creator
Convex Function Creator
Generate secure, type-safe Convex functions following all best practices.
When to Use
Creating new query functions (read data)
Creating new mutation functions (write data)
Creating new action functions (external APIs, long-running)
Adding API endpoints to your Convex backend
Function Types
Queries (Read-Only)
Can only read from database
Cannot modify data or call external APIs
Cached and reactive
Run in transactions
import
{
query
}
from
"./_generated/server"
;
import
{
v
}
from
"convex/values"
;
export
const
getTask
=
query
(
{
args
:
{
taskId
:
v
.
id
(
"tasks"
)
}
,
returns
:
v
.
union
(
v
.
object
(
{
_id
:
v
.
id
(
"tasks"
)
,
text
:
v
.
string
(
)
,
completed
:
v
.
boolean
(
)
,
}
)
,
v
.
null
(
)
)
,
handler
:
async
(
ctx
,
args
)
=>
{
return
await
ctx
.
db
.
get
(
args
.
taskId
)
;
}
,
}
)
;
Mutations (Transactional Writes)
Can read and write to database
Cannot call external APIs
Run in ACID transactions
Automatic retries on conflicts
import
{
mutation
}
from
"./_generated/server"
;
import
{
v
}
from
"convex/values"
;
export
const
createTask
=
mutation
(
{
args
:
{
text
:
v
.
string
(
)
,
priority
:
v
.
optional
(
v
.
union
(
v
.
literal
(
"low"
)
,
v
.
literal
(
"medium"
)
,
v
.
literal
(
"high"
)
)
)
,
}
,
returns
:
v
.
id
(
"tasks"
)
,
handler
:
async
(
ctx
,
args
)
=>
{
const
identity
=
await
ctx
.
auth
.
getUserIdentity
(
)
;
if
(
!
identity
)
throw
new
Error
(
"Not authenticated"
)
;
return
await
ctx
.
db
.
insert
(
"tasks"
,
{
text
:
args
.
text
,
priority
:
args
.
priority
??
"medium"
,
completed
:
false
,
createdAt
:
Date
.
now
(
)
,
}
)
;
}
,
}
)
;
Actions (External + Non-Transactional)
Can call external APIs (fetch, AI, etc.)
Can call mutations via
ctx.runMutation
Cannot directly access database
No automatic retries
Use
"use node"
directive when needing Node.js APIs
Important:
If your action needs Node.js-specific APIs (crypto, third-party SDKs, etc.), add
"use node"
at the top of the file. Files with
"use node"
can ONLY contain actions, not queries or mutations.
"use node"
;
// Required for Node.js APIs like OpenAI SDK
import
{
action
}
from
"./_generated/server"
;
import
{
api
}
from
"./_generated/api"
;
import
{
v
}
from
"convex/values"
;
import
OpenAI
from
"openai"
;
const
openai
=
new
OpenAI
(
{
apiKey
:
process
.
env
.
OPENAI_API_KEY
}
)
;
export
const
generateTaskSuggestion
=
action
(
{
args
:
{
prompt
:
v
.
string
(
)
}
,
returns
:
v
.
string
(
)
,
handler
:
async
(
ctx
,
args
)
=>
{
const
identity
=
await
ctx
.
auth
.
getUserIdentity
(
)
;
if
(
!
identity
)
throw
new
Error
(
"Not authenticated"
)
;
// Call OpenAI (requires "use node")
const
completion
=
await
openai
.
chat
.
completions
.
create
(
{
model
:
"gpt-4"
,
messages
:
[
{
role
:
"user"
,
content
:
args
.
prompt
}
]
,
}
)
;
const
suggestion
=
completion
.
choices
[
0
]
.
message
.
content
;
// Write to database via mutation
await
ctx
.
runMutation
(
api
.
tasks
.
createTask
,
{
text
:
suggestion
,
}
)
;
return
suggestion
;
}
,
}
)
;
Note:
If you only need basic fetch (no Node.js APIs), you can omit
"use node"
. But for third-party SDKs, crypto, or other Node.js features, you must use it.
Required Components
1. Argument Validation
Always
define
args
with validators:
args
:
{
id
:
v
.
id
(
"tasks"
)
,
text
:
v
.
string
(
)
,
count
:
v
.
number
(
)
,
enabled
:
v
.
boolean
(
)
,
tags
:
v
.
array
(
v
.
string
(
)
)
,
metadata
:
v
.
optional
(
v
.
object
(
{
key
:
v
.
string
(
)
,
}
)
)
,
}
2. Return Type Validation
Always
define
returns
:
returns
:
v
.
object
(
{
_id
:
v
.
id
(
"tasks"
)
,
text
:
v
.
string
(
)
,
}
)
// Or for arrays
returns
:
v
.
array
(
v
.
object
(
{
/ ... /
}
)
)
// Or for nullable
returns
:
v
.
union
(
v
.
object
(
{
/ ... /
}
)
,
v
.
null
(
)
)
3. Authentication Check
Always
verify auth in public functions:
const
identity
=
await
ctx
.
auth
.
getUserIdentity
(
)
;
if
(
!
identity
)
{
throw
new
Error
(
"Not authenticated"
)
;
}
4. Authorization Check
Always
verify ownership/permissions:
const
task
=
await
ctx
.
db
.
get
(
args
.
taskId
)
;
if
(
!
task
)
{
throw
new
Error
(
"Task not found"
)
;
}
if
(
task
.
userId
!==
user
.
_id
)
{
throw
new
Error
(
"Unauthorized"
)
;
}
Complete Examples
Secure Query with Auth
export
const
getMyTasks
=
query
(
{
args
:
{
status
:
v
.
optional
(
v
.
union
(
v
.
literal
(
"active"
)
,
v
.
literal
(
"completed"
)
)
)
,
}
,
returns
:
v
.
array
(
v
.
object
(
{
_id
:
v
.
id
(
"tasks"
)
,
text
:
v
.
string
(
)
,
completed
:
v
.
boolean
(
)
,
}
)
)
,
handler
:
async
(
ctx
,
args
)
=>
{
const
identity
=
await
ctx
.
auth
.
getUserIdentity
(
)
;
if
(
!
identity
)
throw
new
Error
(
"Not authenticated"
)
;
const
user
=
await
ctx
.
db
.
query
(
"users"
)
.
withIndex
(
"by_token"
,
q
=>
q
.
eq
(
"tokenIdentifier"
,
identity
.
tokenIdentifier
)
)
.
unique
(
)
;
if
(
!
user
)
throw
new
Error
(
"User not found"
)
;
let
query
=
ctx
.
db
.
query
(
"tasks"
)
.
withIndex
(
"by_user"
,
q
=>
q
.
eq
(
"userId"
,
user
.
_id
)
)
;
const
tasks
=
await
query
.
collect
(
)
;
if
(
args
.
status
)
{
return
tasks
.
filter
(
t
=>
args
.
status
===
"completed"
?
t
.
completed
:
!
t
.
completed
)
;
}
return
tasks
;
}
,
}
)
;
Secure Mutation with Validation
export
const
updateTask
=
mutation
(
{
args
:
{
taskId
:
v
.
id
(
"tasks"
)
,
text
:
v
.
optional
(
v
.
string
(
)
)
,
completed
:
v
.
optional
(
v
.
boolean
(
)
)
,
}
,
returns
:
v
.
id
(
"tasks"
)
,
handler
:
async
(
ctx
,
args
)
=>
{
// 1. Authentication
const
identity
=
await
ctx
.
auth
.
getUserIdentity
(
)
;
if
(
!
identity
)
throw
new
Error
(
"Not authenticated"
)
;
// 2. Get user
const
user
=
await
ctx
.
db
.
query
(
"users"
)
.
withIndex
(
"by_token"
,
q
=>
q
.
eq
(
"tokenIdentifier"
,
identity
.
tokenIdentifier
)
)
.
unique
(
)
;
if
(
!
user
)
throw
new
Error
(
"User not found"
)
;
// 3. Get resource
const
task
=
await
ctx
.
db
.
get
(
args
.
taskId
)
;
if
(
!
task
)
throw
new
Error
(
"Task not found"
)
;
// 4. Authorization
if
(
task
.
userId
!==
user
.
_id
)
{
throw
new
Error
(
"Unauthorized"
)
;
}
// 5. Update
const
updates
:
Partial
<
any
>
=
{
}
;
if
(
args
.
text
!==
undefined
)
updates
.
text
=
args
.
text
;
if
(
args
.
completed
!==
undefined
)
updates
.
completed
=
args
.
completed
;
await
ctx
.
db
.
patch
(
args
.
taskId
,
updates
)
;
return
args
.
taskId
;
}
,
}
)
;
Action Calling External API
Create separate file for actions that need Node.js:
// convex/taskActions.ts
"use node"
;
// Required for SendGrid SDK
import
{
action
}
from
"./_generated/server"
;
import
{
api
}
from
"./_generated/api"
;
import
{
v
}
from
"convex/values"
;
import
sendgrid
from
"@sendgrid/mail"
;
sendgrid
.
setApiKey
(
process
.
env
.
SENDGRID_API_KEY
)
;
export
const
sendTaskReminder
=
action
(
{
args
:
{
taskId
:
v
.
id
(
"tasks"
)
}
,
returns
:
v
.
boolean
(
)
,
handler
:
async
(
ctx
,
args
)
=>
{
// 1. Auth
const
identity
=
await
ctx
.
auth
.
getUserIdentity
(
)
;
if
(
!
identity
)
throw
new
Error
(
"Not authenticated"
)
;
// 2. Get data via query
const
task
=
await
ctx
.
runQuery
(
api
.
tasks
.
getTask
,
{
taskId
:
args
.
taskId
,
}
)
;
if
(
!
task
)
throw
new
Error
(
"Task not found"
)
;
// 3. Call external service (using Node.js SDK)
await
sendgrid
.
send
(
{
to
:
identity
.
email
,
from
:
"noreply@example.com"
,
subject
:
"Task Reminder"
,
text
:
`
Don't forget:
${
task
.
text
}
`
,
}
)
;
// 4. Update via mutation
await
ctx
.
runMutation
(
api
.
tasks
.
markReminderSent
,
{
taskId
:
args
.
taskId
,
}
)
;
return
true
;
}
,
}
)
;
Note:
Keep queries and mutations in
convex/tasks.ts
(without "use node"), and actions that need Node.js in
convex/taskActions.ts
(with "use node").
Internal Functions
For backend-only functions (called by scheduler, other functions):
import
{
internalMutation
}
from
"./_generated/server"
;
export
const
processExpiredTasks
=
internalMutation
(
{
args
:
{
}
,
handler
:
async
(
ctx
)
=>
{
// No auth needed - only callable from backend
const
now
=
Date
.
now
(
)
;
const
expired
=
await
ctx
.
db
.
query
(
"tasks"
)
.
withIndex
(
"by_due_date"
,
q
=>
q
.
lt
(
"dueDate"
,
now
)
)
.
collect
(
)
;
for
(
const
task
of
expired
)
{
await
ctx
.
db
.
patch
(
task
.
_id
,
{
status
:
"expired"
}
)
;
}
}
,
}
)
;
Checklist
args
defined with validators
returns
defined with validator
Authentication check (
ctx.auth.getUserIdentity()
)
Authorization check (ownership/permissions)
All promises awaited
Indexed queries (no
.filter()
on queries)
Error handling with descriptive messages
Scheduled functions use
internal.*
not
api.*
If using Node.js APIs:
"use node"
at top of file
If file has
"use node"
Only actions (no queries/mutations) Actions in separate file from queries/mutations when using "use node"
返回排行榜