You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
description: Rust best practices for the Windmill backend, covering code organization, error handling, performance optimizations, and common patterns to follow when adding new code.
8
+
globs: **/*.rs
9
+
---
10
+
# Windmill Backend - Rust Best Practices
11
+
12
+
## Project Structure
13
+
14
+
Windmill uses a workspace-based architecture with multiple crates:
15
+
16
+
- **windmill-api**: API server functionality
17
+
- **windmill-worker**: Job execution
18
+
- **windmill-common**: Shared code used by all crates
19
+
- **windmill-queue**: Job & flow queuing
20
+
- **windmill-audit**: Audit logging
21
+
- Other specialized crates (git-sync, autoscaling, etc.)
22
+
23
+
## Adding New Code
24
+
25
+
### Module Organization
26
+
27
+
- Place new code in the appropriate crate based on functionality
28
+
- For API endpoints, create or modify files in `windmill-api/src/` organized by domain
29
+
- For shared functionality, use `windmill-common/src/`
30
+
- Use the `_ee.rs` suffix for enterprise-only modules
31
+
- Follow existing patterns for file structure and organization
32
+
33
+
### Error Handling
34
+
35
+
- Use the custom `Error` enum from `windmill-common::error`
36
+
- Return `Result<T, Error>` or `JsonResult<T>` for functions that can fail
37
+
- Use the `?` operator for error propagation
38
+
- Add location tracking to errors using `#[track_caller]`
39
+
40
+
### Database Operations
41
+
42
+
- Use `sqlx` for database operations with prepared statements
43
+
- Leverage existing database helper functions in `db.rs` modules
44
+
- Use transactions for multi-step operations
45
+
- Handle database errors properly
46
+
47
+
### API Endpoints
48
+
49
+
- Follow existing patterns in the `windmill-api` crate
50
+
- Use axum's routing system and extractors
51
+
- Group related routes together
52
+
- Use consistent response formats (JSON)
53
+
- Follow proper authentication and authorization patterns
54
+
55
+
## Performance Optimizations
56
+
57
+
When generating code, especially involving `serde`, `sqlx`, and `tokio`, prioritize performance by applying the following principles:
- **Specify Structure Explicitly:** When defining structs for Serde (`#[derive(Serialize, Deserialize)]`), use `#[serde(...` attributes extensively. This includes:
62
+
* `#[serde(rename = "...")]` or `#[serde(alias = "...")]` to map external names precisely, avoiding dynamic lookups.
63
+
* `#[serde(default)]` for optional fields with default values, reducing parsing complexity.
64
+
* `#[serde(skip_serializing_if = "...")]` to avoid writing fields that meet a certain condition (e.g., `Option::is_none()`, `Vec::is_empty()`, or a custom function), reducing output size and serialization work.
65
+
* `#[serde(skip_serializing)]` or `#[serde(skip_deserializing)]` for fields that should *not* be included.
66
+
- **Prefer Borrowing:** Where possible and safe (data lifetime allows), use `Cow<'a, str>` or `&'a str` (with `#[serde(borrow)]`) instead of `String` for string fields during deserialization. This avoids allocating new strings, enabling zero-copy reading from the input buffer. Apply this principle to byte slices (`&'a [u8]` / `Cow<'a, [u8]>`) and potentially borrowed vectors as well.
67
+
- **Avoid Intermediate `Value`:** Unless the data structure is truly dynamic or unknown at compile time, deserialize directly into a well-defined struct or enum rather than into `serde_json::Value` (or equivalent for other formats). This avoids unnecessary heap allocations and type switching.
68
+
69
+
### SQLx Optimizations (Database Interaction)
70
+
71
+
- **Select Only Necessary Columns:** In `SELECT` queries, list specific column names rather than using `SELECT *`. This reduces data transferred from the database and the work needed for hydration/deserialization.
72
+
- **Batch Operations:** For multiple `INSERT`, `UPDATE`, or `DELETE` statements, prefer executing them in a single query if the database and driver support it efficiently (e.g., `INSERT INTO ... VALUES (...), (...), ...`). This minimizes round trips to the database.
73
+
- **Avoid N+1 Queries:** Do not loop through results of one query and execute a separate query for each item (e.g., fetching users, then querying for each user's profile in a loop). Instead, use JOINs or a single query with an `IN` clause to fetch related data efficiently.
74
+
- **Deserialize Directly:** Use `#[derive(FromRow)]` on structs and ensure the struct fields match the selected columns in the query. This allows SQLx to hydrate objects directly, avoiding intermediate data structures.
75
+
- **Parameterize Queries:** Always use SQLx's query methods (`.bind(...)`) to pass values as parameters rather than string formatting. This prevents SQL injection and allows the database to cache query plans, improving performance on repeated executions.
76
+
77
+
### Tokio Optimizations (Asynchronous Runtime)
78
+
79
+
- **Avoid Blocking Operations:** **Crucially**, never perform blocking operations (synchronous file I/O, `std::thread::sleep`, CPU-bound loops, `std::sync::Mutex::lock`, blocking network calls without `tokio::net`) directly within an `async fn` or a standard `tokio::spawn` task. Blocking pauses the entire worker thread, potentially starving other tasks. Use `tokio::task::spawn_blocking` for CPU-intensive work or blocking I/O.
80
+
- **Use Tokio's Async Primitives:** Prefer `tokio::sync` (channels, mutexes, semaphores), `tokio::io`, `tokio::net`, and `tokio::time` over their `std` counterparts in asynchronous contexts. These are designed to yield control back to the scheduler.
81
+
- **Manage Concurrency:** Be mindful of how many tasks are spawned. Creating a new task for every tiny piece of work can introduce overhead. Group related asynchronous operations where appropriate.
82
+
- **Handle Shared State Efficiently:** Use `Arc` for shared ownership in concurrent tasks. When shared state needs mutation, prefer `tokio::sync::Mutex` over `std::sync::Mutex` in `async` code. Consider `tokio::sync::RwLock` if reads significantly outnumber writes. Minimize the duration for which locks are held.
83
+
- **Understand `.await`:** Place `.await` strategically to allow the runtime to switch to other ready tasks. Ensure that `.await` points to genuinely asynchronous operations.
84
+
- **Backpressure:** If dealing with data streams or queues between tasks, implement backpressure mechanisms (e.g., bounded channels like `tokio::sync::mpsc::channel`) to prevent one component from overwhelming another or critical resources like the database.
85
+
86
+
## Enterprise Features
87
+
88
+
- Use feature flags for enterprise functionality
89
+
- Conditionally compile with `#[cfg(feature = "enterprise")]`
90
+
- Isolate enterprise code in separate modules
91
+
92
+
## Code Style
93
+
94
+
- Group imports by external and internal crates
95
+
- Place struct/enum definitions before implementations
96
+
- Group similar functionality together
97
+
- Use descriptive naming consistent with the codebase
98
+
- Follow existing patterns for async code using tokio
99
+
100
+
## Testing
101
+
102
+
- Write unit tests for core functionality
103
+
- Use the `#[cfg(test)]` module for test code
104
+
- For database tests, use the existing test utilities
This guide outlines best practices for developing with Svelte 5, incorporating the new Runes API and other modern Svelte features. They should be applied on every new files created, but not on existing svelte 4 files unless specifically asked to.
9
+
10
+
## Reactivity with Runes
11
+
12
+
Svelte 5 introduces Runes for more explicit and flexible reactivity.
13
+
14
+
1. **Embrace Runes for State Management**:
15
+
* Use `$state` for reactive local component state.
16
+
```svelte
17
+
<script>
18
+
let count = $state(0);
19
+
20
+
function increment() {
21
+
count += 1;
22
+
}
23
+
</script>
24
+
25
+
<button onclick={increment}>
26
+
Clicked {count} {count === 1 ? 'time' : 'times'}
27
+
</button>
28
+
```
29
+
* Use `$derived` for computed values based on other reactive state.
30
+
```svelte
31
+
<script>
32
+
let count = $state(0);
33
+
const doubled = $derived(count * 2);
34
+
</script>
35
+
36
+
<p>{count} * 2 = {doubled}</p>
37
+
```
38
+
* Use `$effect` for side effects that need to run when reactive values change (e.g., logging, manual DOM manipulation, data fetching). Remember `$effect` does not run on the server.
39
+
```svelte
40
+
<script>
41
+
let count = $state(0);
42
+
43
+
$effect(() => {
44
+
console.log('The count is now', count);
45
+
if (count > 5) {
46
+
alert('Count is too high!');
47
+
}
48
+
});
49
+
</script>
50
+
```
51
+
52
+
2. **Props with `$props`**:
53
+
* Declare component props using `$props()`. This offers better clarity and flexibility compared to `export let`.
54
+
```svelte
55
+
<script>
56
+
// ChildComponent.svelte
57
+
let { name, age = $state(30) } = $props();
58
+
</script>
59
+
60
+
<p>Name: {name}</p>
61
+
<p>Age: {age}</p>
62
+
```
63
+
* For bindable props, use `$bindable`.
64
+
```svelte
65
+
<script>
66
+
// MyInput.svelte
67
+
let { value = $bindable() } = $props();
68
+
</script>
69
+
70
+
<input bind:value />
71
+
```
72
+
73
+
## Event Handling
74
+
75
+
* **Use direct event attributes**: Svelte 5 moves away from `on:` directives for DOM events.
* **For component events, prefer callback props**: Instead of `createEventDispatcher`, pass functions as props.
79
+
```svelte
80
+
<!-- Parent.svelte -->
81
+
<script>
82
+
import Child from './Child.svelte';
83
+
let message = $state('');
84
+
function handleChildEvent(detail) {
85
+
message = detail;
86
+
}
87
+
</script>
88
+
<Child onCustomEvent={handleChildEvent} />
89
+
<p>Message from child: {message}</p>
90
+
91
+
<!-- Child.svelte -->
92
+
<script>
93
+
let { onCustomEvent } = $props();
94
+
function emitEvent() {
95
+
onCustomEvent('Hello from child!');
96
+
}
97
+
</script>
98
+
<button onclick={emitEvent}>Send Event</button>
99
+
```
100
+
101
+
## Snippets for Content Projection
102
+
103
+
* **Use `{#snippet ...}` and `{@render ...}` instead of slots**: Snippets are more powerful and flexible.
104
+
```svelte
105
+
<!-- Parent.svelte -->
106
+
<script>
107
+
import Card from './Card.svelte';
108
+
</script>
109
+
110
+
<Card>
111
+
{#snippet title()}
112
+
My Awesome Title
113
+
{/snippet}
114
+
{#snippet content()}
115
+
<p>Some interesting content here.</p>
116
+
{/snippet}
117
+
</Card>
118
+
119
+
<!-- Card.svelte -->
120
+
<script>
121
+
let { title, content } = $props();
122
+
</script>
123
+
124
+
<article>
125
+
<header>{@render title()}</header>
126
+
<div>{@render content()}</div>
127
+
</article>
128
+
```
129
+
* Default content is passed via the `children` prop (which is a snippet).
130
+
```svelte
131
+
<!-- Wrapper.svelte -->
132
+
<script>
133
+
let { children } = $props();
134
+
</script>
135
+
<div>
136
+
{@render children?.()}
137
+
</div>
138
+
```
139
+
140
+
## Component Design
141
+
142
+
1. **Create Small, Reusable Components**: Break down complex UIs into smaller, focused components. Each component should have a single responsibility. This also aids performance by limiting the scope of reactivity updates.
143
+
2. **Descriptive Naming**: Use clear and descriptive names for variables, functions, and components.
144
+
3. **Minimize Logic in Components**: Move complex business logic to utility functions or services. Keep components focused on presentation and interaction.
145
+
146
+
## State Management (Stores)
147
+
148
+
1. **Segment Stores**: Avoid a single global store. Create multiple stores, each responsible for a specific piece of global state (e.g., `userStore.js`, `themeStore.js`). This can help limit reactivity updates to only the parts of the UI that depend on specific state segments.
149
+
2. **Use Custom Stores for Complex Logic**: For stores with related methods, create custom stores.
150
+
```javascript
151
+
// counterStore.js
152
+
import { writable } from 'svelte/store';
153
+
154
+
function createCounter() {
155
+
const { subscribe, set, update } = writable(0);
156
+
157
+
return {
158
+
subscribe,
159
+
increment: () => update(n => n + 1),
160
+
decrement: () => update(n => n - 1),
161
+
reset: () => set(0)
162
+
};
163
+
}
164
+
export const counter = createCounter();
165
+
```
166
+
3. **Use Context API for Localized State**: For state shared within a component subtree, consider Svelte's context API (`setContext`, `getContext`) instead of global stores when the state doesn't need to be truly global.
167
+
168
+
## Performance Optimizations (Svelte 5)
169
+
170
+
When generating Svelte 5 code, prioritize frontend performance by applying the following principles:
171
+
172
+
### General Svelte 5 Principles
173
+
174
+
- **Leverage the Compiler:** Trust Svelte's compiler to generate optimized JavaScript. Avoid manual DOM manipulation (`document.querySelector`, etc.) unless absolutely necessary for integrating third-party libraries that lack Svelte adapters.
175
+
- **Keep Components Small and Focused:** Reinforcing from Component Design, smaller components lead to less complex reactivity graphs and more targeted, efficient updates.
176
+
177
+
### Reactivity & State Management
178
+
179
+
- **Optimize Computations with `$derived`:** Always use `$derived` for computed values that depend on other state. This ensures the computation only runs when its specific dependencies change, avoiding unnecessary work compared to recomputing derived values in `$effect` or less efficient methods.
180
+
- **Minimize `$effect` Usage:** Use `$effect` sparingly and only for true side effects that interact with the outside world or non-Svelte state. Avoid putting complex logic or state updates *within* an `$effect` unless those updates are explicitly intended as a reaction to external changes or non-Svelte state. Excessive or complex effects can impact rendering performance.
181
+
- **Structure State for Fine-Grained Updates:** Design your `$state` objects or variables such that updates affect only the necessary parts of the UI. Avoid putting too much unrelated state into a single large object that gets frequently updated, as this can potentially trigger broader updates than necessary. Consider normalizing complex, nested state.
182
+
183
+
### List Rendering (`{#each}`)
184
+
185
+
- **Mandate `key` Attribute:** Always use a `key` attribute (`{#each items as item (item.id)}`) that refers to a unique, stable identifier for each item in a list. This is critical for allowing Svelte to efficiently update, reorder, add, or remove list items without destroying and re-creating unnecessary DOM elements and component instances.
186
+
187
+
### Component Loading & Bundling
188
+
189
+
- **Implement Lazy Loading/Code Splitting:** For routes, components, or modules that are not immediately needed on page load, use dynamic imports (`import(...)`) to split the code bundle. SvelteKit handles this automatically for routes, but it can be applied manually to components using helper patterns if needed.
190
+
- **Be Mindful of Third-Party Libraries:** When incorporating external libraries, import only the necessary functions or components to minimize the final bundle size. Prefer libraries designed to be tree-shakeable.
191
+
192
+
### Rendering & DOM
193
+
194
+
- **Use CSS for Animations/Transitions:** Prefer CSS animations or transitions where possible for performance. Svelte's built-in `transition:` directive is also highly optimized and should be used for complex state-driven transitions, but simple cases can often use plain CSS.
195
+
- **Optimize Image Loading:** Implement best practices for images: use optimized formats (WebP, AVIF), lazy loading (`loading="lazy"`), and responsive images (`<picture>`, `srcset`) to avoid loading unnecessarily large images.
196
+
197
+
### Server-Side Rendering (SSR) & Hydration
198
+
199
+
- **Ensure SSR Compatibility:** Write components that can be rendered on the server for faster initial page loads. Avoid relying on browser-specific APIs (like `window` or `document`) in the main `<script>` context. If necessary, use `$effect` or check `if (browser)` inside effects to run browser-specific code only on the client.
200
+
- **Minimize Work During Hydration:** Structure components and data fetching such that minimal complex setup or computation is required when the client-side Svelte code takes over from the server-rendered HTML. Heavy synchronous work during hydration can block the main thread.
201
+
202
+
## General Clean Code Practices
203
+
204
+
1. **Organized File Structure**: Group related files together. A common structure:
205
+
```
206
+
/src
207
+
|-- /routes // Page components (if using a router like SvelteKit)
2. **Scoped Styles**: Keep CSS scoped to components to avoid unintended side effects and improve maintainability. Avoid `:global` where possible.
217
+
3. **Immutability**: With Svelte 5 and `$state`, direct assignments to properties of `$state` objects (`obj.prop = value;`) are generally fine as Svelte's reactivity system handles updates. However, for non-rune state or when interacting with other systems, understanding and sometimes preferring immutable updates (creating new objects/arrays) can still be relevant.
218
+
4. **Use `class:` and `style:` directives**: For dynamic classes and styles, use Svelte's built-in directives for cleaner templates and potentially optimized updates.
219
+
```svelte
220
+
<script>
221
+
let isActive = $state(true);
222
+
let color = $state('blue');
223
+
</script>
224
+
225
+
<div class:active={isActive} style:color={color}>
226
+
Hello
227
+
</div>
228
+
```
229
+
5. **Stay Updated**: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes.
* raise error if end early in flow ([#5653](https://github.com/windmill-labs/windmill/issues/5653)) ([242a565](https://github.com/windmill-labs/windmill/commit/242a5654285b0a3bf222c80e82f6861ffafed838))
* handle . in interpolated args ([0ac8e47](https://github.com/windmill-labs/windmill/commit/0ac8e477d6fb7c5a7699a198fce9d18a08aff68c))
16
+
17
+
18
+
### Bug Fixes
19
+
20
+
* fix azure object storage regression due to object_store regression ([df9f827](https://github.com/windmill-labs/windmill/commit/df9f827d103def27166a767044373bd0754285e2))
21
+
* performance and stability improvement to fetch last deployed script ([75d9924](https://github.com/windmill-labs/windmill/commit/75d992449c845fd11c9a317d401c405e7d78e1ec))
* critical alert if disk near full ([#5549](https://github.com/windmill-labs/windmill/issues/5549)) ([4fd0561](https://github.com/windmill-labs/windmill/commit/4fd056123907337efb5f5669975b337973a124cc))
29
+
30
+
31
+
### Bug Fixes
32
+
33
+
* ansible in agent mode can use inventory.ini ([9bdd301](https://github.com/windmill-labs/windmill/commit/9bdd301f5296fbfb631df9ff9100e92e0984ff64))
0 commit comments