autoworkflow 3.1.5 → 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 +26 -0
  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,409 @@
1
+ # .NET Skill
2
+
3
+ ## Clean Architecture Structure
4
+ \`\`\`
5
+ src/
6
+ ├── MyApp.Api/ # Presentation layer (Controllers, Middleware)
7
+ │ ├── Controllers/
8
+ │ ├── Middleware/
9
+ │ └── Program.cs
10
+ ├── MyApp.Application/ # Application layer (Use cases, DTOs)
11
+ │ ├── Users/
12
+ │ │ ├── Commands/
13
+ │ │ ├── Queries/
14
+ │ │ └── DTOs/
15
+ │ └── Common/
16
+ │ ├── Interfaces/
17
+ │ └── Behaviors/
18
+ ├── MyApp.Domain/ # Domain layer (Entities, Value Objects)
19
+ │ ├── Entities/
20
+ │ ├── ValueObjects/
21
+ │ ├── Enums/
22
+ │ └── Events/
23
+ ├── MyApp.Infrastructure/ # Infrastructure (EF, External Services)
24
+ │ ├── Persistence/
25
+ │ ├── Services/
26
+ │ └── DependencyInjection.cs
27
+ tests/
28
+ ├── MyApp.UnitTests/
29
+ ├── MyApp.IntegrationTests/
30
+ └── MyApp.ArchitectureTests/
31
+ \`\`\`
32
+
33
+ ## Dependency Injection
34
+ \`\`\`csharp
35
+ // Register services by convention
36
+ public static class DependencyInjection
37
+ {
38
+ public static IServiceCollection AddApplication(this IServiceCollection services)
39
+ {
40
+ // Register all services implementing interface
41
+ services.Scan(scan => scan
42
+ .FromAssemblyOf<IUserService>()
43
+ .AddClasses(classes => classes.AssignableTo(typeof(IRequestHandler<,>)))
44
+ .AsImplementedInterfaces()
45
+ .WithScopedLifetime());
46
+
47
+ // MediatR
48
+ services.AddMediatR(cfg =>
49
+ cfg.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly));
50
+
51
+ // FluentValidation
52
+ services.AddValidatorsFromAssembly(typeof(DependencyInjection).Assembly);
53
+
54
+ return services;
55
+ }
56
+
57
+ public static IServiceCollection AddInfrastructure(
58
+ this IServiceCollection services,
59
+ IConfiguration configuration)
60
+ {
61
+ // Database
62
+ services.AddDbContext<AppDbContext>(options =>
63
+ options.UseNpgsql(configuration.GetConnectionString("Default")));
64
+
65
+ // Repositories
66
+ services.AddScoped<IUserRepository, UserRepository>();
67
+
68
+ // External services
69
+ services.AddHttpClient<IEmailService, EmailService>(client =>
70
+ {
71
+ client.BaseAddress = new Uri(configuration["Email:BaseUrl"]!);
72
+ });
73
+
74
+ return services;
75
+ }
76
+ }
77
+
78
+ // Program.cs
79
+ var builder = WebApplication.CreateBuilder(args);
80
+
81
+ builder.Services
82
+ .AddApplication()
83
+ .AddInfrastructure(builder.Configuration);
84
+ \`\`\`
85
+
86
+ ## Configuration with Options Pattern
87
+ \`\`\`csharp
88
+ // Settings classes
89
+ public class DatabaseSettings
90
+ {
91
+ public const string SectionName = "Database";
92
+
93
+ public required string ConnectionString { get; init; }
94
+ public int MaxRetryCount { get; init; } = 3;
95
+ public int CommandTimeout { get; init; } = 30;
96
+ }
97
+
98
+ public class JwtSettings
99
+ {
100
+ public const string SectionName = "Jwt";
101
+
102
+ public required string Secret { get; init; }
103
+ public required string Issuer { get; init; }
104
+ public required string Audience { get; init; }
105
+ public int ExpirationMinutes { get; init; } = 60;
106
+ }
107
+
108
+ // Registration with validation
109
+ services.AddOptions<JwtSettings>()
110
+ .Bind(configuration.GetSection(JwtSettings.SectionName))
111
+ .ValidateDataAnnotations()
112
+ .ValidateOnStart();
113
+
114
+ // Usage
115
+ public class TokenService
116
+ {
117
+ private readonly JwtSettings _settings;
118
+
119
+ public TokenService(IOptions<JwtSettings> options)
120
+ {
121
+ _settings = options.Value;
122
+ }
123
+
124
+ // Or for settings that can change at runtime
125
+ public TokenService(IOptionsMonitor<JwtSettings> options)
126
+ {
127
+ options.OnChange(settings => _settings = settings);
128
+ }
129
+ }
130
+ \`\`\`
131
+
132
+ ## Result Pattern (No Exceptions for Expected Failures)
133
+ \`\`\`csharp
134
+ // Result type
135
+ public class Result<T>
136
+ {
137
+ public bool IsSuccess { get; }
138
+ public T? Value { get; }
139
+ public Error? Error { get; }
140
+
141
+ private Result(T value)
142
+ {
143
+ IsSuccess = true;
144
+ Value = value;
145
+ }
146
+
147
+ private Result(Error error)
148
+ {
149
+ IsSuccess = false;
150
+ Error = error;
151
+ }
152
+
153
+ public static Result<T> Success(T value) => new(value);
154
+ public static Result<T> Failure(Error error) => new(error);
155
+
156
+ public TResult Match<TResult>(
157
+ Func<T, TResult> onSuccess,
158
+ Func<Error, TResult> onFailure) =>
159
+ IsSuccess ? onSuccess(Value!) : onFailure(Error!);
160
+ }
161
+
162
+ public record Error(string Code, string Message);
163
+
164
+ public static class Errors
165
+ {
166
+ public static class User
167
+ {
168
+ public static Error NotFound(string id) =>
169
+ new("User.NotFound", $"User with id '{id}' not found");
170
+
171
+ public static Error EmailTaken(string email) =>
172
+ new("User.EmailTaken", $"Email '{email}' is already in use");
173
+ }
174
+ }
175
+
176
+ // Usage in service
177
+ public async Task<Result<UserResponse>> GetUserAsync(string id, CancellationToken ct)
178
+ {
179
+ var user = await _repository.GetByIdAsync(id, ct);
180
+
181
+ if (user is null)
182
+ return Result<UserResponse>.Failure(Errors.User.NotFound(id));
183
+
184
+ return Result<UserResponse>.Success(UserResponse.From(user));
185
+ }
186
+
187
+ // Usage in controller
188
+ [HttpGet("{id}")]
189
+ public async Task<IActionResult> GetUser(string id, CancellationToken ct)
190
+ {
191
+ var result = await _userService.GetUserAsync(id, ct);
192
+
193
+ return result.Match<IActionResult>(
194
+ onSuccess: user => Ok(user),
195
+ onFailure: error => error.Code switch
196
+ {
197
+ "User.NotFound" => NotFound(error),
198
+ _ => BadRequest(error)
199
+ });
200
+ }
201
+ \`\`\`
202
+
203
+ ## MediatR with CQRS
204
+ \`\`\`csharp
205
+ // Query
206
+ public record GetUserQuery(string Id) : IRequest<Result<UserResponse>>;
207
+
208
+ public class GetUserQueryHandler : IRequestHandler<GetUserQuery, Result<UserResponse>>
209
+ {
210
+ private readonly IUserRepository _repository;
211
+
212
+ public GetUserQueryHandler(IUserRepository repository)
213
+ {
214
+ _repository = repository;
215
+ }
216
+
217
+ public async Task<Result<UserResponse>> Handle(
218
+ GetUserQuery request,
219
+ CancellationToken ct)
220
+ {
221
+ var user = await _repository.GetByIdAsync(request.Id, ct);
222
+
223
+ if (user is null)
224
+ return Result<UserResponse>.Failure(Errors.User.NotFound(request.Id));
225
+
226
+ return Result<UserResponse>.Success(UserResponse.From(user));
227
+ }
228
+ }
229
+
230
+ // Command
231
+ public record CreateUserCommand(string Email, string Name, string Password)
232
+ : IRequest<Result<UserResponse>>;
233
+
234
+ public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Result<UserResponse>>
235
+ {
236
+ private readonly IUserRepository _repository;
237
+ private readonly IPasswordHasher _hasher;
238
+
239
+ public CreateUserCommandHandler(IUserRepository repository, IPasswordHasher hasher)
240
+ {
241
+ _repository = repository;
242
+ _hasher = hasher;
243
+ }
244
+
245
+ public async Task<Result<UserResponse>> Handle(
246
+ CreateUserCommand request,
247
+ CancellationToken ct)
248
+ {
249
+ if (await _repository.ExistsByEmailAsync(request.Email, ct))
250
+ return Result<UserResponse>.Failure(Errors.User.EmailTaken(request.Email));
251
+
252
+ var user = new User
253
+ {
254
+ Email = request.Email,
255
+ Name = request.Name,
256
+ PasswordHash = _hasher.Hash(request.Password)
257
+ };
258
+
259
+ await _repository.AddAsync(user, ct);
260
+
261
+ return Result<UserResponse>.Success(UserResponse.From(user));
262
+ }
263
+ }
264
+
265
+ // Controller usage
266
+ [HttpPost]
267
+ public async Task<IActionResult> CreateUser(
268
+ CreateUserRequest request,
269
+ CancellationToken ct)
270
+ {
271
+ var command = new CreateUserCommand(request.Email, request.Name, request.Password);
272
+ var result = await _mediator.Send(command, ct);
273
+
274
+ return result.Match<IActionResult>(
275
+ onSuccess: user => CreatedAtAction(nameof(GetUser), new { id = user.Id }, user),
276
+ onFailure: error => BadRequest(error));
277
+ }
278
+ \`\`\`
279
+
280
+ ## Pipeline Behaviors (Cross-Cutting Concerns)
281
+ \`\`\`csharp
282
+ // Validation behavior
283
+ public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
284
+ where TRequest : IRequest<TResponse>
285
+ {
286
+ private readonly IEnumerable<IValidator<TRequest>> _validators;
287
+
288
+ public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
289
+ {
290
+ _validators = validators;
291
+ }
292
+
293
+ public async Task<TResponse> Handle(
294
+ TRequest request,
295
+ RequestHandlerDelegate<TResponse> next,
296
+ CancellationToken ct)
297
+ {
298
+ if (!_validators.Any())
299
+ return await next();
300
+
301
+ var context = new ValidationContext<TRequest>(request);
302
+ var failures = _validators
303
+ .Select(v => v.Validate(context))
304
+ .SelectMany(r => r.Errors)
305
+ .Where(f => f is not null)
306
+ .ToList();
307
+
308
+ if (failures.Any())
309
+ throw new ValidationException(failures);
310
+
311
+ return await next();
312
+ }
313
+ }
314
+
315
+ // Logging behavior
316
+ public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
317
+ where TRequest : IRequest<TResponse>
318
+ {
319
+ private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
320
+
321
+ public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
322
+ {
323
+ _logger = logger;
324
+ }
325
+
326
+ public async Task<TResponse> Handle(
327
+ TRequest request,
328
+ RequestHandlerDelegate<TResponse> next,
329
+ CancellationToken ct)
330
+ {
331
+ var requestName = typeof(TRequest).Name;
332
+ _logger.LogInformation("Handling {RequestName}", requestName);
333
+
334
+ var stopwatch = Stopwatch.StartNew();
335
+ var response = await next();
336
+ stopwatch.Stop();
337
+
338
+ _logger.LogInformation("Handled {RequestName} in {ElapsedMs}ms",
339
+ requestName, stopwatch.ElapsedMilliseconds);
340
+
341
+ return response;
342
+ }
343
+ }
344
+
345
+ // Registration
346
+ services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
347
+ services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
348
+ \`\`\`
349
+
350
+ ## Testing
351
+ \`\`\`csharp
352
+ public class GetUserQueryHandlerTests
353
+ {
354
+ private readonly Mock<IUserRepository> _repositoryMock;
355
+ private readonly GetUserQueryHandler _handler;
356
+
357
+ public GetUserQueryHandlerTests()
358
+ {
359
+ _repositoryMock = new Mock<IUserRepository>();
360
+ _handler = new GetUserQueryHandler(_repositoryMock.Object);
361
+ }
362
+
363
+ [Fact]
364
+ public async Task Handle_WhenUserExists_ReturnsSuccess()
365
+ {
366
+ // Arrange
367
+ var user = new User { Id = "1", Email = "test@example.com", Name = "Test" };
368
+ _repositoryMock.Setup(r => r.GetByIdAsync("1", It.IsAny<CancellationToken>()))
369
+ .ReturnsAsync(user);
370
+
371
+ // Act
372
+ var result = await _handler.Handle(new GetUserQuery("1"), CancellationToken.None);
373
+
374
+ // Assert
375
+ result.IsSuccess.Should().BeTrue();
376
+ result.Value!.Email.Should().Be("test@example.com");
377
+ }
378
+
379
+ [Fact]
380
+ public async Task Handle_WhenUserNotExists_ReturnsFailure()
381
+ {
382
+ // Arrange
383
+ _repositoryMock.Setup(r => r.GetByIdAsync("999", It.IsAny<CancellationToken>()))
384
+ .ReturnsAsync((User?)null);
385
+
386
+ // Act
387
+ var result = await _handler.Handle(new GetUserQuery("999"), CancellationToken.None);
388
+
389
+ // Assert
390
+ result.IsSuccess.Should().BeFalse();
391
+ result.Error!.Code.Should().Be("User.NotFound");
392
+ }
393
+ }
394
+ \`\`\`
395
+
396
+ ## ✅ DO
397
+ - Use Clean Architecture / Vertical Slice Architecture
398
+ - Use dependency injection for all dependencies
399
+ - Use IOptions<T> for configuration
400
+ - Use Result pattern instead of exceptions for expected failures
401
+ - Use MediatR for CQRS (optional but recommended)
402
+ - Use pipeline behaviors for cross-cutting concerns
403
+
404
+ ## ❌ DON'T
405
+ - Don't inject IConfiguration directly
406
+ - Don't use static classes for business logic
407
+ - Don't throw exceptions for validation errors
408
+ - Don't put business logic in controllers
409
+ - Don't reference Infrastructure from Domain
@@ -0,0 +1,293 @@
1
+ # Drizzle ORM Skill
2
+
3
+ ## Setup & Configuration
4
+ \`\`\`typescript
5
+ // drizzle.config.ts
6
+ import type { Config } from 'drizzle-kit';
7
+
8
+ export default {
9
+ schema: './src/db/schema.ts',
10
+ out: './drizzle',
11
+ driver: 'pg',
12
+ dbCredentials: {
13
+ connectionString: process.env.DATABASE_URL!,
14
+ },
15
+ } satisfies Config;
16
+
17
+ // src/db/index.ts
18
+ import { drizzle } from 'drizzle-orm/postgres-js';
19
+ import postgres from 'postgres';
20
+ import * as schema from './schema';
21
+
22
+ const client = postgres(process.env.DATABASE_URL!);
23
+ export const db = drizzle(client, { schema });
24
+ \`\`\`
25
+
26
+ ## Schema Definition
27
+ \`\`\`typescript
28
+ // src/db/schema.ts
29
+ import {
30
+ pgTable, uuid, text, timestamp, boolean, integer,
31
+ pgEnum, primaryKey, index, uniqueIndex
32
+ } from 'drizzle-orm/pg-core';
33
+ import { relations } from 'drizzle-orm';
34
+
35
+ // Enums
36
+ export const roleEnum = pgEnum('role', ['user', 'admin', 'moderator']);
37
+
38
+ // Users table
39
+ export const users = pgTable('users', {
40
+ id: uuid('id').primaryKey().defaultRandom(),
41
+ email: text('email').notNull().unique(),
42
+ name: text('name'),
43
+ role: roleEnum('role').default('user').notNull(),
44
+ createdAt: timestamp('created_at').defaultNow().notNull(),
45
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
46
+ deletedAt: timestamp('deleted_at'),
47
+ }, (table) => ({
48
+ emailIdx: uniqueIndex('email_idx').on(table.email),
49
+ roleIdx: index('role_idx').on(table.role),
50
+ }));
51
+
52
+ // Posts table
53
+ export const posts = pgTable('posts', {
54
+ id: uuid('id').primaryKey().defaultRandom(),
55
+ title: text('title').notNull(),
56
+ content: text('content'),
57
+ published: boolean('published').default(false).notNull(),
58
+ authorId: uuid('author_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
59
+ createdAt: timestamp('created_at').defaultNow().notNull(),
60
+ }, (table) => ({
61
+ authorIdx: index('author_idx').on(table.authorId),
62
+ }));
63
+
64
+ // Categories with many-to-many
65
+ export const categories = pgTable('categories', {
66
+ id: uuid('id').primaryKey().defaultRandom(),
67
+ name: text('name').notNull().unique(),
68
+ });
69
+
70
+ export const postsToCategories = pgTable('posts_to_categories', {
71
+ postId: uuid('post_id').notNull().references(() => posts.id, { onDelete: 'cascade' }),
72
+ categoryId: uuid('category_id').notNull().references(() => categories.id, { onDelete: 'cascade' }),
73
+ }, (table) => ({
74
+ pk: primaryKey({ columns: [table.postId, table.categoryId] }),
75
+ }));
76
+
77
+ // Relations (for query builder)
78
+ export const usersRelations = relations(users, ({ many }) => ({
79
+ posts: many(posts),
80
+ }));
81
+
82
+ export const postsRelations = relations(posts, ({ one, many }) => ({
83
+ author: one(users, {
84
+ fields: [posts.authorId],
85
+ references: [users.id],
86
+ }),
87
+ categories: many(postsToCategories),
88
+ }));
89
+ \`\`\`
90
+
91
+ ## Type Inference
92
+ \`\`\`typescript
93
+ import { InferSelectModel, InferInsertModel } from 'drizzle-orm';
94
+ import { users, posts } from './schema';
95
+
96
+ // Infer types from schema
97
+ export type User = InferSelectModel<typeof users>;
98
+ export type NewUser = InferInsertModel<typeof users>;
99
+ export type Post = InferSelectModel<typeof posts>;
100
+ export type NewPost = InferInsertModel<typeof posts>;
101
+
102
+ // With relations
103
+ export type UserWithPosts = User & { posts: Post[] };
104
+ \`\`\`
105
+
106
+ ## Query Patterns
107
+ \`\`\`typescript
108
+ import { eq, and, or, gt, lt, like, isNull, sql, desc, asc } from 'drizzle-orm';
109
+
110
+ // Basic CRUD
111
+ // Create
112
+ const [newUser] = await db.insert(users)
113
+ .values({ email: 'user@example.com', name: 'John' })
114
+ .returning();
115
+
116
+ // Read
117
+ const allUsers = await db.select().from(users);
118
+ const user = await db.select().from(users).where(eq(users.id, userId)).limit(1);
119
+
120
+ // Update
121
+ const [updated] = await db.update(users)
122
+ .set({ name: 'Jane', updatedAt: new Date() })
123
+ .where(eq(users.id, userId))
124
+ .returning();
125
+
126
+ // Delete
127
+ await db.delete(users).where(eq(users.id, userId));
128
+
129
+ // Complex filters
130
+ const filteredUsers = await db.select()
131
+ .from(users)
132
+ .where(
133
+ and(
134
+ eq(users.role, 'user'),
135
+ isNull(users.deletedAt),
136
+ or(
137
+ like(users.name, '%John%'),
138
+ like(users.email, '%@company.com')
139
+ )
140
+ )
141
+ )
142
+ .orderBy(desc(users.createdAt))
143
+ .limit(20)
144
+ .offset(0);
145
+
146
+ // Select specific columns
147
+ const emails = await db.select({
148
+ id: users.id,
149
+ email: users.email,
150
+ }).from(users);
151
+
152
+ // Aggregations
153
+ const [{ count }] = await db.select({
154
+ count: sql<number>\`count(*)\`::int,
155
+ }).from(users);
156
+
157
+ const stats = await db.select({
158
+ role: users.role,
159
+ count: sql<number>\`count(*)\`::int,
160
+ }).from(users).groupBy(users.role);
161
+ \`\`\`
162
+
163
+ ## Joins & Relations
164
+ \`\`\`typescript
165
+ // Manual joins
166
+ const usersWithPosts = await db.select({
167
+ user: users,
168
+ post: posts,
169
+ })
170
+ .from(users)
171
+ .leftJoin(posts, eq(users.id, posts.authorId))
172
+ .where(eq(posts.published, true));
173
+
174
+ // Inner join
175
+ const authoredPosts = await db.select()
176
+ .from(posts)
177
+ .innerJoin(users, eq(posts.authorId, users.id));
178
+
179
+ // Using relational queries (requires relations defined)
180
+ const usersWithRelations = await db.query.users.findMany({
181
+ with: {
182
+ posts: {
183
+ where: eq(posts.published, true),
184
+ orderBy: desc(posts.createdAt),
185
+ limit: 5,
186
+ },
187
+ },
188
+ where: isNull(users.deletedAt),
189
+ });
190
+
191
+ // Find first with relations
192
+ const user = await db.query.users.findFirst({
193
+ where: eq(users.id, userId),
194
+ with: { posts: true },
195
+ });
196
+ \`\`\`
197
+
198
+ ## Transactions
199
+ \`\`\`typescript
200
+ // Basic transaction
201
+ const result = await db.transaction(async (tx) => {
202
+ const [user] = await tx.insert(users)
203
+ .values({ email: 'test@example.com', name: 'Test' })
204
+ .returning();
205
+
206
+ const [post] = await tx.insert(posts)
207
+ .values({ title: 'First Post', authorId: user.id })
208
+ .returning();
209
+
210
+ return { user, post };
211
+ });
212
+
213
+ // With rollback on error
214
+ await db.transaction(async (tx) => {
215
+ await tx.update(users)
216
+ .set({ balance: sql\`balance - 100\` })
217
+ .where(eq(users.id, senderId));
218
+
219
+ const [sender] = await tx.select({ balance: users.balance })
220
+ .from(users)
221
+ .where(eq(users.id, senderId));
222
+
223
+ if (sender.balance < 0) {
224
+ tx.rollback(); // Abort transaction
225
+ }
226
+
227
+ await tx.update(users)
228
+ .set({ balance: sql\`balance + 100\` })
229
+ .where(eq(users.id, receiverId));
230
+ });
231
+ \`\`\`
232
+
233
+ ## Prepared Statements
234
+ \`\`\`typescript
235
+ // Prepare for better performance
236
+ const getUserById = db.select()
237
+ .from(users)
238
+ .where(eq(users.id, sql.placeholder('id')))
239
+ .prepare('get_user_by_id');
240
+
241
+ // Execute with params
242
+ const user = await getUserById.execute({ id: userId });
243
+
244
+ // Prepared insert
245
+ const insertUser = db.insert(users)
246
+ .values({
247
+ email: sql.placeholder('email'),
248
+ name: sql.placeholder('name'),
249
+ })
250
+ .returning()
251
+ .prepare('insert_user');
252
+
253
+ const [newUser] = await insertUser.execute({
254
+ email: 'user@example.com',
255
+ name: 'John',
256
+ });
257
+ \`\`\`
258
+
259
+ ## Migrations
260
+ \`\`\`bash
261
+ # Generate migration from schema changes
262
+ npx drizzle-kit generate:pg
263
+
264
+ # Push schema directly (dev only)
265
+ npx drizzle-kit push:pg
266
+
267
+ # View in Drizzle Studio
268
+ npx drizzle-kit studio
269
+ \`\`\`
270
+
271
+ \`\`\`typescript
272
+ // Run migrations programmatically
273
+ import { migrate } from 'drizzle-orm/postgres-js/migrator';
274
+ import { db } from './db';
275
+
276
+ await migrate(db, { migrationsFolder: './drizzle' });
277
+ \`\`\`
278
+
279
+ ## ❌ DON'T
280
+ - Forget to define relations for relational queries
281
+ - Use raw SQL when Drizzle operators exist
282
+ - Skip type inference (use InferSelectModel/InferInsertModel)
283
+ - Ignore indexes on frequently queried columns
284
+ - Use \`db.select().from()\` without limits on large tables
285
+
286
+ ## ✅ DO
287
+ - Define relations for the query builder
288
+ - Use type inference for type safety
289
+ - Use prepared statements for repeated queries
290
+ - Use transactions for multi-step operations
291
+ - Add indexes in schema definition
292
+ - Use \`returning()\` after insert/update when you need the result
293
+ - Use relational queries (\`db.query\`) for nested data