@malamute/ai-rules 1.4.0 → 1.5.1

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.
@@ -0,0 +1,95 @@
1
+ ---
2
+ description: "AdonisJS 6+ project conventions and architecture"
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # AdonisJS Project Guidelines
7
+
8
+ ## Stack
9
+
10
+ - AdonisJS 6+
11
+ - TypeScript strict mode
12
+ - Node.js 20+
13
+ - Japa (test framework)
14
+ - Lucid ORM
15
+
16
+ ## Architecture
17
+
18
+ ```
19
+ ├── app/
20
+ │ ├── controllers/
21
+ │ ├── models/
22
+ │ ├── services/
23
+ │ ├── validators/
24
+ │ ├── middleware/
25
+ │ ├── exceptions/
26
+ │ └── mails/
27
+ ├── config/
28
+ ├── database/
29
+ │ ├── migrations/
30
+ │ ├── seeders/
31
+ │ └── factories/
32
+ ├── resources/
33
+ │ └── views/
34
+ ├── start/
35
+ │ ├── routes.ts
36
+ │ ├── kernel.ts
37
+ │ └── events.ts
38
+ ├── tests/
39
+ └── adonisrc.ts
40
+ ```
41
+
42
+ ## Request Lifecycle
43
+
44
+ ```
45
+ Request → Global Middleware → Route Middleware → Controller → Response
46
+ ```
47
+
48
+ ## Core Principles
49
+
50
+ ### Layer Responsibilities
51
+
52
+ | Layer | Responsibility |
53
+ |-------|---------------|
54
+ | Controller | HTTP handling, delegates to services |
55
+ | Service | Business logic |
56
+ | Model | Data structure, relationships, hooks |
57
+ | Validator | Input validation with VineJS |
58
+
59
+ ### Naming Conventions
60
+
61
+ - Controllers: `UsersController` (plural)
62
+ - Models: `User` (singular)
63
+ - Validators: `CreateUserValidator`
64
+ - Migrations: `TIMESTAMP_create_users_table`
65
+
66
+ ### Dependency Injection
67
+
68
+ Use the IoC container:
69
+ ```typescript
70
+ @inject()
71
+ export default class UsersController {
72
+ constructor(private userService: UserService) {}
73
+ }
74
+ ```
75
+
76
+ ## Commands
77
+
78
+ ```bash
79
+ node ace serve --watch # Dev server
80
+ node ace build # Production build
81
+ node ace test # Run tests
82
+ node ace make:controller # Generate controller
83
+ node ace make:model # Generate model
84
+ node ace make:validator # Generate validator
85
+ node ace make:service # Generate service
86
+ node ace migration:run # Run migrations
87
+ node ace migration:rollback
88
+ ```
89
+
90
+ ## Code Style
91
+
92
+ - Use `@inject()` decorator for DI
93
+ - Async methods return `Promise<T>`
94
+ - Use `HttpContext` for request/response
95
+ - Validators use VineJS schema
@@ -0,0 +1,167 @@
1
+ ---
2
+ paths:
3
+ - "app/models/**/*.ts"
4
+ - "database/migrations/**/*.ts"
5
+ - "database/seeders/**/*.ts"
6
+ - "database/factories/**/*.ts"
7
+ ---
8
+
9
+ # Lucid ORM
10
+
11
+ ## Models
12
+
13
+ ```typescript
14
+ import { DateTime } from 'luxon'
15
+ import { BaseModel, column, hasMany, belongsTo } from '@adonisjs/lucid/orm'
16
+ import type { HasMany, BelongsTo } from '@adonisjs/lucid/types/relations'
17
+ import Post from '#models/post'
18
+ import Role from '#models/role'
19
+
20
+ export default class User extends BaseModel {
21
+ @column({ isPrimary: true })
22
+ declare id: number
23
+
24
+ @column()
25
+ declare email: string
26
+
27
+ @column({ serializeAs: null })
28
+ declare password: string
29
+
30
+ @column()
31
+ declare roleId: number
32
+
33
+ @column.dateTime({ autoCreate: true })
34
+ declare createdAt: DateTime
35
+
36
+ @column.dateTime({ autoCreate: true, autoUpdate: true })
37
+ declare updatedAt: DateTime
38
+
39
+ // Relationships
40
+ @hasMany(() => Post)
41
+ declare posts: HasMany<typeof Post>
42
+
43
+ @belongsTo(() => Role)
44
+ declare role: BelongsTo<typeof Role>
45
+ }
46
+ ```
47
+
48
+ ## Relationships
49
+
50
+ ```typescript
51
+ // One to Many
52
+ @hasMany(() => Post)
53
+ declare posts: HasMany<typeof Post>
54
+
55
+ // Belongs To
56
+ @belongsTo(() => User)
57
+ declare user: BelongsTo<typeof User>
58
+
59
+ // Many to Many
60
+ @manyToMany(() => Tag, {
61
+ pivotTable: 'post_tags',
62
+ pivotTimestamps: true,
63
+ })
64
+ declare tags: ManyToMany<typeof Tag>
65
+
66
+ // Has One
67
+ @hasOne(() => Profile)
68
+ declare profile: HasOne<typeof Profile>
69
+ ```
70
+
71
+ ## Queries
72
+
73
+ ```typescript
74
+ // Find
75
+ const user = await User.find(1)
76
+ const user = await User.findOrFail(1)
77
+ const user = await User.findBy('email', 'user@example.com')
78
+
79
+ // Query builder
80
+ const users = await User.query()
81
+ .where('isActive', true)
82
+ .whereNotNull('verifiedAt')
83
+ .orderBy('createdAt', 'desc')
84
+ .limit(10)
85
+
86
+ // With relationships
87
+ const user = await User.query()
88
+ .where('id', 1)
89
+ .preload('posts', (query) => {
90
+ query.where('isPublished', true)
91
+ })
92
+ .firstOrFail()
93
+
94
+ // Aggregates
95
+ const count = await User.query().count('* as total')
96
+ ```
97
+
98
+ ## Migrations
99
+
100
+ ```typescript
101
+ import { BaseSchema } from '@adonisjs/lucid/schema'
102
+
103
+ export default class extends BaseSchema {
104
+ protected tableName = 'users'
105
+
106
+ async up() {
107
+ this.schema.createTable(this.tableName, (table) => {
108
+ table.increments('id')
109
+ table.string('email').notNullable().unique()
110
+ table.string('password').notNullable()
111
+ table.integer('role_id').unsigned().references('roles.id').onDelete('CASCADE')
112
+ table.timestamp('created_at')
113
+ table.timestamp('updated_at')
114
+ })
115
+ }
116
+
117
+ async down() {
118
+ this.schema.dropTable(this.tableName)
119
+ }
120
+ }
121
+ ```
122
+
123
+ ## Factories
124
+
125
+ ```typescript
126
+ import Factory from '@adonisjs/lucid/factories'
127
+ import User from '#models/user'
128
+
129
+ export const UserFactory = Factory.define(User, ({ faker }) => ({
130
+ email: faker.internet.email(),
131
+ password: faker.internet.password(),
132
+ }))
133
+ .relation('posts', () => PostFactory)
134
+ .build()
135
+
136
+ // Usage
137
+ const user = await UserFactory.create()
138
+ const users = await UserFactory.createMany(5)
139
+ const userWithPosts = await UserFactory.with('posts', 3).create()
140
+ ```
141
+
142
+ ## Hooks
143
+
144
+ ```typescript
145
+ import { beforeSave } from '@adonisjs/lucid/orm'
146
+ import hash from '@adonisjs/core/services/hash'
147
+
148
+ export default class User extends BaseModel {
149
+ @beforeSave()
150
+ static async hashPassword(user: User) {
151
+ if (user.$dirty.password) {
152
+ user.password = await hash.make(user.password)
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ ## Transactions
159
+
160
+ ```typescript
161
+ import db from '@adonisjs/lucid/services/db'
162
+
163
+ await db.transaction(async (trx) => {
164
+ const user = await User.create({ email, password }, { client: trx })
165
+ await Profile.create({ userId: user.id }, { client: trx })
166
+ })
167
+ ```
@@ -0,0 +1,140 @@
1
+ ---
2
+ paths:
3
+ - "app/middleware/**/*.ts"
4
+ - "start/kernel.ts"
5
+ ---
6
+
7
+ # AdonisJS Middleware
8
+
9
+ ## Creating Middleware
10
+
11
+ ```typescript
12
+ // app/middleware/admin_middleware.ts
13
+ import type { HttpContext } from '@adonisjs/core/http'
14
+ import type { NextFn } from '@adonisjs/core/types/http'
15
+
16
+ export default class AdminMiddleware {
17
+ async handle({ auth, response }: HttpContext, next: NextFn) {
18
+ if (auth.user?.role !== 'admin') {
19
+ return response.forbidden({ message: 'Admin access required' })
20
+ }
21
+
22
+ await next()
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## Register Middleware
28
+
29
+ ```typescript
30
+ // start/kernel.ts
31
+ import router from '@adonisjs/core/services/router'
32
+
33
+ // Named middleware (use on specific routes)
34
+ router.named({
35
+ auth: () => import('#middleware/auth_middleware'),
36
+ admin: () => import('#middleware/admin_middleware'),
37
+ verified: () => import('#middleware/verified_middleware'),
38
+ })
39
+ ```
40
+
41
+ ## Using Middleware
42
+
43
+ ```typescript
44
+ // start/routes.ts
45
+ import router from '@adonisjs/core/services/router'
46
+ import { middleware } from '#start/kernel'
47
+
48
+ // Single middleware
49
+ router.get('dashboard', [DashboardController, 'index'])
50
+ .use(middleware.auth())
51
+
52
+ // Multiple middleware
53
+ router.get('admin', [AdminController, 'index'])
54
+ .use([middleware.auth(), middleware.admin()])
55
+
56
+ // Group middleware
57
+ router.group(() => {
58
+ router.get('profile', [ProfileController, 'show'])
59
+ router.put('profile', [ProfileController, 'update'])
60
+ }).use(middleware.auth())
61
+ ```
62
+
63
+ ## Middleware with Parameters
64
+
65
+ ```typescript
66
+ // app/middleware/role_middleware.ts
67
+ import type { HttpContext } from '@adonisjs/core/http'
68
+ import type { NextFn } from '@adonisjs/core/types/http'
69
+
70
+ export default class RoleMiddleware {
71
+ async handle({ auth, response }: HttpContext, next: NextFn, options: { roles: string[] }) {
72
+ if (!options.roles.includes(auth.user!.role)) {
73
+ return response.forbidden({ message: 'Insufficient permissions' })
74
+ }
75
+
76
+ await next()
77
+ }
78
+ }
79
+
80
+ // Usage
81
+ router.get('reports', [ReportsController, 'index'])
82
+ .use(middleware.role({ roles: ['admin', 'manager'] }))
83
+ ```
84
+
85
+ ## Request Logging Middleware
86
+
87
+ ```typescript
88
+ import type { HttpContext } from '@adonisjs/core/http'
89
+ import type { NextFn } from '@adonisjs/core/types/http'
90
+ import logger from '@adonisjs/core/services/logger'
91
+
92
+ export default class RequestLoggerMiddleware {
93
+ async handle({ request }: HttpContext, next: NextFn) {
94
+ const start = Date.now()
95
+
96
+ await next()
97
+
98
+ const duration = Date.now() - start
99
+ logger.info(`${request.method()} ${request.url()} - ${duration}ms`)
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Global Middleware
105
+
106
+ ```typescript
107
+ // start/kernel.ts
108
+ import server from '@adonisjs/core/services/server'
109
+
110
+ // Server middleware (runs for every request)
111
+ server.use([
112
+ () => import('#middleware/container_bindings_middleware'),
113
+ () => import('#middleware/force_json_response_middleware'),
114
+ ])
115
+
116
+ // Router middleware (runs for routes only)
117
+ router.use([
118
+ () => import('@adonisjs/core/bodyparser_middleware'),
119
+ () => import('#middleware/request_logger_middleware'),
120
+ ])
121
+ ```
122
+
123
+ ## Terminate Hook
124
+
125
+ ```typescript
126
+ export default class AnalyticsMiddleware {
127
+ async handle(ctx: HttpContext, next: NextFn) {
128
+ await next()
129
+ }
130
+
131
+ // Runs after response is sent
132
+ async terminate(ctx: HttpContext) {
133
+ await Analytics.track({
134
+ path: ctx.request.url(),
135
+ userId: ctx.auth.user?.id,
136
+ duration: ctx.response.getResponseTime(),
137
+ })
138
+ }
139
+ }
140
+ ```
@@ -0,0 +1,154 @@
1
+ ---
2
+ paths:
3
+ - "app/services/**/*.ts"
4
+ ---
5
+
6
+ # AdonisJS Services
7
+
8
+ ## Structure
9
+
10
+ Services contain business logic. Controllers delegate to services.
11
+
12
+ ```typescript
13
+ // app/services/user_service.ts
14
+ import { inject } from '@adonisjs/core'
15
+ import User from '#models/user'
16
+ import hash from '@adonisjs/core/services/hash'
17
+
18
+ @inject()
19
+ export default class UserService {
20
+ async getAll() {
21
+ return User.query().orderBy('createdAt', 'desc')
22
+ }
23
+
24
+ async findOrFail(id: number) {
25
+ return User.findOrFail(id)
26
+ }
27
+
28
+ async create(data: { email: string; password: string; name: string }) {
29
+ return User.create({
30
+ ...data,
31
+ password: await hash.make(data.password),
32
+ })
33
+ }
34
+
35
+ async update(id: number, data: Partial<{ email: string; name: string }>) {
36
+ const user = await User.findOrFail(id)
37
+ user.merge(data)
38
+ await user.save()
39
+ return user
40
+ }
41
+
42
+ async delete(id: number) {
43
+ const user = await User.findOrFail(id)
44
+ await user.delete()
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Service with Dependencies
50
+
51
+ ```typescript
52
+ import { inject } from '@adonisjs/core'
53
+ import mail from '@adonisjs/mail/services/main'
54
+ import User from '#models/user'
55
+ import NotificationService from '#services/notification_service'
56
+
57
+ @inject()
58
+ export default class OrderService {
59
+ constructor(private notificationService: NotificationService) {}
60
+
61
+ async create(userId: number, items: OrderItem[]) {
62
+ const user = await User.findOrFail(userId)
63
+
64
+ const order = await Order.create({
65
+ userId,
66
+ total: this.calculateTotal(items),
67
+ })
68
+
69
+ await order.related('items').createMany(items)
70
+
71
+ // Use injected service
72
+ await this.notificationService.sendOrderConfirmation(user, order)
73
+
74
+ return order
75
+ }
76
+
77
+ private calculateTotal(items: OrderItem[]): number {
78
+ return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Register in Container (optional)
84
+
85
+ For complex setup or interfaces:
86
+
87
+ ```typescript
88
+ // providers/app_provider.ts
89
+ import type { ApplicationService } from '@adonisjs/core/types'
90
+
91
+ export default class AppProvider {
92
+ constructor(protected app: ApplicationService) {}
93
+
94
+ async boot() {
95
+ const { default: PaymentService } = await import('#services/payment_service')
96
+
97
+ this.app.container.singleton('payment', () => {
98
+ return new PaymentService(env.get('STRIPE_KEY'))
99
+ })
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Error Handling
105
+
106
+ ```typescript
107
+ import { Exception } from '@adonisjs/core/exceptions'
108
+
109
+ export class InsufficientFundsException extends Exception {
110
+ static status = 422
111
+ static code = 'E_INSUFFICIENT_FUNDS'
112
+ }
113
+
114
+ export default class WalletService {
115
+ async withdraw(userId: number, amount: number) {
116
+ const wallet = await Wallet.findByOrFail('userId', userId)
117
+
118
+ if (wallet.balance < amount) {
119
+ throw new InsufficientFundsException('Insufficient funds')
120
+ }
121
+
122
+ wallet.balance -= amount
123
+ await wallet.save()
124
+ return wallet
125
+ }
126
+ }
127
+ ```
128
+
129
+ ## Testing Services
130
+
131
+ ```typescript
132
+ import { test } from '@japa/runner'
133
+ import UserService from '#services/user_service'
134
+ import { UserFactory } from '#database/factories/user_factory'
135
+
136
+ test.group('UserService', (group) => {
137
+ group.each.setup(async () => {
138
+ await Database.beginGlobalTransaction()
139
+ return () => Database.rollbackGlobalTransaction()
140
+ })
141
+
142
+ test('creates user with hashed password', async ({ assert }) => {
143
+ const service = new UserService()
144
+
145
+ const user = await service.create({
146
+ email: 'test@example.com',
147
+ password: 'plaintext',
148
+ name: 'Test',
149
+ })
150
+
151
+ assert.notEqual(user.password, 'plaintext')
152
+ })
153
+ })
154
+ ```