forge_macros/lib.rs
1use proc_macro::TokenStream;
2
3mod action;
4mod cron;
5mod enum_type;
6mod job;
7mod model;
8mod mutation;
9mod query;
10mod workflow;
11
12/// Marks a struct as a FORGE model, generating schema metadata and SQL.
13///
14/// # Example
15/// ```ignore
16/// #[forge::model]
17/// #[table(name = "users")]
18/// pub struct User {
19/// #[id]
20/// pub id: Uuid,
21///
22/// #[indexed]
23/// #[unique]
24/// pub email: String,
25///
26/// pub name: String,
27/// }
28/// ```
29#[proc_macro_attribute]
30pub fn model(attr: TokenStream, item: TokenStream) -> TokenStream {
31 model::expand_model(attr, item)
32}
33
34/// Marks an enum for database storage as a PostgreSQL ENUM type.
35///
36/// # Example
37/// ```ignore
38/// #[forge::forge_enum]
39/// pub enum ProjectStatus {
40/// Draft,
41/// Active,
42/// Paused,
43/// Completed,
44/// }
45/// ```
46#[proc_macro_attribute]
47pub fn forge_enum(attr: TokenStream, item: TokenStream) -> TokenStream {
48 enum_type::expand_enum(attr, item)
49}
50
51/// Marks a function as a query (read-only, cacheable, subscribable).
52///
53/// Queries can only read from the database and are automatically cached.
54/// They can be subscribed to for real-time updates.
55///
56/// # Attributes
57/// - `cache = "5m"` - Cache TTL (duration like "30s", "5m", "1h")
58/// - `public` - No authentication required
59/// - `require_auth` - Require authentication
60/// - `timeout = 30` - Timeout in seconds
61///
62/// # Example
63/// ```ignore
64/// #[forge::query]
65/// pub async fn get_user(ctx: &QueryContext, user_id: Uuid) -> Result<User> {
66/// ctx.db().query::<User>().filter(|u| u.id == user_id).fetch_one().await
67/// }
68///
69/// #[forge::query(cache = "5m", require_auth)]
70/// pub async fn get_profile(ctx: &QueryContext) -> Result<Profile> {
71/// let user_id = ctx.require_user_id()?;
72/// // ...
73/// }
74/// ```
75#[proc_macro_attribute]
76pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream {
77 query::expand_query(attr, item)
78}
79
80/// Marks a function as a mutation (transactional write).
81///
82/// Mutations run within a database transaction and can read and write data.
83/// All changes either commit together or roll back on error.
84///
85/// # Attributes
86/// - `require_auth` - Require authentication
87/// - `require_role("admin")` - Require specific role
88/// - `timeout = 30` - Timeout in seconds
89///
90/// # Example
91/// ```ignore
92/// #[forge::mutation]
93/// pub async fn create_project(
94/// ctx: &MutationContext,
95/// input: CreateProjectInput,
96/// ) -> Result<Project> {
97/// let user_id = ctx.require_user_id()?;
98/// // All operations in a transaction
99/// let project = ctx.db().insert(Project { ... }).await?;
100/// Ok(project)
101/// }
102/// ```
103#[proc_macro_attribute]
104pub fn mutation(attr: TokenStream, item: TokenStream) -> TokenStream {
105 mutation::expand_mutation(attr, item)
106}
107
108/// Marks a function as an action (side effects, external APIs).
109///
110/// Actions can call external APIs and perform side effects.
111/// They are NOT transactional by default but can call queries and mutations.
112///
113/// # Attributes
114/// - `require_auth` - Require authentication
115/// - `require_role("admin")` - Require specific role
116/// - `timeout = 60` - Timeout in seconds
117///
118/// # Example
119/// ```ignore
120/// #[forge::action(timeout = 60)]
121/// pub async fn sync_with_stripe(
122/// ctx: &ActionContext,
123/// user_id: Uuid,
124/// ) -> Result<SyncResult> {
125/// // Can call external APIs
126/// let customer = stripe::Customer::retrieve(...).await?;
127/// Ok(SyncResult::success())
128/// }
129/// ```
130#[proc_macro_attribute]
131pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream {
132 action::expand_action(attr, item)
133}
134
135/// Marks a function as a background job.
136///
137/// Jobs are durable background tasks that survive server restarts,
138/// automatically retry on failure, and can be scheduled for the future.
139///
140/// # Attributes
141/// - `timeout = "30m"` - Job timeout (duration like "30s", "5m", "1h")
142/// - `priority = "normal"` - Priority: background, low, normal, high, critical
143/// - `max_attempts = 3` - Maximum retry attempts
144/// - `worker_capability = "general"` - Required worker capability
145/// - `idempotent` - Enable deduplication by key
146///
147/// # Example
148/// ```ignore
149/// #[forge::job]
150/// #[timeout = "30m"]
151/// #[priority = "high"]
152/// #[max_attempts = 5]
153/// pub async fn send_welcome_email(
154/// ctx: &JobContext,
155/// input: SendEmailInput,
156/// ) -> Result<()> {
157/// email::send(&input).await
158/// }
159/// ```
160#[proc_macro_attribute]
161pub fn job(attr: TokenStream, item: TokenStream) -> TokenStream {
162 job::job_impl(attr, item)
163}
164
165/// Marks a function as a scheduled cron task.
166///
167/// Cron jobs run on a schedule and are guaranteed to run exactly once
168/// per scheduled time across the entire cluster.
169///
170/// # Arguments
171/// The cron expression is passed as the first argument:
172/// - `"0 * * * *"` - Every hour
173/// - `"*/5 * * * *"` - Every 5 minutes
174/// - `"0 0 * * *"` - Every day at midnight
175///
176/// # Attributes
177/// - `timezone = "UTC"` - Timezone for the schedule (default: UTC)
178/// - `catch_up` - Run missed executions after downtime
179/// - `catch_up_limit = 10` - Maximum missed runs to catch up
180/// - `timeout = "1h"` - Execution timeout
181///
182/// # Example
183/// ```ignore
184/// #[forge::cron("0 0 * * *")]
185/// #[timezone = "America/New_York"]
186/// #[catch_up]
187/// pub async fn daily_cleanup(ctx: &CronContext) -> Result<()> {
188/// ctx.log.info("Starting cleanup", json!({}));
189/// // Cleanup logic...
190/// Ok(())
191/// }
192/// ```
193#[proc_macro_attribute]
194pub fn cron(attr: TokenStream, item: TokenStream) -> TokenStream {
195 cron::cron_impl(attr, item)
196}
197
198/// Marks a function as a durable workflow.
199///
200/// Workflows are multi-step processes that:
201/// - Survive server restarts
202/// - Handle failures with compensation
203/// - Track progress and state
204/// - Can run for hours, days, or longer
205///
206/// # Attributes
207/// - `version = 1` - Workflow version (increment for breaking changes)
208/// - `timeout = "24h"` - Maximum workflow execution time
209/// - `deprecated` - Mark as deprecated
210///
211/// # Example
212/// ```ignore
213/// #[forge::workflow]
214/// #[version = 1]
215/// pub async fn user_onboarding(
216/// ctx: &WorkflowContext,
217/// input: OnboardingInput,
218/// ) -> Result<OnboardingResult> {
219/// let user = ctx.step("create_user")
220/// .run(|| ctx.mutate(create_user, input.clone()))
221/// .compensate(|user| ctx.mutate(delete_user, user.id))
222/// .await?;
223///
224/// ctx.step("send_welcome")
225/// .run(|| send_email(&user.email))
226/// .optional()
227/// .await;
228///
229/// Ok(OnboardingResult { user })
230/// }
231/// ```
232#[proc_macro_attribute]
233pub fn workflow(attr: TokenStream, item: TokenStream) -> TokenStream {
234 workflow::workflow_impl(attr, item)
235}