autoworkflow 3.1.4 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +174 -11
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/pre-edit.sh +129 -0
  12. package/.claude/hooks/session-check.sh +79 -0
  13. package/.claude/settings.json +40 -6
  14. package/.claude/settings.local.json +3 -1
  15. package/.claude/skills/actix.md +337 -0
  16. package/.claude/skills/alembic.md +504 -0
  17. package/.claude/skills/angular.md +237 -0
  18. package/.claude/skills/api-design.md +187 -0
  19. package/.claude/skills/aspnet-core.md +377 -0
  20. package/.claude/skills/astro.md +245 -0
  21. package/.claude/skills/auth-clerk.md +327 -0
  22. package/.claude/skills/auth-firebase.md +367 -0
  23. package/.claude/skills/auth-nextauth.md +359 -0
  24. package/.claude/skills/auth-supabase.md +368 -0
  25. package/.claude/skills/axum.md +386 -0
  26. package/.claude/skills/blazor.md +456 -0
  27. package/.claude/skills/chi.md +348 -0
  28. package/.claude/skills/code-review.md +133 -0
  29. package/.claude/skills/csharp.md +296 -0
  30. package/.claude/skills/css-modules.md +325 -0
  31. package/.claude/skills/cypress.md +343 -0
  32. package/.claude/skills/debugging.md +133 -0
  33. package/.claude/skills/diesel.md +392 -0
  34. package/.claude/skills/django.md +301 -0
  35. package/.claude/skills/docker.md +319 -0
  36. package/.claude/skills/doctrine.md +473 -0
  37. package/.claude/skills/documentation.md +182 -0
  38. package/.claude/skills/dotnet.md +409 -0
  39. package/.claude/skills/drizzle.md +293 -0
  40. package/.claude/skills/echo.md +321 -0
  41. package/.claude/skills/eloquent.md +256 -0
  42. package/.claude/skills/emotion.md +426 -0
  43. package/.claude/skills/entity-framework.md +370 -0
  44. package/.claude/skills/express.md +316 -0
  45. package/.claude/skills/fastapi.md +329 -0
  46. package/.claude/skills/fastify.md +299 -0
  47. package/.claude/skills/fiber.md +315 -0
  48. package/.claude/skills/flask.md +322 -0
  49. package/.claude/skills/gin.md +342 -0
  50. package/.claude/skills/git.md +116 -0
  51. package/.claude/skills/github-actions.md +353 -0
  52. package/.claude/skills/go.md +377 -0
  53. package/.claude/skills/gorm.md +409 -0
  54. package/.claude/skills/graphql.md +478 -0
  55. package/.claude/skills/hibernate.md +379 -0
  56. package/.claude/skills/hono.md +306 -0
  57. package/.claude/skills/java.md +400 -0
  58. package/.claude/skills/jest.md +313 -0
  59. package/.claude/skills/jpa.md +282 -0
  60. package/.claude/skills/kotlin.md +347 -0
  61. package/.claude/skills/kubernetes.md +363 -0
  62. package/.claude/skills/laravel.md +414 -0
  63. package/.claude/skills/mcp-browser.md +320 -0
  64. package/.claude/skills/mcp-database.md +219 -0
  65. package/.claude/skills/mcp-fetch.md +241 -0
  66. package/.claude/skills/mcp-filesystem.md +204 -0
  67. package/.claude/skills/mcp-github.md +217 -0
  68. package/.claude/skills/mcp-memory.md +240 -0
  69. package/.claude/skills/mcp-search.md +218 -0
  70. package/.claude/skills/mcp-slack.md +262 -0
  71. package/.claude/skills/micronaut.md +388 -0
  72. package/.claude/skills/mongodb.md +319 -0
  73. package/.claude/skills/mongoose.md +355 -0
  74. package/.claude/skills/mysql.md +281 -0
  75. package/.claude/skills/nestjs.md +335 -0
  76. package/.claude/skills/nextjs-app-router.md +260 -0
  77. package/.claude/skills/nextjs-pages.md +172 -0
  78. package/.claude/skills/nuxt.md +202 -0
  79. package/.claude/skills/openapi.md +489 -0
  80. package/.claude/skills/performance.md +199 -0
  81. package/.claude/skills/php.md +398 -0
  82. package/.claude/skills/playwright.md +371 -0
  83. package/.claude/skills/postgresql.md +257 -0
  84. package/.claude/skills/prisma.md +293 -0
  85. package/.claude/skills/pydantic.md +304 -0
  86. package/.claude/skills/pytest.md +313 -0
  87. package/.claude/skills/python.md +272 -0
  88. package/.claude/skills/quarkus.md +377 -0
  89. package/.claude/skills/react.md +230 -0
  90. package/.claude/skills/redis.md +391 -0
  91. package/.claude/skills/refactoring.md +143 -0
  92. package/.claude/skills/remix.md +246 -0
  93. package/.claude/skills/rest-api.md +490 -0
  94. package/.claude/skills/rocket.md +366 -0
  95. package/.claude/skills/rust.md +341 -0
  96. package/.claude/skills/sass.md +380 -0
  97. package/.claude/skills/sea-orm.md +382 -0
  98. package/.claude/skills/security.md +167 -0
  99. package/.claude/skills/sequelize.md +395 -0
  100. package/.claude/skills/spring-boot.md +416 -0
  101. package/.claude/skills/sqlalchemy.md +269 -0
  102. package/.claude/skills/sqlx-rust.md +408 -0
  103. package/.claude/skills/state-jotai.md +346 -0
  104. package/.claude/skills/state-mobx.md +353 -0
  105. package/.claude/skills/state-pinia.md +431 -0
  106. package/.claude/skills/state-redux.md +337 -0
  107. package/.claude/skills/state-tanstack-query.md +434 -0
  108. package/.claude/skills/state-zustand.md +340 -0
  109. package/.claude/skills/styled-components.md +403 -0
  110. package/.claude/skills/svelte.md +238 -0
  111. package/.claude/skills/sveltekit.md +207 -0
  112. package/.claude/skills/symfony.md +437 -0
  113. package/.claude/skills/tailwind.md +279 -0
  114. package/.claude/skills/terraform.md +394 -0
  115. package/.claude/skills/testing-library.md +371 -0
  116. package/.claude/skills/trpc.md +426 -0
  117. package/.claude/skills/typeorm.md +368 -0
  118. package/.claude/skills/vitest.md +330 -0
  119. package/.claude/skills/vue.md +202 -0
  120. package/.claude/skills/warp.md +365 -0
  121. package/README.md +135 -52
  122. package/package.json +1 -1
  123. package/system/triggers.md +152 -11
