Skip to main content

forge_macros/
lib.rs

1use proc_macro::TokenStream;
2
3mod cron;
4mod enum_type;
5mod job;
6mod model;
7mod mutation;
8mod query;
9mod sql_extractor;
10mod workflow;
11
12/// Marks a struct as a FORGE model, generating schema metadata for TypeScript codegen.
13///
14/// # Example
15/// ```ignore
16/// #[forge::model]
17/// pub struct User {
18///     pub id: Uuid,
19///     pub email: String,
20///     pub name: String,
21///     pub created_at: DateTime<Utc>,
22/// }
23/// ```
24#[proc_macro_attribute]
25pub fn model(attr: TokenStream, item: TokenStream) -> TokenStream {
26    model::expand_model(attr, item)
27}
28
29/// Marks an enum for database storage as a PostgreSQL ENUM type.
30///
31/// # Example
32/// ```ignore
33/// #[forge::forge_enum]
34/// pub enum ProjectStatus {
35///     Draft,
36///     Active,
37///     Paused,
38///     Completed,
39/// }
40/// ```
41#[proc_macro_attribute]
42pub fn forge_enum(attr: TokenStream, item: TokenStream) -> TokenStream {
43    enum_type::expand_enum(attr, item)
44}
45
46/// Marks a function as a query (read-only, cacheable, subscribable).
47///
48/// # Authentication
49/// By default, queries require an authenticated user. Override with:
50/// - `public` - No authentication required
51/// - `require_role("admin")` - Require specific role
52///
53/// # Attributes
54/// - `cache = "5m"` - Cache TTL (duration like "30s", "5m", "1h")
55/// - `log` - Enable logging for this query
56/// - `timeout = 30` - Timeout in seconds
57/// - `tables = ["users", "projects"]` - Explicit table dependencies (for dynamic SQL)
58///
59/// # Table Dependency Extraction
60/// By default, table dependencies are automatically extracted from SQL strings
61/// in the function body at compile time. This enables accurate reactive
62/// subscription invalidation for queries that join multiple tables.
63///
64/// For dynamic SQL (e.g., table names built at runtime), use the `tables`
65/// attribute to explicitly specify dependencies.
66///
67/// # Example
68/// ```ignore
69/// #[forge::query]  // Requires authenticated user (default)
70/// pub async fn get_user(ctx: &QueryContext, user_id: Uuid) -> Result<User> {
71///     // Tables automatically extracted from SQL
72/// }
73///
74/// #[forge::query(public)]  // No auth required
75/// pub async fn get_public_data(ctx: &QueryContext) -> Result<Data> {
76///     // ...
77/// }
78///
79/// #[forge::query(require_role("admin"), cache = "5m", log)]
80/// pub async fn admin_stats(ctx: &QueryContext) -> Result<Stats> {
81///     // Requires admin role
82/// }
83///
84/// #[forge::query(tables = ["users", "audit_log"])]
85/// pub async fn dynamic_query(ctx: &QueryContext, table: String) -> Result<Vec<Row>> {
86///     // Explicit tables for dynamic SQL
87/// }
88/// ```
89#[proc_macro_attribute]
90pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream {
91    query::expand_query(attr, item)
92}
93
94/// Marks a function as a mutation (transactional write).
95///
96/// Mutations run within a database transaction. All changes commit together or roll back on error.
97///
98/// # Authentication
99/// By default, mutations require an authenticated user. Override with:
100/// - `public` - No authentication required
101/// - `require_role("admin")` - Require specific role
102///
103/// # Attributes
104/// - `log` - Enable logging for this mutation
105/// - `timeout = 30` - Timeout in seconds
106///
107/// # Example
108/// ```ignore
109/// #[forge::mutation]  // Requires authenticated user (default)
110/// pub async fn create_project(ctx: &MutationContext, input: CreateProjectInput) -> Result<Project> {
111///     let user_id = ctx.require_user_id()?;
112///     // ...
113/// }
114///
115/// #[forge::mutation(public)]  // No auth required
116/// pub async fn submit_feedback(ctx: &MutationContext, input: FeedbackInput) -> Result<()> {
117///     // ...
118/// }
119///
120/// #[forge::mutation(require_role("admin"), log)]
121/// pub async fn delete_user(ctx: &MutationContext, user_id: Uuid) -> Result<()> {
122///     // Requires admin role
123/// }
124/// ```
125#[proc_macro_attribute]
126pub fn mutation(attr: TokenStream, item: TokenStream) -> TokenStream {
127    mutation::expand_mutation(attr, item)
128}
129
130/// Marks a function as a background job.
131///
132/// Jobs are durable background tasks that survive server restarts and automatically retry on failure.
133///
134/// # Authentication
135/// By default, jobs require an authenticated user to dispatch. Override with:
136/// - `public` - Can be dispatched without authentication
137/// - `require_role("admin")` - Requires specific role to dispatch
138///
139/// # Attributes
140/// - `timeout = "30m"` - Job timeout (supports s, m, h suffixes)
141/// - `priority = "normal"` - background, low, normal, high, critical
142/// - `max_attempts = 3` - Maximum retry attempts
143/// - `backoff = "exponential"` - fixed, linear, or exponential
144/// - `max_backoff = "5m"` - Maximum backoff duration
145/// - `retry(max_attempts = 3, backoff = "exponential", max_backoff = "5m")` - Grouped retry config
146/// - `worker_capability = "media"` - Required worker capability
147/// - `idempotent` - Mark job as idempotent
148/// - `idempotent(key = "input.id")` - Idempotent with custom key
149/// - `name = "custom_name"` - Override job name
150///
151/// # Example
152/// ```ignore
153/// #[forge::job(timeout = "30m", priority = "high")]  // Requires authenticated user (default)
154/// pub async fn send_welcome_email(ctx: &JobContext, input: SendEmailInput) -> Result<()> {
155///     // ...
156/// }
157///
158/// #[forge::job(public)]  // Can be dispatched without auth
159/// pub async fn process_webhook(ctx: &JobContext, input: WebhookInput) -> Result<()> {
160///     // ...
161/// }
162///
163/// #[forge::job(retry(max_attempts = 5, backoff = "exponential"), require_role("admin"))]
164/// pub async fn process_payment(ctx: &JobContext, input: PaymentInput) -> Result<()> {
165///     // Requires admin role to dispatch
166/// }
167/// ```
168#[proc_macro_attribute]
169pub fn job(attr: TokenStream, item: TokenStream) -> TokenStream {
170    job::job_impl(attr, item)
171}
172
173/// Marks a function as a scheduled cron task.
174///
175/// Cron jobs run on a schedule, exactly once per scheduled time across the cluster.
176///
177/// # Attributes
178/// All attributes are specified inline within the macro:
179/// - First argument: Cron schedule expression (required)
180/// - `timezone = "UTC"` - Timezone for the schedule
181/// - `timeout = "1h"` - Execution timeout
182/// - `catch_up` - Run missed executions after downtime
183/// - `catch_up_limit = 10` - Maximum number of catch-up runs
184///
185/// # Example
186/// ```ignore
187/// #[forge::cron("0 0 * * *", timezone = "America/New_York", timeout = "30m", catch_up)]
188/// pub async fn daily_cleanup(ctx: &CronContext) -> Result<()> {
189///     // ...
190/// }
191/// ```
192#[proc_macro_attribute]
193pub fn cron(attr: TokenStream, item: TokenStream) -> TokenStream {
194    cron::cron_impl(attr, item)
195}
196
197/// Marks a function as a durable workflow.
198///
199/// Workflows are multi-step processes that survive restarts and handle failures with compensation.
200///
201/// # Authentication
202/// By default, workflows require an authenticated user to start. Override with:
203/// - `public` - Can be started without authentication
204/// - `require_role("admin")` - Requires specific role to start
205///
206/// # Attributes
207/// - `version = 1` - Workflow version (increment for breaking changes)
208/// - `timeout = "24h"` - Maximum execution time
209/// - `name = "custom_name"` - Override workflow name
210///
211/// # Example
212/// ```ignore
213/// #[forge::workflow(version = 1, timeout = "24h")]  // Requires authenticated user (default)
214/// pub async fn user_onboarding(ctx: &WorkflowContext, input: OnboardingInput) -> Result<OnboardingResult> {
215///     let user = ctx.step("create_user", || async { /* ... */ }).await?;
216///     ctx.step("send_welcome", || async { /* ... */ }).await;
217///     Ok(OnboardingResult { user })
218/// }
219///
220/// #[forge::workflow(public)]  // Can be started without auth
221/// pub async fn process_webhook(ctx: &WorkflowContext, input: WebhookInput) -> Result<()> {
222///     // ...
223/// }
224///
225/// #[forge::workflow(version = 2, require_role("admin"))]
226/// pub async fn admin_workflow(ctx: &WorkflowContext, input: AdminInput) -> Result<AdminResult> {
227///     // Requires admin role to start
228/// }
229/// ```
230#[proc_macro_attribute]
231pub fn workflow(attr: TokenStream, item: TokenStream) -> TokenStream {
232    workflow::workflow_impl(attr, item)
233}