@malamute/ai-rules 1.0.0 → 1.2.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 (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,335 @@
1
+ ---
2
+ paths:
3
+ - "**/*.pipe.ts"
4
+ - "**/*.directive.ts"
5
+ - "**/pipes/**"
6
+ - "**/directives/**"
7
+ ---
8
+
9
+ # Angular Pipes & Directives
10
+
11
+ ## Custom Pipes
12
+
13
+ ### Pure Pipe (default, memoized)
14
+
15
+ ```typescript
16
+ // pipes/time-ago.pipe.ts
17
+ import { Pipe, PipeTransform } from '@angular/core';
18
+
19
+ @Pipe({
20
+ name: 'timeAgo',
21
+ })
22
+ export class TimeAgoPipe implements PipeTransform {
23
+ public transform(value: Date | string | number): string {
24
+ const date = new Date(value);
25
+ const now = new Date();
26
+ const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
27
+
28
+ if (seconds < 60) return 'just now';
29
+ if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`;
30
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
31
+ if (seconds < 2592000) return `${Math.floor(seconds / 86400)} days ago`;
32
+
33
+ return date.toLocaleDateString();
34
+ }
35
+ }
36
+
37
+ // Usage: {{ createdAt | timeAgo }}
38
+ ```
39
+
40
+ ### Pipe with Parameters
41
+
42
+ ```typescript
43
+ // pipes/truncate.pipe.ts
44
+ @Pipe({
45
+ name: 'truncate',
46
+ })
47
+ export class TruncatePipe implements PipeTransform {
48
+ public transform(
49
+ value: string,
50
+ limit: number = 100,
51
+ trail: string = '...'
52
+ ): string {
53
+ if (!value || value.length <= limit) {
54
+ return value;
55
+ }
56
+ return value.substring(0, limit).trim() + trail;
57
+ }
58
+ }
59
+
60
+ // Usage: {{ description | truncate:50:'…' }}
61
+ ```
62
+
63
+ ### Filter Pipe
64
+
65
+ ```typescript
66
+ // pipes/filter.pipe.ts
67
+ @Pipe({
68
+ name: 'filter',
69
+ })
70
+ export class FilterPipe implements PipeTransform {
71
+ public transform<T>(
72
+ items: T[],
73
+ field: keyof T,
74
+ value: unknown
75
+ ): T[] {
76
+ if (!items || !field || value === undefined) {
77
+ return items;
78
+ }
79
+ return items.filter(item => item[field] === value);
80
+ }
81
+ }
82
+
83
+ // Usage: @for (user of users() | filter:'status':'active'; track user.id)
84
+ ```
85
+
86
+ ### Safe HTML Pipe
87
+
88
+ ```typescript
89
+ // pipes/safe-html.pipe.ts
90
+ import { Pipe, PipeTransform, inject } from '@angular/core';
91
+ import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
92
+
93
+ @Pipe({
94
+ name: 'safeHtml',
95
+ })
96
+ export class SafeHtmlPipe implements PipeTransform {
97
+ private readonly sanitizer = inject(DomSanitizer);
98
+
99
+ public transform(value: string): SafeHtml {
100
+ return this.sanitizer.bypassSecurityTrustHtml(value);
101
+ }
102
+ }
103
+
104
+ // Usage: <div [innerHTML]="content | safeHtml"></div>
105
+ // ⚠️ Only use with trusted content!
106
+ ```
107
+
108
+ ## Custom Directives
109
+
110
+ ### Attribute Directive
111
+
112
+ ```typescript
113
+ // directives/highlight.directive.ts
114
+ import { Directive, ElementRef, HostListener, input, inject } from '@angular/core';
115
+
116
+ @Directive({
117
+ selector: '[appHighlight]',
118
+ })
119
+ export class HighlightDirective {
120
+ private readonly el = inject(ElementRef);
121
+
122
+ public readonly appHighlight = input<string>('#ffff00');
123
+ public readonly defaultColor = input<string>('transparent');
124
+
125
+ @HostListener('mouseenter')
126
+ public onMouseEnter(): void {
127
+ this.highlight(this.appHighlight() || '#ffff00');
128
+ }
129
+
130
+ @HostListener('mouseleave')
131
+ public onMouseLeave(): void {
132
+ this.highlight(this.defaultColor());
133
+ }
134
+
135
+ private highlight(color: string): void {
136
+ this.el.nativeElement.style.backgroundColor = color;
137
+ }
138
+ }
139
+
140
+ // Usage: <p appHighlight="#e0e0e0">Hover me</p>
141
+ ```
142
+
143
+ ### Click Outside Directive
144
+
145
+ ```typescript
146
+ // directives/click-outside.directive.ts
147
+ import { Directive, ElementRef, output, inject } from '@angular/core';
148
+ import { fromEvent } from 'rxjs';
149
+ import { filter } from 'rxjs/operators';
150
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
151
+
152
+ @Directive({
153
+ selector: '[appClickOutside]',
154
+ })
155
+ export class ClickOutsideDirective {
156
+ private readonly el = inject(ElementRef);
157
+
158
+ public readonly appClickOutside = output<void>();
159
+
160
+ constructor() {
161
+ fromEvent<MouseEvent>(document, 'click')
162
+ .pipe(
163
+ filter(event => !this.el.nativeElement.contains(event.target)),
164
+ takeUntilDestroyed(),
165
+ )
166
+ .subscribe(() => this.appClickOutside.emit());
167
+ }
168
+ }
169
+
170
+ // Usage: <div appClickOutside (appClickOutside)="closeDropdown()">
171
+ ```
172
+
173
+ ### Auto Focus Directive
174
+
175
+ ```typescript
176
+ // directives/auto-focus.directive.ts
177
+ import { Directive, ElementRef, AfterViewInit, input, inject } from '@angular/core';
178
+
179
+ @Directive({
180
+ selector: '[appAutoFocus]',
181
+ })
182
+ export class AutoFocusDirective implements AfterViewInit {
183
+ private readonly el = inject(ElementRef);
184
+
185
+ public readonly appAutoFocus = input<boolean>(true);
186
+
187
+ public ngAfterViewInit(): void {
188
+ if (this.appAutoFocus()) {
189
+ setTimeout(() => this.el.nativeElement.focus(), 0);
190
+ }
191
+ }
192
+ }
193
+
194
+ // Usage: <input appAutoFocus />
195
+ ```
196
+
197
+ ### Structural Directive
198
+
199
+ ```typescript
200
+ // directives/permission.directive.ts
201
+ import { Directive, TemplateRef, ViewContainerRef, input, effect, inject } from '@angular/core';
202
+ import { AuthService } from '../services/auth.service';
203
+
204
+ @Directive({
205
+ selector: '[appHasPermission]',
206
+ })
207
+ export class HasPermissionDirective {
208
+ private readonly templateRef = inject(TemplateRef<unknown>);
209
+ private readonly viewContainer = inject(ViewContainerRef);
210
+ private readonly authService = inject(AuthService);
211
+
212
+ public readonly appHasPermission = input.required<string | string[]>();
213
+
214
+ private hasView = false;
215
+
216
+ constructor() {
217
+ effect(() => {
218
+ const permissions = this.appHasPermission();
219
+ const permissionArray = Array.isArray(permissions) ? permissions : [permissions];
220
+ const hasPermission = this.authService.hasAnyPermission(permissionArray);
221
+
222
+ if (hasPermission && !this.hasView) {
223
+ this.viewContainer.createEmbeddedView(this.templateRef);
224
+ this.hasView = true;
225
+ } else if (!hasPermission && this.hasView) {
226
+ this.viewContainer.clear();
227
+ this.hasView = false;
228
+ }
229
+ });
230
+ }
231
+ }
232
+
233
+ // Usage: <button *appHasPermission="'users.create'">Create User</button>
234
+ ```
235
+
236
+ ### Debounce Input Directive
237
+
238
+ ```typescript
239
+ // directives/debounce-input.directive.ts
240
+ import { Directive, ElementRef, output, input, inject, OnInit, DestroyRef } from '@angular/core';
241
+ import { fromEvent, debounceTime, distinctUntilChanged, map } from 'rxjs';
242
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
243
+
244
+ @Directive({
245
+ selector: 'input[appDebounce]',
246
+ })
247
+ export class DebounceInputDirective implements OnInit {
248
+ private readonly el = inject(ElementRef<HTMLInputElement>);
249
+ private readonly destroyRef = inject(DestroyRef);
250
+
251
+ public readonly appDebounce = input<number>(300);
252
+ public readonly debounceValue = output<string>();
253
+
254
+ public ngOnInit(): void {
255
+ fromEvent<Event>(this.el.nativeElement, 'input')
256
+ .pipe(
257
+ map(event => (event.target as HTMLInputElement).value),
258
+ debounceTime(this.appDebounce()),
259
+ distinctUntilChanged(),
260
+ takeUntilDestroyed(this.destroyRef),
261
+ )
262
+ .subscribe(value => this.debounceValue.emit(value));
263
+ }
264
+ }
265
+
266
+ // Usage: <input appDebounce [appDebounce]="500" (debounceValue)="onSearch($event)" />
267
+ ```
268
+
269
+ ## Composition
270
+
271
+ ```typescript
272
+ // components/user-list.component.ts
273
+ @Component({
274
+ selector: 'app-user-list',
275
+ imports: [
276
+ TimeAgoPipe,
277
+ TruncatePipe,
278
+ FilterPipe,
279
+ HighlightDirective,
280
+ HasPermissionDirective,
281
+ ],
282
+ template: `
283
+ @for (user of users() | filter:'status':'active'; track user.id) {
284
+ <div appHighlight="#f0f0f0">
285
+ <h3>{{ user.name }}</h3>
286
+ <p>{{ user.bio | truncate:100 }}</p>
287
+ <span>{{ user.createdAt | timeAgo }}</span>
288
+
289
+ <button *appHasPermission="'users.delete'">Delete</button>
290
+ </div>
291
+ }
292
+ `,
293
+ })
294
+ export class UserListComponent {
295
+ protected readonly users = input.required<User[]>();
296
+ }
297
+ ```
298
+
299
+ ## Anti-patterns
300
+
301
+ ```typescript
302
+ // BAD: Impure pipe for filtering (causes performance issues)
303
+ @Pipe({ name: 'filter', pure: false })
304
+
305
+ // GOOD: Use pure pipe (default) + signal for reactivity
306
+ @Pipe({ name: 'filter' })
307
+ // And update source data via signals
308
+
309
+
310
+ // BAD: Direct DOM manipulation
311
+ this.el.nativeElement.innerHTML = '<b>text</b>';
312
+
313
+ // GOOD: Use Renderer2 or Angular bindings
314
+ @HostBinding('innerHTML') content = '<b>text</b>';
315
+
316
+
317
+ // BAD: Adding standalone: true (it's the default since Angular 19)
318
+ @Pipe({ name: 'myPipe', standalone: true })
319
+
320
+ // GOOD: Omit standalone (defaults to true)
321
+ @Pipe({ name: 'myPipe' })
322
+
323
+
324
+ // BAD: Using @Input() in directives
325
+ @Directive({ selector: '[appHighlight]' })
326
+ export class HighlightDirective {
327
+ @Input() appHighlight: string; // Use input() instead
328
+ }
329
+
330
+ // GOOD: Use signal inputs
331
+ @Directive({ selector: '[appHighlight]' })
332
+ export class HighlightDirective {
333
+ public readonly appHighlight = input<string>();
334
+ }
335
+ ```
@@ -26,6 +26,7 @@
26
26
  ]
27
27
  },
28
28
  "env": {
29
+ "NODE_ENV": "development",
29
30
  "NX_DAEMON": "true"
30
31
  }
31
32
  }
@@ -0,0 +1,362 @@
1
+ ---
2
+ name: ngrx-slice
3
+ description: Generate a complete NgRx store slice with actions, reducer, effects, selectors, and Entity Adapter
4
+ argument-hint: <domain> [--entity <EntityName>]
5
+ ---
6
+
7
+ # Generate NgRx Store Slice
8
+
9
+ Generate a complete NgRx store slice following best practices with Entity Adapter.
10
+
11
+ ## Syntax
12
+
13
+ ```
14
+ /ngrx-slice <domain> [--entity <EntityName>]
15
+ ```
16
+
17
+ ## Examples
18
+
19
+ ```bash
20
+ /ngrx-slice users
21
+ /ngrx-slice products --entity Product
22
+ /ngrx-slice orders --entity Order
23
+ ```
24
+
25
+ ## Generated Structure
26
+
27
+ ```
28
+ libs/<domain>/data-access/src/lib/+state/
29
+ ├── <domain>.actions.ts # Action groups with createActionGroup
30
+ ├── <domain>.reducer.ts # Reducer with Entity Adapter
31
+ ├── <domain>.effects.ts # Functional effects
32
+ ├── <domain>.selectors.ts # Memoized selectors
33
+ ├── <domain>.state.ts # State interface
34
+ └── <domain>.adapter.ts # Entity Adapter config
35
+ ```
36
+
37
+ ## File Templates
38
+
39
+ ### 1. State Interface (`<domain>.state.ts`)
40
+
41
+ ```typescript
42
+ import { EntityState } from '@ngrx/entity';
43
+
44
+ export interface <Entity> {
45
+ id: string;
46
+ // Add entity properties
47
+ }
48
+
49
+ export interface <Entity>State extends EntityState<<Entity>> {
50
+ selectedId: string | null;
51
+ loading: boolean;
52
+ error: string | null;
53
+ }
54
+ ```
55
+
56
+ ### 2. Entity Adapter (`<domain>.adapter.ts`)
57
+
58
+ ```typescript
59
+ import { createEntityAdapter, EntityAdapter } from '@ngrx/entity';
60
+ import { <Entity>, <Entity>State } from './<domain>.state';
61
+
62
+ export const <entity>Adapter: EntityAdapter<<Entity>> = createEntityAdapter<<Entity>>({
63
+ selectId: (<entity>) => <entity>.id,
64
+ sortComparer: (a, b) => a.name.localeCompare(b.name),
65
+ });
66
+
67
+ export const initial<Entity>State: <Entity>State = <entity>Adapter.getInitialState({
68
+ selectedId: null,
69
+ loading: false,
70
+ error: null,
71
+ });
72
+ ```
73
+
74
+ ### 3. Actions (`<domain>.actions.ts`)
75
+
76
+ ```typescript
77
+ import { createActionGroup, emptyProps, props } from '@ngrx/store';
78
+ import { Update } from '@ngrx/entity';
79
+ import { <Entity> } from './<domain>.state';
80
+
81
+ export const <Entity>Actions = createActionGroup({
82
+ source: '<Entities>',
83
+ events: {
84
+ // Load
85
+ 'Load <Entities>': emptyProps(),
86
+ 'Load <Entities> Success': props<{ <entities>: <Entity>[] }>(),
87
+ 'Load <Entities> Failure': props<{ error: string }>(),
88
+
89
+ // Load Single
90
+ 'Load <Entity>': props<{ id: string }>(),
91
+ 'Load <Entity> Success': props<{ <entity>: <Entity> }>(),
92
+ 'Load <Entity> Failure': props<{ error: string }>(),
93
+
94
+ // Create
95
+ 'Create <Entity>': props<{ <entity>: Omit<<Entity>, 'id'> }>(),
96
+ 'Create <Entity> Success': props<{ <entity>: <Entity> }>(),
97
+ 'Create <Entity> Failure': props<{ error: string }>(),
98
+
99
+ // Update
100
+ 'Update <Entity>': props<{ update: Update<<Entity>> }>(),
101
+ 'Update <Entity> Success': props<{ <entity>: <Entity> }>(),
102
+ 'Update <Entity> Failure': props<{ error: string }>(),
103
+
104
+ // Delete
105
+ 'Delete <Entity>': props<{ id: string }>(),
106
+ 'Delete <Entity> Success': props<{ id: string }>(),
107
+ 'Delete <Entity> Failure': props<{ error: string }>(),
108
+
109
+ // Selection
110
+ 'Select <Entity>': props<{ id: string }>(),
111
+ 'Clear Selection': emptyProps(),
112
+ },
113
+ });
114
+ ```
115
+
116
+ ### 4. Reducer (`<domain>.reducer.ts`)
117
+
118
+ ```typescript
119
+ import { createReducer, on } from '@ngrx/store';
120
+ import { <Entity>Actions } from './<domain>.actions';
121
+ import { <entity>Adapter, initial<Entity>State } from './<domain>.adapter';
122
+
123
+ export const <entity>Reducer = createReducer(
124
+ initial<Entity>State,
125
+
126
+ // Load All
127
+ on(<Entity>Actions.load<Entities>, (state) => ({
128
+ ...state,
129
+ loading: true,
130
+ error: null,
131
+ })),
132
+ on(<Entity>Actions.load<Entities>Success, (state, { <entities> }) =>
133
+ <entity>Adapter.setAll(<entities>, { ...state, loading: false })
134
+ ),
135
+ on(<Entity>Actions.load<Entities>Failure, (state, { error }) => ({
136
+ ...state,
137
+ loading: false,
138
+ error,
139
+ })),
140
+
141
+ // Load Single
142
+ on(<Entity>Actions.load<Entity>Success, (state, { <entity> }) =>
143
+ <entity>Adapter.upsertOne(<entity>, state)
144
+ ),
145
+
146
+ // Create
147
+ on(<Entity>Actions.create<Entity>Success, (state, { <entity> }) =>
148
+ <entity>Adapter.addOne(<entity>, state)
149
+ ),
150
+
151
+ // Update
152
+ on(<Entity>Actions.update<Entity>Success, (state, { <entity> }) =>
153
+ <entity>Adapter.updateOne({ id: <entity>.id, changes: <entity> }, state)
154
+ ),
155
+
156
+ // Delete
157
+ on(<Entity>Actions.delete<Entity>Success, (state, { id }) =>
158
+ <entity>Adapter.removeOne(id, state)
159
+ ),
160
+
161
+ // Selection
162
+ on(<Entity>Actions.select<Entity>, (state, { id }) => ({
163
+ ...state,
164
+ selectedId: id,
165
+ })),
166
+ on(<Entity>Actions.clearSelection, (state) => ({
167
+ ...state,
168
+ selectedId: null,
169
+ })),
170
+ );
171
+ ```
172
+
173
+ ### 5. Selectors (`<domain>.selectors.ts`)
174
+
175
+ ```typescript
176
+ import { createFeatureSelector, createSelector } from '@ngrx/store';
177
+ import { <Entity>State } from './<domain>.state';
178
+ import { <entity>Adapter } from './<domain>.adapter';
179
+
180
+ export const select<Entity>State = createFeatureSelector<<Entity>State>('<entities>');
181
+
182
+ const { selectAll, selectEntities, selectIds, selectTotal } =
183
+ <entity>Adapter.getSelectors();
184
+
185
+ export const selectAll<Entities> = createSelector(select<Entity>State, selectAll);
186
+
187
+ export const select<Entity>Entities = createSelector(select<Entity>State, selectEntities);
188
+
189
+ export const select<Entity>Ids = createSelector(select<Entity>State, selectIds);
190
+
191
+ export const select<Entity>Total = createSelector(select<Entity>State, selectTotal);
192
+
193
+ export const select<Entities>Loading = createSelector(
194
+ select<Entity>State,
195
+ (state) => state.loading
196
+ );
197
+
198
+ export const select<Entities>Error = createSelector(
199
+ select<Entity>State,
200
+ (state) => state.error
201
+ );
202
+
203
+ export const selectSelected<Entity>Id = createSelector(
204
+ select<Entity>State,
205
+ (state) => state.selectedId
206
+ );
207
+
208
+ export const selectSelected<Entity> = createSelector(
209
+ select<Entity>Entities,
210
+ selectSelected<Entity>Id,
211
+ (entities, selectedId) => (selectedId ? entities[selectedId] : null)
212
+ );
213
+ ```
214
+
215
+ ### 6. Effects (`<domain>.effects.ts`)
216
+
217
+ ```typescript
218
+ import { inject } from '@angular/core';
219
+ import { Actions, createEffect, ofType } from '@ngrx/effects';
220
+ import { catchError, exhaustMap, map, of } from 'rxjs';
221
+ import { <Entity>Actions } from './<domain>.actions';
222
+ import { <Entity>Service } from '../services/<domain>.service';
223
+
224
+ export const load<Entities>$ = createEffect(
225
+ (
226
+ actions$ = inject(Actions),
227
+ <entity>Service = inject(<Entity>Service)
228
+ ) =>
229
+ actions$.pipe(
230
+ ofType(<Entity>Actions.load<Entities>),
231
+ exhaustMap(() =>
232
+ <entity>Service.getAll().pipe(
233
+ map((<entities>) => <Entity>Actions.load<Entities>Success({ <entities> })),
234
+ catchError((error) =>
235
+ of(<Entity>Actions.load<Entities>Failure({ error: error.message }))
236
+ )
237
+ )
238
+ )
239
+ ),
240
+ { functional: true }
241
+ );
242
+
243
+ export const load<Entity>$ = createEffect(
244
+ (
245
+ actions$ = inject(Actions),
246
+ <entity>Service = inject(<Entity>Service)
247
+ ) =>
248
+ actions$.pipe(
249
+ ofType(<Entity>Actions.load<Entity>),
250
+ exhaustMap(({ id }) =>
251
+ <entity>Service.getById(id).pipe(
252
+ map((<entity>) => <Entity>Actions.load<Entity>Success({ <entity> })),
253
+ catchError((error) =>
254
+ of(<Entity>Actions.load<Entity>Failure({ error: error.message }))
255
+ )
256
+ )
257
+ )
258
+ ),
259
+ { functional: true }
260
+ );
261
+
262
+ export const create<Entity>$ = createEffect(
263
+ (
264
+ actions$ = inject(Actions),
265
+ <entity>Service = inject(<Entity>Service)
266
+ ) =>
267
+ actions$.pipe(
268
+ ofType(<Entity>Actions.create<Entity>),
269
+ exhaustMap(({ <entity> }) =>
270
+ <entity>Service.create(<entity>).pipe(
271
+ map((<entity>) => <Entity>Actions.create<Entity>Success({ <entity> })),
272
+ catchError((error) =>
273
+ of(<Entity>Actions.create<Entity>Failure({ error: error.message }))
274
+ )
275
+ )
276
+ )
277
+ ),
278
+ { functional: true }
279
+ );
280
+
281
+ export const update<Entity>$ = createEffect(
282
+ (
283
+ actions$ = inject(Actions),
284
+ <entity>Service = inject(<Entity>Service)
285
+ ) =>
286
+ actions$.pipe(
287
+ ofType(<Entity>Actions.update<Entity>),
288
+ exhaustMap(({ update }) =>
289
+ <entity>Service.update(update.id as string, update.changes).pipe(
290
+ map((<entity>) => <Entity>Actions.update<Entity>Success({ <entity> })),
291
+ catchError((error) =>
292
+ of(<Entity>Actions.update<Entity>Failure({ error: error.message }))
293
+ )
294
+ )
295
+ )
296
+ ),
297
+ { functional: true }
298
+ );
299
+
300
+ export const delete<Entity>$ = createEffect(
301
+ (
302
+ actions$ = inject(Actions),
303
+ <entity>Service = inject(<Entity>Service)
304
+ ) =>
305
+ actions$.pipe(
306
+ ofType(<Entity>Actions.delete<Entity>),
307
+ exhaustMap(({ id }) =>
308
+ <entity>Service.delete(id).pipe(
309
+ map(() => <Entity>Actions.delete<Entity>Success({ id })),
310
+ catchError((error) =>
311
+ of(<Entity>Actions.delete<Entity>Failure({ error: error.message }))
312
+ )
313
+ )
314
+ )
315
+ ),
316
+ { functional: true }
317
+ );
318
+ ```
319
+
320
+ ## Execution Steps
321
+
322
+ 1. **Parse Arguments**
323
+ - Extract domain name (e.g., "users")
324
+ - Extract entity name (e.g., "User") or derive from domain
325
+
326
+ 2. **Create Files**
327
+ - Generate all 6 files with proper naming
328
+ - Replace placeholders with actual names
329
+
330
+ 3. **Update Public API**
331
+ - Export all from `index.ts`
332
+
333
+ 4. **Provide Registration**
334
+ - Show how to register in `app.config.ts`
335
+
336
+ ## Output
337
+
338
+ ```typescript
339
+ // app.config.ts
340
+ import { provideState } from '@ngrx/store';
341
+ import { provideEffects } from '@ngrx/effects';
342
+ import { <entity>Reducer } from '@app/<domain>/data-access';
343
+ import * as <entity>Effects from '@app/<domain>/data-access';
344
+
345
+ export const appConfig: ApplicationConfig = {
346
+ providers: [
347
+ provideStore(),
348
+ provideState('<entities>', <entity>Reducer),
349
+ provideEffects(<entity>Effects),
350
+ ],
351
+ };
352
+ ```
353
+
354
+ ## Placeholders
355
+
356
+ | Placeholder | Example (users) |
357
+ |-------------|-----------------|
358
+ | `<domain>` | users |
359
+ | `<entity>` | user |
360
+ | `<Entity>` | User |
361
+ | `<entities>` | users |
362
+ | `<Entities>` | Users |