@c0x12c/ai-toolkit 1.15.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.
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +12 -0
- package/README.md +439 -0
- package/VERSION +1 -0
- package/agents/design-critic.md +127 -0
- package/agents/idea-killer.md +72 -0
- package/agents/infrastructure-expert.md +49 -0
- package/agents/micronaut-backend-expert.md +45 -0
- package/agents/phase-reviewer.md +150 -0
- package/agents/research-planner.md +70 -0
- package/agents/solution-architect-cto.md +49 -0
- package/agents/sre-architect.md +49 -0
- package/agents/team-coordinator.md +111 -0
- package/bin/cli.js +780 -0
- package/claude-md/00-header.md +39 -0
- package/claude-md/01-core.md +105 -0
- package/claude-md/05-database.md +20 -0
- package/claude-md/11-backend-micronaut.md +19 -0
- package/claude-md/20-frontend-react.md +44 -0
- package/claude-md/25-ux-design.md +56 -0
- package/claude-md/30-infrastructure.md +24 -0
- package/claude-md/30-project-mgmt.md +119 -0
- package/claude-md/40-product.md +39 -0
- package/claude-md/50-ops.md +34 -0
- package/claude-md/60-research.md +27 -0
- package/claude-md/90-footer.md +21 -0
- package/commands/spartan/brainstorm.md +134 -0
- package/commands/spartan/brownfield.md +157 -0
- package/commands/spartan/build.md +435 -0
- package/commands/spartan/careful.md +94 -0
- package/commands/spartan/commit-message.md +112 -0
- package/commands/spartan/content.md +17 -0
- package/commands/spartan/context-save.md +161 -0
- package/commands/spartan/contribute.md +140 -0
- package/commands/spartan/daily.md +42 -0
- package/commands/spartan/debug.md +308 -0
- package/commands/spartan/deep-dive.md +55 -0
- package/commands/spartan/deploy.md +207 -0
- package/commands/spartan/e2e.md +264 -0
- package/commands/spartan/env-setup.md +166 -0
- package/commands/spartan/epic.md +199 -0
- package/commands/spartan/fe-review.md +181 -0
- package/commands/spartan/figma-to-code.md +260 -0
- package/commands/spartan/forensics.md +46 -0
- package/commands/spartan/freeze.md +84 -0
- package/commands/spartan/fundraise.md +53 -0
- package/commands/spartan/gate-review.md +229 -0
- package/commands/spartan/gsd-upgrade.md +376 -0
- package/commands/spartan/guard.md +42 -0
- package/commands/spartan/init-project.md +178 -0
- package/commands/spartan/init-rules.md +298 -0
- package/commands/spartan/interview.md +154 -0
- package/commands/spartan/kickoff.md +73 -0
- package/commands/spartan/kotlin-service.md +109 -0
- package/commands/spartan/lean-canvas.md +222 -0
- package/commands/spartan/lint-rules.md +122 -0
- package/commands/spartan/map-codebase.md +124 -0
- package/commands/spartan/migration.md +82 -0
- package/commands/spartan/next-app.md +317 -0
- package/commands/spartan/next-feature.md +212 -0
- package/commands/spartan/onboard.md +326 -0
- package/commands/spartan/outreach.md +16 -0
- package/commands/spartan/phase.md +142 -0
- package/commands/spartan/pitch.md +18 -0
- package/commands/spartan/plan.md +210 -0
- package/commands/spartan/pr-ready.md +202 -0
- package/commands/spartan/project.md +106 -0
- package/commands/spartan/qa.md +222 -0
- package/commands/spartan/research.md +254 -0
- package/commands/spartan/review.md +132 -0
- package/commands/spartan/scan-rules.md +173 -0
- package/commands/spartan/sessions.md +143 -0
- package/commands/spartan/spec.md +131 -0
- package/commands/spartan/startup.md +257 -0
- package/commands/spartan/team.md +570 -0
- package/commands/spartan/teardown.md +161 -0
- package/commands/spartan/testcontainer.md +97 -0
- package/commands/spartan/tf-cost.md +123 -0
- package/commands/spartan/tf-deploy.md +116 -0
- package/commands/spartan/tf-drift.md +100 -0
- package/commands/spartan/tf-import.md +107 -0
- package/commands/spartan/tf-module.md +121 -0
- package/commands/spartan/tf-plan.md +100 -0
- package/commands/spartan/tf-review.md +106 -0
- package/commands/spartan/tf-scaffold.md +109 -0
- package/commands/spartan/tf-security.md +147 -0
- package/commands/spartan/think.md +221 -0
- package/commands/spartan/unfreeze.md +13 -0
- package/commands/spartan/update.md +134 -0
- package/commands/spartan/ux.md +1233 -0
- package/commands/spartan/validate.md +193 -0
- package/commands/spartan/web-to-prd.md +706 -0
- package/commands/spartan/workstreams.md +109 -0
- package/commands/spartan/write.md +16 -0
- package/commands/spartan.md +386 -0
- package/frameworks/00-framework-comparison-guide.md +317 -0
- package/frameworks/01-lean-canvas.md +196 -0
- package/frameworks/02-design-sprint.md +304 -0
- package/frameworks/03-foundation-sprint.md +337 -0
- package/frameworks/04-business-model-canvas.md +391 -0
- package/frameworks/05-customer-development.md +426 -0
- package/frameworks/06-jobs-to-be-done.md +358 -0
- package/frameworks/07-mom-test.md +392 -0
- package/frameworks/08-value-proposition-canvas.md +488 -0
- package/frameworks/09-javelin-board.md +428 -0
- package/frameworks/10-build-measure-learn.md +467 -0
- package/frameworks/11-mvp-approaches.md +533 -0
- package/frameworks/think-before-build.md +593 -0
- package/lib/assembler.js +197 -0
- package/lib/assembler.test.js +159 -0
- package/lib/detector.js +166 -0
- package/lib/detector.test.js +221 -0
- package/lib/packs.js +16 -0
- package/lib/resolver.js +272 -0
- package/lib/resolver.test.js +298 -0
- package/lib/worktree.sh +104 -0
- package/package.json +50 -0
- package/packs/backend-micronaut.yaml +35 -0
- package/packs/backend-nodejs.yaml +15 -0
- package/packs/backend-python.yaml +15 -0
- package/packs/core.yaml +37 -0
- package/packs/database.yaml +21 -0
- package/packs/frontend-react.yaml +24 -0
- package/packs/infrastructure.yaml +40 -0
- package/packs/ops.yaml +16 -0
- package/packs/packs.compiled.json +371 -0
- package/packs/product.yaml +22 -0
- package/packs/project-mgmt.yaml +24 -0
- package/packs/research.yaml +39 -0
- package/packs/shared-backend.yaml +14 -0
- package/packs/ux-design.yaml +21 -0
- package/rules/backend-micronaut/API_DESIGN.md +313 -0
- package/rules/backend-micronaut/BATCH_PROCESSING.md +92 -0
- package/rules/backend-micronaut/CONTROLLERS.md +388 -0
- package/rules/backend-micronaut/KOTLIN.md +414 -0
- package/rules/backend-micronaut/RETROFIT_PLACEMENT.md +290 -0
- package/rules/backend-micronaut/SERVICES_AND_BEANS.md +325 -0
- package/rules/core/NAMING_CONVENTIONS.md +208 -0
- package/rules/core/SKILL_AUTHORING.md +174 -0
- package/rules/core/TIMEZONE.md +316 -0
- package/rules/database/ORM_AND_REPO.md +289 -0
- package/rules/database/SCHEMA.md +146 -0
- package/rules/database/TRANSACTIONS.md +311 -0
- package/rules/frontend-react/FRONTEND.md +344 -0
- package/rules/infrastructure/MODULES.md +260 -0
- package/rules/infrastructure/NAMING.md +196 -0
- package/rules/infrastructure/PROVIDERS.md +309 -0
- package/rules/infrastructure/SECURITY.md +310 -0
- package/rules/infrastructure/STATE_AND_BACKEND.md +237 -0
- package/rules/infrastructure/STRUCTURE.md +234 -0
- package/rules/infrastructure/VARIABLES.md +285 -0
- package/rules/shared-backend/ARCHITECTURE.md +46 -0
- package/rules/ux-design/DESIGN_PROCESS.md +176 -0
- package/skills/api-endpoint-creator/SKILL.md +455 -0
- package/skills/api-endpoint-creator/error-handling-guide.md +244 -0
- package/skills/api-endpoint-creator/examples.md +522 -0
- package/skills/api-endpoint-creator/testing-patterns.md +302 -0
- package/skills/article-writing/SKILL.md +109 -0
- package/skills/article-writing/examples.md +59 -0
- package/skills/backend-api-design/SKILL.md +84 -0
- package/skills/backend-api-design/code-patterns.md +138 -0
- package/skills/brainstorm/SKILL.md +95 -0
- package/skills/browser-qa/SKILL.md +87 -0
- package/skills/browser-qa/playwright-snippets.md +110 -0
- package/skills/ci-cd-patterns/SKILL.md +108 -0
- package/skills/ci-cd-patterns/workflows.md +149 -0
- package/skills/competitive-teardown/SKILL.md +93 -0
- package/skills/competitive-teardown/example-analysis.md +50 -0
- package/skills/content-engine/SKILL.md +131 -0
- package/skills/content-engine/examples.md +72 -0
- package/skills/database-patterns/SKILL.md +72 -0
- package/skills/database-patterns/code-templates.md +114 -0
- package/skills/database-table-creator/SKILL.md +141 -0
- package/skills/database-table-creator/examples.md +552 -0
- package/skills/database-table-creator/kotlin-templates.md +400 -0
- package/skills/database-table-creator/migration-template.sql +68 -0
- package/skills/database-table-creator/validation-checklist.md +337 -0
- package/skills/deep-research/SKILL.md +80 -0
- package/skills/design-intelligence/SKILL.md +268 -0
- package/skills/design-workflow/SKILL.md +127 -0
- package/skills/design-workflow/checklists.md +45 -0
- package/skills/idea-validation/SKILL.md +129 -0
- package/skills/idea-validation/example-report.md +50 -0
- package/skills/investor-materials/SKILL.md +122 -0
- package/skills/investor-materials/example-outline.md +70 -0
- package/skills/investor-outreach/SKILL.md +112 -0
- package/skills/investor-outreach/examples.md +76 -0
- package/skills/kotlin-best-practices/SKILL.md +58 -0
- package/skills/kotlin-best-practices/code-patterns.md +132 -0
- package/skills/market-research/SKILL.md +99 -0
- package/skills/security-checklist/SKILL.md +65 -0
- package/skills/security-checklist/audit-reference.md +95 -0
- package/skills/service-debugging/SKILL.md +116 -0
- package/skills/service-debugging/common-issues.md +65 -0
- package/skills/startup-pipeline/SKILL.md +152 -0
- package/skills/terraform-best-practices/SKILL.md +244 -0
- package/skills/terraform-module-creator/SKILL.md +284 -0
- package/skills/terraform-review/SKILL.md +222 -0
- package/skills/terraform-security-audit/SKILL.md +280 -0
- package/skills/terraform-service-scaffold/SKILL.md +574 -0
- package/skills/testing-strategies/SKILL.md +116 -0
- package/skills/testing-strategies/examples.md +103 -0
- package/skills/testing-strategies/integration-test-setup.md +71 -0
- package/skills/ui-ux-pro-max/SKILL.md +238 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/python-setup.md +146 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/skills/web-to-prd/SKILL.md +478 -0
- package/templates/build-config.yaml +44 -0
- package/templates/commands-config.yaml +55 -0
- package/templates/competitor-analysis.md +60 -0
- package/templates/content/AGENT_TEMPLATE.md +47 -0
- package/templates/content/COMMAND_TEMPLATE.md +27 -0
- package/templates/content/RULE_TEMPLATE.md +40 -0
- package/templates/content/SKILL_TEMPLATE.md +41 -0
- package/templates/design-config.md +105 -0
- package/templates/design-doc.md +207 -0
- package/templates/epic.md +100 -0
- package/templates/feature-spec.md +181 -0
- package/templates/idea-canvas.md +47 -0
- package/templates/implementation-plan.md +159 -0
- package/templates/prd-template.md +86 -0
- package/templates/preamble.md +89 -0
- package/templates/project-readme.md +35 -0
- package/templates/quality-gates.md +230 -0
- package/templates/spartan-config.yaml +164 -0
- package/templates/user-interview.md +69 -0
- package/templates/validation-checklist.md +108 -0
- package/templates/workflow-backend-micronaut.md +409 -0
- package/templates/workflow-frontend-react.md +233 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Database Schema Rules
|
|
2
|
+
|
|
3
|
+
> Full guide: use /database-patterns or /database-table-creator skill
|
|
4
|
+
|
|
5
|
+
## Core Principles
|
|
6
|
+
1. **Simplicity First** — Keep it simple, avoid over-engineering
|
|
7
|
+
2. **No Foreign Keys** — Handle relationships at application level
|
|
8
|
+
3. **No CASCADE** — Handle deletions in application
|
|
9
|
+
4. **Consistent Data Types** — Use TEXT instead of VARCHAR
|
|
10
|
+
|
|
11
|
+
## Schema Rules
|
|
12
|
+
|
|
13
|
+
### Table Design
|
|
14
|
+
- **No REFERENCES** — Never use foreign key constraints
|
|
15
|
+
- **No ON DELETE CASCADE** — Handle deletions in application
|
|
16
|
+
- **TEXT not VARCHAR** — Always use TEXT for strings
|
|
17
|
+
- **UUID primary keys** — `uuid_generate_v4()`
|
|
18
|
+
- **Soft delete** — Use `deleted_at TIMESTAMPTZ`, never hard delete records
|
|
19
|
+
- Standard columns: `id`, `created_at`, `updated_at`, `deleted_at`
|
|
20
|
+
|
|
21
|
+
### Data Type Standards
|
|
22
|
+
- Strings: TEXT (not VARCHAR)
|
|
23
|
+
- IDs: UUID
|
|
24
|
+
- Dates: TIMESTAMPTZ (all timestamps are UTC, see `TIMEZONE.md`)
|
|
25
|
+
- Booleans: BOOLEAN
|
|
26
|
+
- Flexible data: JSONB
|
|
27
|
+
- IP addresses: INET
|
|
28
|
+
|
|
29
|
+
### Naming Conventions
|
|
30
|
+
- Tables: plural, snake_case (users, user_sessions)
|
|
31
|
+
- Columns: snake_case (user_id, created_at)
|
|
32
|
+
- Indexes: idx_tablename_column (idx_users_email)
|
|
33
|
+
- Unique indexes: idx_tablename_column_unique
|
|
34
|
+
|
|
35
|
+
### Required Features
|
|
36
|
+
- Add indexes for frequently queried columns
|
|
37
|
+
- Add indexes for foreign key columns (even without constraints)
|
|
38
|
+
- Use triggers for updated_at automation
|
|
39
|
+
- Keep migrations simple and focused
|
|
40
|
+
|
|
41
|
+
### What NOT to Include
|
|
42
|
+
- No audit logs unless specifically asked
|
|
43
|
+
- No 2FA unless specifically asked
|
|
44
|
+
- No complex features unless needed
|
|
45
|
+
- No foreign key constraints
|
|
46
|
+
- No cascading deletes
|
|
47
|
+
|
|
48
|
+
### Application-Level Handling
|
|
49
|
+
Since we don't use database constraints, the application must:
|
|
50
|
+
- Validate foreign key relationships
|
|
51
|
+
- Handle cascading deletes
|
|
52
|
+
- Make sure data stays consistent
|
|
53
|
+
- Manage transactions properly
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Kotlin Code Synchronization Rules
|
|
58
|
+
|
|
59
|
+
### When Adding a New Table
|
|
60
|
+
You MUST create:
|
|
61
|
+
1. **SQL migration** in `migrations/sql/`
|
|
62
|
+
2. **Table object** in `module-repository/src/main/kotlin/com/yourcompany/postgresql/table/`
|
|
63
|
+
- Extend `SoftDeleteTable`
|
|
64
|
+
- Use `text()` for strings (not varchar)
|
|
65
|
+
- Don't re-declare `createdAt`, `updatedAt`, `deletedAt` (inherited)
|
|
66
|
+
3. **Entity data class** in `module-repository/src/main/kotlin/com/yourcompany/postgresql/entity/`
|
|
67
|
+
- Implement `Entity<Instant>` interface
|
|
68
|
+
- Match all table columns
|
|
69
|
+
- Use proper Kotlin types (String for TEXT, UUID for ids, Instant for timestamps)
|
|
70
|
+
4. **Constants/Enums** in `module-repository/src/main/kotlin/com/yourcompany/postgresql/constant/` if needed
|
|
71
|
+
- Create enums for status fields
|
|
72
|
+
- Create constants for roles/types
|
|
73
|
+
5. **Repository** in `module-repository/repository/` with soft delete support
|
|
74
|
+
|
|
75
|
+
### When Modifying a Table
|
|
76
|
+
You MUST update:
|
|
77
|
+
1. **Table object** — Add/remove/modify column definitions
|
|
78
|
+
2. **Entity data class** — Add/remove/modify properties to match
|
|
79
|
+
3. **Constants/Enums** — Update if column values changed
|
|
80
|
+
|
|
81
|
+
### When Deleting a Table
|
|
82
|
+
You MUST delete:
|
|
83
|
+
1. **Table object** file
|
|
84
|
+
2. **Entity data class** file
|
|
85
|
+
3. **Related constants/enums** if no longer used
|
|
86
|
+
|
|
87
|
+
### File Naming Conventions
|
|
88
|
+
- Tables: `{TableName}Table.kt` (e.g., `UsersTable.kt`)
|
|
89
|
+
- Entities: `{TableName}Entity.kt` (e.g., `UserEntity.kt`)
|
|
90
|
+
- Constants: `{FieldName}.kt` (e.g., `UserStatus.kt`, `UserRole.kt`)
|
|
91
|
+
|
|
92
|
+
### Package Structure
|
|
93
|
+
```
|
|
94
|
+
module-repository/src/main/kotlin/
|
|
95
|
+
├── com/yourcompany/postgresql/
|
|
96
|
+
│ ├── table/ # Exposed table definitions
|
|
97
|
+
│ ├── entity/ # Data class entities
|
|
98
|
+
│ └── constant/ # Enums and constants
|
|
99
|
+
└── spartan/exposed/codegen/
|
|
100
|
+
└── Entity.kt # Base Entity interface
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Example Synchronization
|
|
104
|
+
When SQL migration adds:
|
|
105
|
+
```sql
|
|
106
|
+
CREATE TABLE products (
|
|
107
|
+
id UUID PRIMARY KEY,
|
|
108
|
+
name TEXT NOT NULL,
|
|
109
|
+
status TEXT DEFAULT 'active',
|
|
110
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
111
|
+
updated_at TIMESTAMPTZ,
|
|
112
|
+
deleted_at TIMESTAMPTZ
|
|
113
|
+
);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
You MUST create:
|
|
117
|
+
1. `ProductsTable.kt` with all columns
|
|
118
|
+
2. `ProductEntity.kt` implementing Entity<Instant>
|
|
119
|
+
3. `ProductStatus.kt` enum for status values
|
|
120
|
+
4. `ProductRepository.kt` with CRUD operations
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Additional Standards (merged from DATABASE_DESIGN.md)
|
|
125
|
+
|
|
126
|
+
### UUID Generation
|
|
127
|
+
- **Always use `uuid_generate_v4()`** — NOT `gen_random_uuid()`
|
|
128
|
+
- Ensure consistency across all migrations
|
|
129
|
+
|
|
130
|
+
### Trigger Functions
|
|
131
|
+
- **Reuse existing `update_updated_at()` function** from `000-init.sql`
|
|
132
|
+
- Do NOT create duplicate trigger functions in each migration
|
|
133
|
+
|
|
134
|
+
### Index Strategy for Soft Deletes
|
|
135
|
+
- Create **partial indexes** for active record queries:
|
|
136
|
+
```sql
|
|
137
|
+
CREATE INDEX idx_table_active ON table_name(column) WHERE deleted_at IS NULL;
|
|
138
|
+
```
|
|
139
|
+
- Create **separate index** for cleanup queries:
|
|
140
|
+
```sql
|
|
141
|
+
CREATE INDEX idx_table_deleted ON table_name(deleted_at) WHERE deleted_at IS NOT NULL;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Exposed ORM Compatibility
|
|
145
|
+
- Use `TEXT` for flexible data fields (contains JSON) — Exposed reads as String
|
|
146
|
+
- JSONB columns use `text()` in Exposed Table definition, serialize/deserialize in Entity
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# Transaction Rules
|
|
2
|
+
|
|
3
|
+
## CRITICAL: Multi-Table Operations Must Use Transactions
|
|
4
|
+
|
|
5
|
+
### 1. Always Use Transactions for Multi-Table Operations
|
|
6
|
+
**Any operation that modifies data in 2 or more tables MUST be wrapped in a transaction.**
|
|
7
|
+
|
|
8
|
+
#### Why This Is Critical
|
|
9
|
+
- Ensures data consistency and integrity
|
|
10
|
+
- Prevents partial updates if one operation fails
|
|
11
|
+
- Allows rollback of all changes if any error occurs
|
|
12
|
+
- Maintains referential integrity between related tables
|
|
13
|
+
- Prevents orphaned records
|
|
14
|
+
|
|
15
|
+
### 2. Transaction Pattern for Managers
|
|
16
|
+
|
|
17
|
+
**Required pattern for multi-table operations:**
|
|
18
|
+
|
|
19
|
+
```kotlin
|
|
20
|
+
class DefaultSomeManager(
|
|
21
|
+
private val db: DatabaseContext, // MUST have DatabaseContext
|
|
22
|
+
private val repository1: Repository1,
|
|
23
|
+
private val repository2: Repository2
|
|
24
|
+
) : SomeManager {
|
|
25
|
+
|
|
26
|
+
override suspend fun multiTableOperation(
|
|
27
|
+
params: Params
|
|
28
|
+
): Either<ClientException, Result> {
|
|
29
|
+
return transaction(db.primary) {
|
|
30
|
+
try {
|
|
31
|
+
// Operation 1 on table 1
|
|
32
|
+
val result1 = repository1.insert(entity1)
|
|
33
|
+
|
|
34
|
+
// Operation 2 on table 2
|
|
35
|
+
val result2 = repository2.insert(entity2)
|
|
36
|
+
|
|
37
|
+
// Return success
|
|
38
|
+
Result.right()
|
|
39
|
+
} catch (e: Exception) {
|
|
40
|
+
rollback()
|
|
41
|
+
ClientError.SOME_ERROR.asException().left()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Examples of Operations That MUST Use Transactions
|
|
49
|
+
|
|
50
|
+
#### Creating Related Entities
|
|
51
|
+
```kotlin
|
|
52
|
+
// BAD - No transaction
|
|
53
|
+
override suspend fun createProject(request: CreateProjectRequest): Either<ClientException, Project> {
|
|
54
|
+
val project = projectRepository.insert(projectEntity)
|
|
55
|
+
val idea = projectIdeasRepository.insert(ideaEntity) // Could fail, leaving orphaned project
|
|
56
|
+
return toProject(project, idea).right()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// GOOD - With transaction
|
|
60
|
+
override suspend fun createProject(request: CreateProjectRequest): Either<ClientException, Project> {
|
|
61
|
+
return transaction(db.primary) {
|
|
62
|
+
try {
|
|
63
|
+
val project = projectRepository.insert(projectEntity)
|
|
64
|
+
val idea = projectIdeasRepository.insert(ideaEntity)
|
|
65
|
+
toProject(project, idea).right()
|
|
66
|
+
} catch (e: Exception) {
|
|
67
|
+
rollback()
|
|
68
|
+
ClientError.USER_NOT_FOUND.asException().left()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Updating Related Entities
|
|
75
|
+
```kotlin
|
|
76
|
+
// BAD - No transaction
|
|
77
|
+
override suspend fun submitIdea(projectId: UUID, request: SubmitIdeaRequest): Either<ClientException, Project> {
|
|
78
|
+
val idea = projectIdeasRepository.insert(ideaEntity)
|
|
79
|
+
projectRepository.update(projectId, status = ProjectStatus.VALIDATING) // Could fail
|
|
80
|
+
return project.right()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// GOOD - With transaction
|
|
84
|
+
override suspend fun submitIdea(projectId: UUID, request: SubmitIdeaRequest): Either<ClientException, Project> {
|
|
85
|
+
return transaction(db.primary) {
|
|
86
|
+
try {
|
|
87
|
+
val idea = projectIdeasRepository.insert(ideaEntity)
|
|
88
|
+
projectRepository.update(projectId, status = ProjectStatus.VALIDATING)
|
|
89
|
+
project.right()
|
|
90
|
+
} catch (e: Exception) {
|
|
91
|
+
rollback()
|
|
92
|
+
ClientError.USER_NOT_FOUND.asException().left()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Deleting Related Entities
|
|
99
|
+
```kotlin
|
|
100
|
+
// BAD - No transaction
|
|
101
|
+
override suspend fun deleteProject(projectId: UUID): Either<ClientException, Boolean> {
|
|
102
|
+
projectRepository.deleteById(projectId)
|
|
103
|
+
projectIdeasRepository.deleteByProjectId(projectId) // Could fail, leaving orphaned ideas
|
|
104
|
+
validationSessionsRepository.deleteByProjectId(projectId) // Could fail
|
|
105
|
+
return true.right()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// GOOD - With transaction
|
|
109
|
+
override suspend fun deleteProject(projectId: UUID): Either<ClientException, Boolean> {
|
|
110
|
+
return transaction(db.primary) {
|
|
111
|
+
try {
|
|
112
|
+
// Delete in correct order (children first)
|
|
113
|
+
validationSessionsRepository.deleteByProjectId(projectId)
|
|
114
|
+
projectIdeasRepository.deleteByProjectId(projectId)
|
|
115
|
+
projectRepository.deleteById(projectId)
|
|
116
|
+
true.right()
|
|
117
|
+
} catch (e: Exception) {
|
|
118
|
+
rollback()
|
|
119
|
+
ClientError.USER_NOT_FOUND.asException().left()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 4. Manager Constructor Requirements
|
|
126
|
+
|
|
127
|
+
**When a manager needs to perform multi-table operations:**
|
|
128
|
+
|
|
129
|
+
```kotlin
|
|
130
|
+
// Manager Factory must provide DatabaseContext
|
|
131
|
+
@Singleton
|
|
132
|
+
fun provideProjectManager(
|
|
133
|
+
databaseContext: DatabaseContext, // REQUIRED for transactions
|
|
134
|
+
projectRepository: ProjectRepository,
|
|
135
|
+
projectIdeasRepository: ProjectIdeasRepository,
|
|
136
|
+
validationSessionsRepository: ValidationSessionsRepository
|
|
137
|
+
): ProjectManager = DefaultProjectManager(
|
|
138
|
+
db = databaseContext,
|
|
139
|
+
projectRepository = projectRepository,
|
|
140
|
+
projectIdeasRepository = projectIdeasRepository,
|
|
141
|
+
validationSessionsRepository = validationSessionsRepository
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 5. Single Table Operations
|
|
146
|
+
|
|
147
|
+
**Single table operations DON'T need explicit transactions** (repositories handle them internally):
|
|
148
|
+
|
|
149
|
+
```kotlin
|
|
150
|
+
// OK - Single table operation
|
|
151
|
+
override suspend fun getProject(projectId: UUID): Either<ClientException, Project> {
|
|
152
|
+
val project = projectRepository.byId(projectId)
|
|
153
|
+
?: return ClientError.USER_NOT_FOUND.asException().left()
|
|
154
|
+
return project.right()
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 6. Common Scenarios Requiring Transactions
|
|
159
|
+
|
|
160
|
+
1. **Creating parent-child relationships**
|
|
161
|
+
- Project + ProjectIdea
|
|
162
|
+
- User + UserCredentials
|
|
163
|
+
- Order + OrderItems
|
|
164
|
+
|
|
165
|
+
2. **Updating status across tables**
|
|
166
|
+
- Updating project status when idea is submitted
|
|
167
|
+
- Updating user status when subscription changes
|
|
168
|
+
|
|
169
|
+
3. **Cascading deletes**
|
|
170
|
+
- Deleting project and all related data
|
|
171
|
+
- Deleting user and all related records
|
|
172
|
+
|
|
173
|
+
4. **Complex business operations**
|
|
174
|
+
- Processing payments (update multiple records)
|
|
175
|
+
- Starting validation (create session + update project)
|
|
176
|
+
|
|
177
|
+
### 7. Transaction Checklist
|
|
178
|
+
|
|
179
|
+
Before implementing any manager method, ask:
|
|
180
|
+
- [ ] Does this operation touch more than one table?
|
|
181
|
+
- [ ] Could partial failure leave the database inconsistent?
|
|
182
|
+
- [ ] Are there parent-child relationships involved?
|
|
183
|
+
- [ ] Does the operation need to be atomic?
|
|
184
|
+
|
|
185
|
+
If ANY answer is YES → **USE A TRANSACTION**
|
|
186
|
+
|
|
187
|
+
### 8. Error Handling in Transactions
|
|
188
|
+
|
|
189
|
+
```kotlin
|
|
190
|
+
return transaction(db.primary) {
|
|
191
|
+
try {
|
|
192
|
+
// Your operations here
|
|
193
|
+
successResult.right()
|
|
194
|
+
} catch (e: Exception) {
|
|
195
|
+
rollback() // Always rollback on error
|
|
196
|
+
// Log the actual error for debugging
|
|
197
|
+
logger.error("Transaction failed", e)
|
|
198
|
+
// Return appropriate client error
|
|
199
|
+
ClientError.APPROPRIATE_ERROR.asException().left()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 9. Testing Transactions
|
|
205
|
+
|
|
206
|
+
Always test transaction rollback scenarios:
|
|
207
|
+
- Simulate failures in the second operation
|
|
208
|
+
- Verify first operation is rolled back
|
|
209
|
+
- Ensure no partial data remains
|
|
210
|
+
|
|
211
|
+
### Remember:
|
|
212
|
+
**When in doubt, use a transaction. It's better to have an unnecessary transaction than risk data inconsistency.**
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## CRITICAL: Atomic Operations for Balance Updates
|
|
217
|
+
|
|
218
|
+
### 10. Race Condition Prevention with Atomic Operations
|
|
219
|
+
|
|
220
|
+
**Any operation that updates a numeric balance (points, allowance, count) for multiple records MUST use atomic SQL operations.**
|
|
221
|
+
|
|
222
|
+
#### Why This Is Critical
|
|
223
|
+
- Prevents race conditions when updating multiple records in a loop
|
|
224
|
+
- Avoids stale data issues from read-modify-write pattern
|
|
225
|
+
- Ensures accurate calculations even with concurrent requests
|
|
226
|
+
|
|
227
|
+
#### Bad Pattern (Race Condition Risk)
|
|
228
|
+
```kotlin
|
|
229
|
+
// BAD - Fetches data first, then uses stale values in loop
|
|
230
|
+
val receivers = userRepository.findByIds(receiverIds) // Fetched at T=0
|
|
231
|
+
|
|
232
|
+
receivers.forEach { receiver ->
|
|
233
|
+
userRepository.update(
|
|
234
|
+
id = receiver.id,
|
|
235
|
+
pointsBalance = receiver.pointsBalance + points // Uses T=0 value, not current!
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Problem**: If `receiver.pointsBalance` was 100 at fetch time, and another request updated it to 120 before this loop runs, this code will set it to 100 + 10 = 110 instead of 120 + 10 = 130.
|
|
241
|
+
|
|
242
|
+
#### Good Pattern (Atomic SQL Operations)
|
|
243
|
+
```kotlin
|
|
244
|
+
// GOOD - Uses SQL-level increment, always reads current value
|
|
245
|
+
receivers.forEach { receiver ->
|
|
246
|
+
userRepository.incrementPointsBalance(receiver.id, points)
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Implementation in Repository:**
|
|
251
|
+
```kotlin
|
|
252
|
+
import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus
|
|
253
|
+
import org.jetbrains.exposed.sql.SqlExpressionBuilder.minus
|
|
254
|
+
|
|
255
|
+
override fun incrementPointsBalance(id: UUID, amount: Int): UserEntity? {
|
|
256
|
+
return transaction(db.primary) {
|
|
257
|
+
val updated = UsersTable.update({
|
|
258
|
+
(UsersTable.id eq id) and (UsersTable.deletedAt.isNull())
|
|
259
|
+
}) {
|
|
260
|
+
it[pointsBalance] = pointsBalance + amount // SQL: points_balance = points_balance + ?
|
|
261
|
+
it[updatedAt] = Instant.now()
|
|
262
|
+
}
|
|
263
|
+
if (updated > 0) {
|
|
264
|
+
UsersTable.selectAll()
|
|
265
|
+
.where { UsersTable.id eq id }
|
|
266
|
+
.singleOrNull()
|
|
267
|
+
?.let { convert(it) }
|
|
268
|
+
} else null
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
override fun decrementAllowanceBalance(id: UUID, amount: Int): UserEntity? {
|
|
273
|
+
return transaction(db.primary) {
|
|
274
|
+
val updated = UsersTable.update({
|
|
275
|
+
(UsersTable.id eq id) and (UsersTable.deletedAt.isNull())
|
|
276
|
+
}) {
|
|
277
|
+
it[allowanceBalance] = allowanceBalance - amount // SQL: allowance_balance = allowance_balance - ?
|
|
278
|
+
it[updatedAt] = Instant.now()
|
|
279
|
+
}
|
|
280
|
+
if (updated > 0) {
|
|
281
|
+
UsersTable.selectAll()
|
|
282
|
+
.where { UsersTable.id eq id }
|
|
283
|
+
.singleOrNull()
|
|
284
|
+
?.let { convert(it) }
|
|
285
|
+
} else null
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### 11. Common Scenarios Requiring Atomic Operations
|
|
291
|
+
|
|
292
|
+
1. **Recognition points distribution**
|
|
293
|
+
- Incrementing receiver's points balance
|
|
294
|
+
- Decrementing giver's allowance balance
|
|
295
|
+
|
|
296
|
+
2. **Redemption processing**
|
|
297
|
+
- Deducting user's points on redeem
|
|
298
|
+
- Refunding points on reject/cancel
|
|
299
|
+
|
|
300
|
+
3. **Any counter increment/decrement**
|
|
301
|
+
- View counts, like counts, comment counts
|
|
302
|
+
- Stock quantities, inventory levels
|
|
303
|
+
|
|
304
|
+
### 12. Atomic Operations Checklist
|
|
305
|
+
|
|
306
|
+
Before implementing any balance update, ask:
|
|
307
|
+
- [ ] Am I updating a numeric value that could be modified by concurrent requests?
|
|
308
|
+
- [ ] Am I using a value fetched earlier in the same method?
|
|
309
|
+
- [ ] Am I updating multiple records in a loop?
|
|
310
|
+
|
|
311
|
+
If ANY answer is YES → **USE ATOMIC SQL OPERATIONS**
|