@@ -0,0 +1,377 @@
1
+ # ASP.NET Core Skill
2
+
3
+ ## Program.cs (Minimal API)
4
+ \`\`\`csharp
5
+ var builder = WebApplication.CreateBuilder(args);
6
+
7
+ // Add services
8
+ builder.Services.AddControllers();
9
+ builder.Services.AddEndpointsApiExplorer();
10
+ builder.Services.AddSwaggerGen();
11
+
12
+ // Custom services
13
+ builder.Services.AddScoped<IUserService, UserService>();
14
+ builder.Services.AddDbContext<AppDbContext>(options =>
15
+ options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
16
+
17
+ // Authentication
18
+ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
19
+ .AddJwtBearer(options =>
20
+ {
21
+ options.TokenValidationParameters = new TokenValidationParameters
22
+ {
23
+ ValidateIssuer = true,
24
+ ValidateAudience = true,
25
+ ValidateLifetime = true,
26
+ ValidIssuer = builder.Configuration["Jwt:Issuer"],
27
+ ValidAudience = builder.Configuration["Jwt:Audience"],
28
+ IssuerSigningKey = new SymmetricSecurityKey(
29
+ Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
30
+ };
31
+ });
32
+
33
+ builder.Services.AddAuthorization();
34
+
35
+ var app = builder.Build();
36
+
37
+ // Middleware pipeline
38
+ if (app.Environment.IsDevelopment())
39
+ {
40
+ app.UseSwagger();
41
+ app.UseSwaggerUI();
42
+ }
43
+
44
+ app.UseExceptionHandler("/error");
45
+ app.UseHttpsRedirection();
46
+ app.UseAuthentication();
47
+ app.UseAuthorization();
48
+
49
+ app.MapControllers();
50
+
51
+ app.Run();
52
+ \`\`\`
53
+
54
+ ## Controller Pattern
55
+ \`\`\`csharp
56
+ [ApiController]
57
+ [Route("api/v1/[controller]")]
58
+ [Produces("application/json")]
59
+ public class UsersController : ControllerBase
60
+ {
61
+ private readonly IUserService _userService;
62
+
63
+ public UsersController(IUserService userService)
64
+ {
65
+ _userService = userService;
66
+ }
67
+
68
+ [HttpGet]
69
+ [ProducesResponseType(typeof(PagedResult<UserResponse>), StatusCodes.Status200OK)]
70
+ public async Task<ActionResult<PagedResult<UserResponse>>> GetUsers(
71
+ [FromQuery] int page = 1,
72
+ [FromQuery] int pageSize = 20,
73
+ CancellationToken ct = default)
74
+ {
75
+ var result = await _userService.GetAllAsync(page, pageSize, ct);
76
+ return Ok(result);
77
+ }
78
+
79
+ [HttpGet("{id}")]
80
+ [ProducesResponseType(typeof(UserResponse), StatusCodes.Status200OK)]
81
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
82
+ public async Task<ActionResult<UserResponse>> GetUser(string id, CancellationToken ct)
83
+ {
84
+ var user = await _userService.GetByIdAsync(id, ct);
85
+ return user is null ? NotFound() : Ok(user);
86
+ }
87
+
88
+ [HttpPost]
89
+ [ProducesResponseType(typeof(UserResponse), StatusCodes.Status201Created)]
90
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
91
+ public async Task<ActionResult<UserResponse>> CreateUser(
92
+ [FromBody] CreateUserRequest request,
93
+ CancellationToken ct)
94
+ {
95
+ var user = await _userService.CreateAsync(request, ct);
96
+ return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
97
+ }
98
+
99
+ [HttpPut("{id}")]
100
+ [ProducesResponseType(typeof(UserResponse), StatusCodes.Status200OK)]
101
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
102
+ public async Task<ActionResult<UserResponse>> UpdateUser(
103
+ string id,
104
+ [FromBody] UpdateUserRequest request,
105
+ CancellationToken ct)
106
+ {
107
+ var user = await _userService.UpdateAsync(id, request, ct);
108
+ return user is null ? NotFound() : Ok(user);
109
+ }
110
+
111
+ [HttpDelete("{id}")]
112
+ [Authorize(Roles = "Admin")]
113
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
114
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
115
+ public async Task<IActionResult> DeleteUser(string id, CancellationToken ct)
116
+ {
117
+ var deleted = await _userService.DeleteAsync(id, ct);
118
+ return deleted ? NoContent() : NotFound();
119
+ }
120
+ }
121
+ \`\`\`
122
+
123
+ ## Minimal APIs
124
+ \`\`\`csharp
125
+ var app = builder.Build();
126
+
127
+ // Group routes
128
+ var users = app.MapGroup("/api/v1/users")
129
+ .WithTags("Users");
130
+
131
+ users.MapGet("/", async (IUserService service, CancellationToken ct) =>
132
+ await service.GetAllAsync(ct));
133
+
134
+ users.MapGet("/{id}", async (string id, IUserService service, CancellationToken ct) =>
135
+ await service.GetByIdAsync(id, ct) is { } user
136
+ ? Results.Ok(user)
137
+ : Results.NotFound());
138
+
139
+ users.MapPost("/", async (CreateUserRequest request, IUserService service, CancellationToken ct) =>
140
+ {
141
+ var user = await service.CreateAsync(request, ct);
142
+ return Results.Created($"/api/v1/users/{user.Id}", user);
143
+ })
144
+ .RequireAuthorization();
145
+
146
+ // With validation
147
+ users.MapPost("/", async (
148
+ [FromBody] CreateUserRequest request,
149
+ IValidator<CreateUserRequest> validator,
150
+ IUserService service,
151
+ CancellationToken ct) =>
152
+ {
153
+ var validation = await validator.ValidateAsync(request, ct);
154
+ if (!validation.IsValid)
155
+ {
156
+ return Results.ValidationProblem(validation.ToDictionary());
157
+ }
158
+ var user = await service.CreateAsync(request, ct);
159
+ return Results.Created($"/api/v1/users/{user.Id}", user);
160
+ });
161
+ \`\`\`
162
+
163
+ ## Validation with FluentValidation
164
+ \`\`\`csharp
165
+ public record CreateUserRequest(string Email, string Name, string Password);
166
+
167
+ public class CreateUserRequestValidator : AbstractValidator<CreateUserRequest>
168
+ {
169
+ public CreateUserRequestValidator()
170
+ {
171
+ RuleFor(x => x.Email)
172
+ .NotEmpty()
173
+ .EmailAddress()
174
+ .MaximumLength(255);
175
+
176
+ RuleFor(x => x.Name)
177
+ .NotEmpty()
178
+ .MinimumLength(2)
179
+ .MaximumLength(100);
180
+
181
+ RuleFor(x => x.Password)
182
+ .NotEmpty()
183
+ .MinimumLength(8)
184
+ .Matches("[A-Z]").WithMessage("Password must contain uppercase letter")
185
+ .Matches("[0-9]").WithMessage("Password must contain digit");
186
+ }
187
+ }
188
+
189
+ // Registration
190
+ builder.Services.AddValidatorsFromAssemblyContaining<Program>();
191
+ \`\`\`
192
+
193
+ ## Exception Handling
194
+ \`\`\`csharp
195
+ // Global exception handler
196
+ public class GlobalExceptionHandler : IExceptionHandler
197
+ {
198
+ private readonly ILogger<GlobalExceptionHandler> _logger;
199
+
200
+ public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
201
+ {
202
+ _logger = logger;
203
+ }
204
+
205
+ public async ValueTask<bool> TryHandleAsync(
206
+ HttpContext context,
207
+ Exception exception,
208
+ CancellationToken ct)
209
+ {
210
+ _logger.LogError(exception, "Exception occurred: {Message}", exception.Message);
211
+
212
+ var (statusCode, response) = exception switch
213
+ {
214
+ NotFoundException e => (StatusCodes.Status404NotFound,
215
+ new ProblemDetails { Title = "Not Found", Detail = e.Message }),
216
+ ValidationException e => (StatusCodes.Status400BadRequest,
217
+ new ProblemDetails { Title = "Validation Error", Detail = e.Message }),
218
+ UnauthorizedAccessException => (StatusCodes.Status401Unauthorized,
219
+ new ProblemDetails { Title = "Unauthorized" }),
220
+ _ => (StatusCodes.Status500InternalServerError,
221
+ new ProblemDetails { Title = "Internal Server Error" })
222
+ };
223
+
224
+ context.Response.StatusCode = statusCode;
225
+ await context.Response.WriteAsJsonAsync(response, ct);
226
+ return true;
227
+ }
228
+ }
229
+
230
+ // Registration
231
+ builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
232
+ builder.Services.AddProblemDetails();
233
+
234
+ // In pipeline
235
+ app.UseExceptionHandler();
236
+ \`\`\`
237
+
238
+ ## Middleware
239
+ \`\`\`csharp
240
+ public class RequestTimingMiddleware
241
+ {
242
+ private readonly RequestDelegate _next;
243
+ private readonly ILogger<RequestTimingMiddleware> _logger;
244
+
245
+ public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
246
+ {
247
+ _next = next;
248
+ _logger = logger;
249
+ }
250
+
251
+ public async Task InvokeAsync(HttpContext context)
252
+ {
253
+ var stopwatch = Stopwatch.StartNew();
254
+
255
+ try
256
+ {
257
+ await _next(context);
258
+ }
259
+ finally
260
+ {
261
+ stopwatch.Stop();
262
+ _logger.LogInformation(
263
+ "{Method} {Path} responded {StatusCode} in {ElapsedMs}ms",
264
+ context.Request.Method,
265
+ context.Request.Path,
266
+ context.Response.StatusCode,
267
+ stopwatch.ElapsedMilliseconds);
268
+ }
269
+ }
270
+ }
271
+
272
+ // Registration
273
+ app.UseMiddleware<RequestTimingMiddleware>();
274
+
275
+ // Or as extension method
276
+ public static class MiddlewareExtensions
277
+ {
278
+ public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder app)
279
+ => app.UseMiddleware<RequestTimingMiddleware>();
280
+ }
281
+ \`\`\`
282
+
283
+ ## Configuration
284
+ \`\`\`csharp
285
+ // appsettings.json
286
+ {
287
+ "ConnectionStrings": {
288
+ "Default": "Host=localhost;Database=myapp;Username=postgres;Password=secret"
289
+ },
290
+ "Jwt": {
291
+ "Key": "your-secret-key-here",
292
+ "Issuer": "your-app",
293
+ "Audience": "your-app"
294
+ }
295
+ }
296
+
297
+ // Strongly-typed configuration
298
+ public class JwtSettings
299
+ {
300
+ public required string Key { get; init; }
301
+ public required string Issuer { get; init; }
302
+ public required string Audience { get; init; }
303
+ public int ExpirationMinutes { get; init; } = 60;
304
+ }
305
+
306
+ // Registration
307
+ builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("Jwt"));
308
+
309
+ // Usage
310
+ public class TokenService
311
+ {
312
+ private readonly JwtSettings _settings;
313
+
314
+ public TokenService(IOptions<JwtSettings> options)
315
+ {
316
+ _settings = options.Value;
317
+ }
318
+ }
319
+ \`\`\`
320
+
321
+ ## Testing
322
+ \`\`\`csharp
323
+ public class UsersControllerTests : IClassFixture<WebApplicationFactory<Program>>
324
+ {
325
+ private readonly HttpClient _client;
326
+
327
+ public UsersControllerTests(WebApplicationFactory<Program> factory)
328
+ {
329
+ _client = factory.WithWebHostBuilder(builder =>
330
+ {
331
+ builder.ConfigureServices(services =>
332
+ {
333
+ // Replace with test database
334
+ services.RemoveAll<DbContextOptions<AppDbContext>>();
335
+ services.AddDbContext<AppDbContext>(options =>
336
+ options.UseInMemoryDatabase("TestDb"));
337
+ });
338
+ }).CreateClient();
339
+ }
340
+
341
+ [Fact]
342
+ public async Task GetUser_ReturnsUser_WhenExists()
343
+ {
344
+ var response = await _client.GetAsync("/api/v1/users/1");
345
+
346
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
347
+ var user = await response.Content.ReadFromJsonAsync<UserResponse>();
348
+ user.Should().NotBeNull();
349
+ }
350
+
351
+ [Fact]
352
+ public async Task CreateUser_ReturnsCreated_WithValidData()
353
+ {
354
+ var request = new CreateUserRequest("test@example.com", "Test", "Password123");
355
+
356
+ var response = await _client.PostAsJsonAsync("/api/v1/users", request);
357
+
358
+ response.StatusCode.Should().Be(HttpStatusCode.Created);
359
+ response.Headers.Location.Should().NotBeNull();
360
+ }
361
+ }
362
+ \`\`\`
363
+
364
+ ## ✅ DO
365
+ - Use \`[ApiController]\` attribute for automatic model validation
366
+ - Use \`CancellationToken\` in all async endpoints
367
+ - Return \`ActionResult<T>\` for typed responses
368
+ - Use \`IOptions<T>\` for configuration
369
+ - Use global exception handling
370
+ - Use ProblemDetails for error responses
371
+
372
+ ## ❌ DON'T
373
+ - Don't inject \`IConfiguration\` directly - use \`IOptions<T>\`
374
+ - Don't use \`[FromBody]\` on complex types with \`[ApiController]\`
375
+ - Don't forget \`ConfigureAwait(false)\` in library code
376
+ - Don't catch exceptions in controllers - use global handler
377
+ - Don't return entities - use DTOs/records
@@ -0,0 +1,245 @@
1
+ # Astro Skill
2
+
3
+ ## Project Structure
4
+ \`\`\`
5
+ src/
6
+ ├── pages/ # File-based routing
7
+ │ ├── index.astro # /
8
+ │ ├── about.astro # /about
9
+ │ ├── posts/
10
+ │ │ ├── index.astro # /posts
11
+ │ │ └── [slug].astro # /posts/:slug
12
+ │ └── api/
13
+ │ └── users.ts # API endpoint
14
+ ├── layouts/ # Page layouts
15
+ ├── components/ # UI components
16
+ ├── content/ # Content collections
17
+ │ └── blog/
18
+ │ ├── post-1.md
19
+ │ └── post-2.mdx
20
+ └── styles/ # Global styles
21
+ \`\`\`
22
+
23
+ ## Astro Components
24
+ \`\`\`astro
25
+ ---
26
+ // Component script (runs at build time)
27
+ import Layout from '../layouts/Layout.astro';
28
+ import Card from '../components/Card.astro';
29
+
30
+ // Props
31
+ interface Props {
32
+ title: string;
33
+ description?: string;
34
+ }
35
+
36
+ const { title, description = 'Default description' } = Astro.props;
37
+
38
+ // Fetch data at build time
39
+ const response = await fetch('https://api.example.com/posts');
40
+ const posts = await response.json();
41
+
42
+ // Access URL params
43
+ const { slug } = Astro.params;
44
+ ---
45
+
46
+ <Layout title={title}>
47
+ <h1>{title}</h1>
48
+ <p>{description}</p>
49
+
50
+ <ul>
51
+ {posts.map((post) => (
52
+ <li>
53
+ <Card title={post.title} href={\`/posts/\${post.slug}\`} />
54
+ </li>
55
+ ))}
56
+ </ul>
57
+ </Layout>
58
+
59
+ <style>
60
+ /* Scoped styles by default */
61
+ h1 {
62
+ color: navy;
63
+ }
64
+ </style>
65
+ \`\`\`
66
+
67
+ ## Component Islands (Partial Hydration)
68
+ \`\`\`astro
69
+ ---
70
+ import ReactCounter from './Counter.tsx';
71
+ import VueWidget from './Widget.vue';
72
+ import SvelteForm from './Form.svelte';
73
+ ---
74
+
75
+ <!-- No JS - static HTML only -->
76
+ <ReactCounter />
77
+
78
+ <!-- Hydrate immediately on page load -->
79
+ <ReactCounter client:load />
80
+
81
+ <!-- Hydrate when component is visible -->
82
+ <ReactCounter client:visible />
83
+
84
+ <!-- Hydrate on idle (requestIdleCallback) -->
85
+ <ReactCounter client:idle />
86
+
87
+ <!-- Hydrate on media query match -->
88
+ <VueWidget client:media="(max-width: 768px)" />
89
+
90
+ <!-- Hydrate only on client (skip SSR) -->
91
+ <SvelteForm client:only="svelte" />
92
+ \`\`\`
93
+
94
+ ## Content Collections
95
+ \`\`\`typescript
96
+ // src/content/config.ts
97
+ import { defineCollection, z } from 'astro:content';
98
+
99
+ const blogCollection = defineCollection({
100
+ type: 'content',
101
+ schema: z.object({
102
+ title: z.string(),
103
+ description: z.string(),
104
+ pubDate: z.date(),
105
+ author: z.string(),
106
+ tags: z.array(z.string()).optional(),
107
+ image: z.string().optional(),
108
+ }),
109
+ });
110
+
111
+ export const collections = {
112
+ blog: blogCollection,
113
+ };
114
+
115
+ // Usage in pages
116
+ ---
117
+ import { getCollection, getEntry } from 'astro:content';
118
+
119
+ // Get all posts
120
+ const posts = await getCollection('blog');
121
+
122
+ // Filter posts
123
+ const publishedPosts = await getCollection('blog', ({ data }) => {
124
+ return data.pubDate < new Date();
125
+ });
126
+
127
+ // Get single entry
128
+ const post = await getEntry('blog', 'my-post-slug');
129
+ ---
130
+
131
+ {posts.map((post) => (
132
+ <article>
133
+ <h2>{post.data.title}</h2>
134
+ <time>{post.data.pubDate.toDateString()}</time>
135
+ </article>
136
+ ))}
137
+ \`\`\`
138
+
139
+ ## Dynamic Routes
140
+ \`\`\`astro
141
+ ---
142
+ // src/pages/posts/[slug].astro
143
+ import { getCollection } from 'astro:content';
144
+
145
+ export async function getStaticPaths() {
146
+ const posts = await getCollection('blog');
147
+ return posts.map((post) => ({
148
+ params: { slug: post.slug },
149
+ props: { post },
150
+ }));
151
+ }
152
+
153
+ const { post } = Astro.props;
154
+ const { Content } = await post.render();
155
+ ---
156
+
157
+ <article>
158
+ <h1>{post.data.title}</h1>
159
+ <Content />
160
+ </article>
161
+ \`\`\`
162
+
163
+ ## API Routes
164
+ \`\`\`typescript
165
+ // src/pages/api/users.ts
166
+ import type { APIRoute } from 'astro';
167
+
168
+ export const GET: APIRoute = async ({ request }) => {
169
+ const users = await db.user.findMany();
170
+
171
+ return new Response(JSON.stringify(users), {
172
+ status: 200,
173
+ headers: { 'Content-Type': 'application/json' },
174
+ });
175
+ };
176
+
177
+ export const POST: APIRoute = async ({ request }) => {
178
+ const data = await request.json();
179
+
180
+ const user = await db.user.create({ data });
181
+
182
+ return new Response(JSON.stringify(user), {
183
+ status: 201,
184
+ headers: { 'Content-Type': 'application/json' },
185
+ });
186
+ };
187
+ \`\`\`
188
+
189
+ ## Middleware
190
+ \`\`\`typescript
191
+ // src/middleware.ts
192
+ import { defineMiddleware } from 'astro:middleware';
193
+
194
+ export const onRequest = defineMiddleware(async (context, next) => {
195
+ // Run before each page/endpoint
196
+ const token = context.cookies.get('token');
197
+
198
+ if (token) {
199
+ context.locals.user = await validateToken(token.value);
200
+ }
201
+
202
+ // Protected routes
203
+ if (context.url.pathname.startsWith('/dashboard') && !context.locals.user) {
204
+ return context.redirect('/login');
205
+ }
206
+
207
+ return next();
208
+ });
209
+ \`\`\`
210
+
211
+ ## SSR Mode (Hybrid/Server)
212
+ \`\`\`typescript
213
+ // astro.config.mjs
214
+ export default defineConfig({
215
+ output: 'hybrid', // or 'server' for full SSR
216
+
217
+ adapter: vercel(), // or netlify(), node(), etc.
218
+ });
219
+
220
+ // Per-page opt-in/out
221
+ ---
222
+ // Static page in hybrid mode
223
+ export const prerender = true;
224
+ ---
225
+
226
+ ---
227
+ // Dynamic page (SSR)
228
+ export const prerender = false;
229
+ const user = Astro.locals.user;
230
+ ---
231
+ \`\`\`
232
+
233
+ ## ❌ DON'T
234
+ - Hydrate everything (defeats the purpose)
235
+ - Use client:load when client:visible works
236
+ - Forget to define content collection schemas
237
+ - Skip prerender in hybrid mode for static pages
238
+
239
+ ## ✅ DO
240
+ - Default to zero JavaScript
241
+ - Hydrate only interactive components
242
+ - Use content collections for structured content
243
+ - Use middleware for auth/redirects
244
+ - Choose the right hydration directive
245
+ - Use hybrid mode for mixed static/dynamic