@heyai-rules/pilo-masterkit 2.1.0 → 3.1.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/.agent/agents/PILO_MASTER.md +77 -77
- package/.agent/agents/architect.md +211 -211
- package/.agent/agents/backend-specialist.md +263 -263
- package/.agent/agents/build-error-resolver.md +114 -114
- package/.agent/agents/chief-of-staff.md +151 -151
- package/.agent/agents/code-archaeologist.md +106 -106
- package/.agent/agents/code-reviewer.md +237 -237
- package/.agent/agents/cpp-build-resolver.md +90 -90
- package/.agent/agents/cpp-reviewer.md +72 -72
- package/.agent/agents/csharp-reviewer.md +101 -101
- package/.agent/agents/dart-build-resolver.md +201 -201
- package/.agent/agents/database-architect.md +226 -226
- package/.agent/agents/database-reviewer.md +91 -91
- package/.agent/agents/debugger.md +225 -225
- package/.agent/agents/devops-engineer.md +242 -242
- package/.agent/agents/doc-updater.md +107 -107
- package/.agent/agents/docs-lookup.md +68 -68
- package/.agent/agents/documentation-writer.md +104 -104
- package/.agent/agents/e2e-runner.md +107 -107
- package/.agent/agents/explorer-agent.md +73 -73
- package/.agent/agents/flutter-reviewer.md +243 -243
- package/.agent/agents/frontend-specialist.md +593 -593
- package/.agent/agents/game-developer.md +162 -162
- package/.agent/agents/gan-evaluator.md +209 -209
- package/.agent/agents/gan-generator.md +131 -131
- package/.agent/agents/gan-planner.md +99 -99
- package/.agent/agents/go-build-resolver.md +94 -94
- package/.agent/agents/go-reviewer.md +76 -76
- package/.agent/agents/harness-optimizer.md +35 -35
- package/.agent/agents/healthcare-reviewer.md +83 -83
- package/.agent/agents/java-build-resolver.md +153 -153
- package/.agent/agents/java-reviewer.md +92 -92
- package/.agent/agents/kotlin-build-resolver.md +118 -118
- package/.agent/agents/kotlin-reviewer.md +159 -159
- package/.agent/agents/loop-operator.md +36 -36
- package/.agent/agents/mobile-developer.md +377 -377
- package/.agent/agents/opensource-forker.md +198 -198
- package/.agent/agents/opensource-packager.md +249 -249
- package/.agent/agents/opensource-sanitizer.md +188 -188
- package/.agent/agents/orchestrator.md +416 -416
- package/.agent/agents/penetration-tester.md +188 -188
- package/.agent/agents/performance-optimizer.md +446 -446
- package/.agent/agents/personas/athena-agent/agent.json +10 -10
- package/.agent/agents/personas/athena-agent/athena-backend-logic-architecture-profile.md +3 -3
- package/.agent/agents/personas/athena-agent/context-files/agents.md +1 -1
- package/.agent/agents/personas/athena-agent/context-files/identity.md +1 -1
- package/.agent/agents/personas/athena-agent/context-files/soul.md +1 -1
- package/.agent/agents/personas/athena-agent/context-files/user-predefined.md +1 -1
- package/.agent/agents/personas/athena-agent/user-context-files/system/bootstrap.md +1 -1
- package/.agent/agents/personas/athena-agent/user-context-files/system/user.md +1 -1
- package/.agent/agents/personas/da-vinci-agent/agent.json +10 -10
- package/.agent/agents/personas/da-vinci-agent/context-files/agents.md +1 -1
- package/.agent/agents/personas/da-vinci-agent/context-files/identity.md +1 -1
- package/.agent/agents/personas/da-vinci-agent/context-files/soul.md +1 -1
- package/.agent/agents/personas/da-vinci-agent/context-files/user-predefined.md +1 -1
- package/.agent/agents/personas/da-vinci-agent/da-vinci-frontend-ui-ux-design-profile.md +3 -3
- package/.agent/agents/personas/da-vinci-agent/user-context-files/system/bootstrap.md +1 -1
- package/.agent/agents/personas/da-vinci-agent/user-context-files/system/user.md +1 -1
- package/.agent/agents/personas/duong-tang-agent/agent.json +10 -10
- package/.agent/agents/personas/duong-tang-agent/context-files/agents.md +1 -1
- package/.agent/agents/personas/duong-tang-agent/context-files/identity.md +1 -1
- package/.agent/agents/personas/duong-tang-agent/context-files/soul.md +1 -1
- package/.agent/agents/personas/duong-tang-agent/context-files/user-predefined.md +1 -1
- package/.agent/agents/personas/duong-tang-agent/tang-monk-quality-testing-documentation-profile.md +3 -3
- package/.agent/agents/personas/duong-tang-agent/user-context-files/system/bootstrap.md +1 -1
- package/.agent/agents/personas/duong-tang-agent/user-context-files/system/user.md +1 -1
- package/.agent/agents/personas/gia-cat-luong-agent/agent.json +10 -10
- package/.agent/agents/personas/gia-cat-luong-agent/context-files/agents.md +1 -1
- package/.agent/agents/personas/gia-cat-luong-agent/context-files/identity.md +1 -1
- package/.agent/agents/personas/gia-cat-luong-agent/context-files/soul.md +1 -1
- package/.agent/agents/personas/gia-cat-luong-agent/context-files/user-predefined.md +1 -1
- package/.agent/agents/personas/gia-cat-luong-agent/kongming-research-strategy-analysis-profile.md +3 -3
- package/.agent/agents/personas/gia-cat-luong-agent/user-context-files/system/bootstrap.md +1 -1
- package/.agent/agents/personas/gia-cat-luong-agent/user-context-files/system/user.md +1 -1
- package/.agent/agents/personas/mihata-agent/agent.json +10 -10
- package/.agent/agents/personas/mihata-agent/context-files/agents.md +1 -1
- package/.agent/agents/personas/mihata-agent/context-files/identity.md +1 -1
- package/.agent/agents/personas/mihata-agent/context-files/soul.md +1 -1
- package/.agent/agents/personas/mihata-agent/context-files/user-predefined.md +1 -1
- package/.agent/agents/personas/mihata-agent/mihata-multi-agent-orchestration-profile.md +3 -3
- package/.agent/agents/personas/mihata-agent/user-context-files/system/bootstrap.md +1 -1
- package/.agent/agents/personas/mihata-agent/user-context-files/system/user.md +1 -1
- package/.agent/agents/personas/tesla-agent/agent.json +10 -10
- package/.agent/agents/personas/tesla-agent/context-files/agents.md +1 -1
- package/.agent/agents/personas/tesla-agent/context-files/identity.md +1 -1
- package/.agent/agents/personas/tesla-agent/context-files/soul.md +1 -1
- package/.agent/agents/personas/tesla-agent/context-files/user-predefined.md +1 -1
- package/.agent/agents/personas/tesla-agent/tesla-fullstack-system-optimization-profile.md +3 -3
- package/.agent/agents/personas/tesla-agent/user-context-files/system/bootstrap.md +1 -1
- package/.agent/agents/personas/tesla-agent/user-context-files/system/user.md +1 -1
- package/.agent/agents/personas/tu-ma-y-agent/agent.json +10 -10
- package/.agent/agents/personas/tu-ma-y-agent/context-files/agents.md +1 -1
- package/.agent/agents/personas/tu-ma-y-agent/context-files/identity.md +1 -1
- package/.agent/agents/personas/tu-ma-y-agent/context-files/soul.md +1 -1
- package/.agent/agents/personas/tu-ma-y-agent/context-files/user-predefined.md +1 -1
- package/.agent/agents/personas/tu-ma-y-agent/simayi-feasibility-risk-control-profile.md +3 -3
- package/.agent/agents/personas/tu-ma-y-agent/user-context-files/system/bootstrap.md +1 -1
- package/.agent/agents/personas/tu-ma-y-agent/user-context-files/system/user.md +1 -1
- package/.agent/agents/personas/venti-agent/agent.json +10 -10
- package/.agent/agents/personas/venti-agent/context-files/agents.md +1 -1
- package/.agent/agents/personas/venti-agent/context-files/identity.md +1 -1
- package/.agent/agents/personas/venti-agent/context-files/soul.md +1 -1
- package/.agent/agents/personas/venti-agent/context-files/user-predefined.md +1 -1
- package/.agent/agents/personas/venti-agent/user-context-files/system/bootstrap.md +1 -1
- package/.agent/agents/personas/venti-agent/user-context-files/system/user.md +1 -1
- package/.agent/agents/personas/venti-agent/venti-learning-communication-mentoring-profile.md +3 -3
- package/.agent/agents/planner.md +212 -212
- package/.agent/agents/product-manager.md +112 -112
- package/.agent/agents/product-owner.md +95 -95
- package/.agent/agents/project-planner.md +406 -406
- package/.agent/agents/python-reviewer.md +98 -98
- package/.agent/agents/pytorch-build-resolver.md +120 -120
- package/.agent/agents/qa-automation-engineer.md +103 -103
- package/.agent/agents/refactor-cleaner.md +85 -85
- package/.agent/agents/rust-build-resolver.md +148 -148
- package/.agent/agents/rust-reviewer.md +94 -94
- package/.agent/agents/security-auditor.md +170 -170
- package/.agent/agents/security-reviewer.md +108 -108
- package/.agent/agents/seo-specialist.md +111 -111
- package/.agent/agents/tdd-guide.md +91 -91
- package/.agent/agents/test-engineer.md +158 -158
- package/.agent/agents/typescript-reviewer.md +112 -112
- package/.agent/contexts/dev.md +20 -20
- package/.agent/contexts/research.md +26 -26
- package/.agent/contexts/review.md +22 -22
- package/.agent/hooks/hooks.json +395 -395
- package/.agent/hooks/readme.md +222 -222
- package/.agent/mcp-configs/mcp-servers.json +181 -181
- package/.agent/rules/ARCHITECTURAL_BLUEPRINTS.md +62 -62
- package/.agent/rules/CODE_CRAFTSMANSHIP.md +69 -69
- package/.agent/rules/CORE_RULES.md +72 -72
- package/.agent/rules/PROJECT_MAP.md +58 -58
- package/.agent/rules/QUALITY_ASSURANCE.md +54 -54
- package/.agent/rules/SECURITY_ARMOR.md +44 -44
- package/.agent/rules/VERSION_ORCHESTRATION.md +64 -64
- package/.agent/rules/WORKFLOW_ORCHESTRATION.md +55 -55
- package/.agent/rules/common/agents.md +50 -50
- package/.agent/rules/common/code-review.md +124 -124
- package/.agent/rules/common/coding-style.md +48 -48
- package/.agent/rules/common/development-workflow.md +44 -44
- package/.agent/rules/common/git-workflow.md +24 -24
- package/.agent/rules/common/hooks.md +30 -30
- package/.agent/rules/common/patterns.md +31 -31
- package/.agent/rules/common/performance.md +55 -55
- package/.agent/rules/common/security.md +29 -29
- package/.agent/rules/common/testing.md +29 -29
- package/.agent/rules/cpp/coding-style.md +44 -44
- package/.agent/rules/cpp/hooks.md +39 -39
- package/.agent/rules/cpp/patterns.md +51 -51
- package/.agent/rules/cpp/security.md +51 -51
- package/.agent/rules/cpp/testing.md +44 -44
- package/.agent/rules/csharp/coding-style.md +72 -72
- package/.agent/rules/csharp/hooks.md +25 -25
- package/.agent/rules/csharp/patterns.md +50 -50
- package/.agent/rules/csharp/security.md +58 -58
- package/.agent/rules/csharp/testing.md +46 -46
- package/.agent/rules/dart/coding-style.md +159 -159
- package/.agent/rules/dart/hooks.md +66 -66
- package/.agent/rules/dart/patterns.md +261 -261
- package/.agent/rules/dart/security.md +135 -135
- package/.agent/rules/dart/testing.md +215 -215
- package/.agent/rules/golang/coding-style.md +32 -32
- package/.agent/rules/golang/hooks.md +17 -17
- package/.agent/rules/golang/patterns.md +45 -45
- package/.agent/rules/golang/security.md +34 -34
- package/.agent/rules/golang/testing.md +31 -31
- package/.agent/rules/java/coding-style.md +114 -114
- package/.agent/rules/java/hooks.md +18 -18
- package/.agent/rules/java/patterns.md +146 -146
- package/.agent/rules/java/security.md +100 -100
- package/.agent/rules/java/testing.md +131 -131
- package/.agent/rules/kotlin/coding-style.md +86 -86
- package/.agent/rules/kotlin/hooks.md +17 -17
- package/.agent/rules/kotlin/patterns.md +146 -146
- package/.agent/rules/kotlin/security.md +82 -82
- package/.agent/rules/kotlin/testing.md +128 -128
- package/.agent/rules/perl/coding-style.md +46 -46
- package/.agent/rules/perl/hooks.md +22 -22
- package/.agent/rules/perl/patterns.md +76 -76
- package/.agent/rules/perl/security.md +69 -69
- package/.agent/rules/perl/testing.md +54 -54
- package/.agent/rules/php/coding-style.md +40 -40
- package/.agent/rules/php/hooks.md +24 -24
- package/.agent/rules/php/patterns.md +33 -33
- package/.agent/rules/php/security.md +37 -37
- package/.agent/rules/php/testing.md +39 -39
- package/.agent/rules/python/coding-style.md +42 -42
- package/.agent/rules/python/hooks.md +19 -19
- package/.agent/rules/python/patterns.md +39 -39
- package/.agent/rules/python/security.md +30 -30
- package/.agent/rules/python/testing.md +38 -38
- package/.agent/rules/readme.md +111 -111
- package/.agent/rules/rust/coding-style.md +151 -151
- package/.agent/rules/rust/hooks.md +16 -16
- package/.agent/rules/rust/patterns.md +168 -168
- package/.agent/rules/rust/security.md +141 -141
- package/.agent/rules/rust/testing.md +154 -154
- package/.agent/rules/swift/coding-style.md +47 -47
- package/.agent/rules/swift/hooks.md +20 -20
- package/.agent/rules/swift/patterns.md +66 -66
- package/.agent/rules/swift/security.md +33 -33
- package/.agent/rules/swift/testing.md +45 -45
- package/.agent/rules/typescript/coding-style.md +199 -199
- package/.agent/rules/typescript/hooks.md +22 -22
- package/.agent/rules/typescript/patterns.md +52 -52
- package/.agent/rules/typescript/security.md +28 -28
- package/.agent/rules/typescript/testing.md +18 -18
- package/.agent/rules/web/coding-style.md +96 -96
- package/.agent/rules/web/design-quality.md +63 -63
- package/.agent/rules/web/hooks.md +120 -120
- package/.agent/rules/web/patterns.md +79 -79
- package/.agent/rules/web/performance.md +64 -64
- package/.agent/rules/web/security.md +57 -57
- package/.agent/rules/web/testing.md +55 -55
- package/.agent/rules/zh/agents.md +50 -50
- package/.agent/rules/zh/code-review.md +124 -124
- package/.agent/rules/zh/coding-style.md +48 -48
- package/.agent/rules/zh/development-workflow.md +44 -44
- package/.agent/rules/zh/git-workflow.md +24 -24
- package/.agent/rules/zh/hooks.md +30 -30
- package/.agent/rules/zh/patterns.md +31 -31
- package/.agent/rules/zh/performance.md +55 -55
- package/.agent/rules/zh/readme.md +108 -108
- package/.agent/rules/zh/security.md +29 -29
- package/.agent/rules/zh/testing.md +29 -29
- package/.agent/scripts/auto_preview.py +148 -148
- package/.agent/scripts/checklist.py +217 -217
- package/.agent/scripts/session_manager.py +120 -120
- package/.agent/scripts/verify_all.py +327 -327
- package/.agent/skills/agent-eval/SKILL.md +145 -145
- package/.agent/skills/agent-harness-construction/SKILL.md +73 -73
- package/.agent/skills/agent-payment-x402/SKILL.md +178 -178
- package/.agent/skills/agentic-engineering/SKILL.md +63 -63
- package/.agent/skills/ai-first-engineering/SKILL.md +51 -51
- package/.agent/skills/ai-regression-testing/SKILL.md +385 -385
- package/.agent/skills/android-clean-architecture/SKILL.md +339 -339
- package/.agent/skills/api-design/SKILL.md +523 -523
- package/.agent/skills/api-patterns/SKILL.md +81 -81
- package/.agent/skills/api-patterns/api-style.md +42 -42
- package/.agent/skills/api-patterns/auth.md +24 -24
- package/.agent/skills/api-patterns/documentation.md +26 -26
- package/.agent/skills/api-patterns/graphql.md +41 -41
- package/.agent/skills/api-patterns/rate-limiting.md +31 -31
- package/.agent/skills/api-patterns/response.md +37 -37
- package/.agent/skills/api-patterns/rest.md +40 -40
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -211
- package/.agent/skills/api-patterns/security-testing.md +122 -122
- package/.agent/skills/api-patterns/trpc.md +41 -41
- package/.agent/skills/api-patterns/versioning.md +22 -22
- package/.agent/skills/app-builder/SKILL.md +75 -75
- package/.agent/skills/app-builder/agent-coordination.md +71 -71
- package/.agent/skills/app-builder/feature-building.md +53 -53
- package/.agent/skills/app-builder/project-detection.md +34 -34
- package/.agent/skills/app-builder/scaffolding.md +118 -118
- package/.agent/skills/app-builder/tech-stack.md +41 -41
- package/.agent/skills/app-builder/templates/SKILL.md +39 -39
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -76
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -92
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -88
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -88
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -83
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -90
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -90
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -122
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -122
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -169
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -134
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -83
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -119
- package/.agent/skills/architecture/SKILL.md +55 -55
- package/.agent/skills/architecture/context-discovery.md +43 -43
- package/.agent/skills/architecture/examples.md +94 -94
- package/.agent/skills/architecture/pattern-selection.md +68 -68
- package/.agent/skills/architecture/patterns-reference.md +50 -50
- package/.agent/skills/architecture/trade-off-analysis.md +77 -77
- package/.agent/skills/architecture-decision-records/SKILL.md +179 -179
- package/.agent/skills/article-writing/SKILL.md +79 -79
- package/.agent/skills/autonomous-agent-harness/SKILL.md +267 -267
- package/.agent/skills/autonomous-loops/SKILL.md +610 -610
- package/.agent/skills/backend-patterns/SKILL.md +598 -598
- package/.agent/skills/bash-linux/SKILL.md +199 -199
- package/.agent/skills/behavioral-modes/SKILL.md +242 -242
- package/.agent/skills/benchmark/SKILL.md +93 -93
- package/.agent/skills/blueprint/SKILL.md +105 -105
- package/.agent/skills/brainstorming/SKILL.md +163 -163
- package/.agent/skills/brainstorming/dynamic-questioning.md +350 -350
- package/.agent/skills/brand-voice/SKILL.md +97 -97
- package/.agent/skills/brand-voice/references/voice-profile-schema.md +55 -55
- package/.agent/skills/browser-qa/SKILL.md +87 -87
- package/.agent/skills/bun-runtime/SKILL.md +84 -84
- package/.agent/skills/canary-watch/SKILL.md +99 -99
- package/.agent/skills/carrier-relationship-management/SKILL.md +212 -212
- package/.agent/skills/ck/SKILL.md +147 -147
- package/.agent/skills/ck/commands/forget.mjs +44 -44
- package/.agent/skills/ck/commands/info.mjs +24 -24
- package/.agent/skills/ck/commands/init.mjs +143 -143
- package/.agent/skills/ck/commands/list.mjs +40 -40
- package/.agent/skills/ck/commands/migrate.mjs +202 -202
- package/.agent/skills/ck/commands/resume.mjs +36 -36
- package/.agent/skills/ck/commands/save.mjs +210 -210
- package/.agent/skills/ck/commands/shared.mjs +387 -387
- package/.agent/skills/ck/hooks/session-start.mjs +224 -224
- package/.agent/skills/claude-api/SKILL.md +337 -337
- package/.agent/skills/claude-devfleet/SKILL.md +103 -103
- package/.agent/skills/clean-code/SKILL.md +201 -201
- package/.agent/skills/click-path-audit/SKILL.md +244 -244
- package/.agent/skills/clickhouse-io/SKILL.md +439 -439
- package/.agent/skills/code-review-checklist/SKILL.md +109 -109
- package/.agent/skills/codebase-onboarding/SKILL.md +233 -233
- package/.agent/skills/coding-standards/SKILL.md +530 -530
- package/.agent/skills/compose-multiplatform-patterns/SKILL.md +299 -299
- package/.agent/skills/configure-ecc/SKILL.md +367 -367
- package/.agent/skills/connections-optimizer/SKILL.md +189 -189
- package/.agent/skills/content-engine/SKILL.md +131 -131
- package/.agent/skills/content-hash-cache-pattern/SKILL.md +161 -161
- package/.agent/skills/context-budget/SKILL.md +135 -135
- package/.agent/skills/continuous-agent-loop/SKILL.md +45 -45
- package/.agent/skills/continuous-learning/SKILL.md +119 -119
- package/.agent/skills/continuous-learning/config.json +18 -18
- package/.agent/skills/continuous-learning/evaluate-session.sh +69 -69
- package/.agent/skills/continuous-learning-v2/SKILL.md +365 -365
- package/.agent/skills/continuous-learning-v2/agents/observer-loop.sh +271 -271
- package/.agent/skills/continuous-learning-v2/agents/observer.md +198 -198
- package/.agent/skills/continuous-learning-v2/agents/session-guardian.sh +150 -150
- package/.agent/skills/continuous-learning-v2/agents/start-observer.sh +244 -244
- package/.agent/skills/continuous-learning-v2/config.json +8 -8
- package/.agent/skills/continuous-learning-v2/hooks/observe.sh +428 -428
- package/.agent/skills/continuous-learning-v2/scripts/detect-project.sh +228 -228
- package/.agent/skills/continuous-learning-v2/scripts/instinct-cli.py +1426 -1426
- package/.agent/skills/continuous-learning-v2/scripts/test-parse-instinct.py +984 -984
- package/.agent/skills/cost-aware-llm-pipeline/SKILL.md +183 -183
- package/.agent/skills/cpp-coding-standards/SKILL.md +723 -723
- package/.agent/skills/cpp-testing/SKILL.md +324 -324
- package/.agent/skills/crosspost/SKILL.md +111 -111
- package/.agent/skills/csharp-testing/SKILL.md +321 -321
- package/.agent/skills/customer-billing-ops/SKILL.md +140 -140
- package/.agent/skills/customs-trade-compliance/SKILL.md +263 -263
- package/.agent/skills/dart-flutter-patterns/SKILL.md +563 -563
- package/.agent/skills/data-scraper-agent/SKILL.md +764 -764
- package/.agent/skills/database-design/SKILL.md +52 -52
- package/.agent/skills/database-design/database-selection.md +43 -43
- package/.agent/skills/database-design/indexing.md +39 -39
- package/.agent/skills/database-design/migrations.md +48 -48
- package/.agent/skills/database-design/optimization.md +36 -36
- package/.agent/skills/database-design/orm-selection.md +30 -30
- package/.agent/skills/database-design/schema-design.md +56 -56
- package/.agent/skills/database-design/scripts/schema_validator.py +172 -172
- package/.agent/skills/database-migrations/SKILL.md +429 -429
- package/.agent/skills/deep-research/SKILL.md +155 -155
- package/.agent/skills/deployment-patterns/SKILL.md +427 -427
- package/.agent/skills/deployment-procedures/SKILL.md +241 -241
- package/.agent/skills/design-system/SKILL.md +82 -82
- package/.agent/skills/django-patterns/SKILL.md +734 -734
- package/.agent/skills/django-security/SKILL.md +593 -593
- package/.agent/skills/django-tdd/SKILL.md +729 -729
- package/.agent/skills/django-verification/SKILL.md +469 -469
- package/.agent/skills/dmux-workflows/SKILL.md +191 -191
- package/.agent/skills/doc.md +177 -177
- package/.agent/skills/docker-patterns/SKILL.md +364 -364
- package/.agent/skills/documentation-lookup/SKILL.md +90 -90
- package/.agent/skills/documentation-templates/SKILL.md +194 -194
- package/.agent/skills/dotnet-patterns/SKILL.md +321 -321
- package/.agent/skills/e2e-testing/SKILL.md +326 -326
- package/.agent/skills/energy-procurement/SKILL.md +228 -228
- package/.agent/skills/enterprise-agent-ops/SKILL.md +50 -50
- package/.agent/skills/eval-harness/SKILL.md +270 -270
- package/.agent/skills/exa-search/SKILL.md +103 -103
- package/.agent/skills/fal-ai-media/SKILL.md +284 -284
- package/.agent/skills/flutter-dart-code-review/SKILL.md +435 -435
- package/.agent/skills/foundation-models-on-device/SKILL.md +243 -243
- package/.agent/skills/frontend-design/SKILL.md +452 -452
- package/.agent/skills/frontend-design/animation-guide.md +331 -331
- package/.agent/skills/frontend-design/color-system.md +311 -311
- package/.agent/skills/frontend-design/decision-trees.md +418 -418
- package/.agent/skills/frontend-design/motion-graphics.md +306 -306
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -183
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -722
- package/.agent/skills/frontend-design/typography-system.md +345 -345
- package/.agent/skills/frontend-design/ux-psychology.md +1116 -1116
- package/.agent/skills/frontend-design/visual-effects.md +383 -383
- package/.agent/skills/frontend-patterns/SKILL.md +642 -642
- package/.agent/skills/frontend-slides/SKILL.md +184 -184
- package/.agent/skills/frontend-slides/style-presets.md +330 -330
- package/.agent/skills/game-development/2d-games/SKILL.md +119 -119
- package/.agent/skills/game-development/3d-games/SKILL.md +135 -135
- package/.agent/skills/game-development/SKILL.md +167 -167
- package/.agent/skills/game-development/game-art/SKILL.md +185 -185
- package/.agent/skills/game-development/game-audio/SKILL.md +190 -190
- package/.agent/skills/game-development/game-design/SKILL.md +129 -129
- package/.agent/skills/game-development/mobile-games/SKILL.md +108 -108
- package/.agent/skills/game-development/multiplayer/SKILL.md +132 -132
- package/.agent/skills/game-development/pc-games/SKILL.md +144 -144
- package/.agent/skills/game-development/vr-ar/SKILL.md +123 -123
- package/.agent/skills/game-development/web-games/SKILL.md +150 -150
- package/.agent/skills/gan-style-harness/SKILL.md +278 -278
- package/.agent/skills/geo-fundamentals/SKILL.md +156 -156
- package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -289
- package/.agent/skills/git-workflow/SKILL.md +715 -715
- package/.agent/skills/golang-patterns/SKILL.md +674 -674
- package/.agent/skills/golang-testing/SKILL.md +720 -720
- package/.agent/skills/google-workspace-ops/SKILL.md +95 -95
- package/.agent/skills/healthcare-cdss-patterns/SKILL.md +245 -245
- package/.agent/skills/healthcare-emr-patterns/SKILL.md +159 -159
- package/.agent/skills/healthcare-eval-harness/SKILL.md +207 -207
- package/.agent/skills/healthcare-phi-compliance/SKILL.md +145 -145
- package/.agent/skills/hexagonal-architecture/SKILL.md +276 -276
- package/.agent/skills/i18n-localization/SKILL.md +154 -154
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -241
- package/.agent/skills/intelligent-routing/SKILL.md +335 -335
- package/.agent/skills/inventory-demand-planning/SKILL.md +247 -247
- package/.agent/skills/investor-materials/SKILL.md +96 -96
- package/.agent/skills/investor-outreach/SKILL.md +91 -91
- package/.agent/skills/iterative-retrieval/SKILL.md +211 -211
- package/.agent/skills/java-coding-standards/SKILL.md +147 -147
- package/.agent/skills/jira-integration/SKILL.md +293 -293
- package/.agent/skills/jpa-patterns/SKILL.md +151 -151
- package/.agent/skills/kotlin-coroutines-flows/SKILL.md +284 -284
- package/.agent/skills/kotlin-exposed-patterns/SKILL.md +719 -719
- package/.agent/skills/kotlin-ktor-patterns/SKILL.md +689 -689
- package/.agent/skills/kotlin-patterns/SKILL.md +711 -711
- package/.agent/skills/kotlin-testing/SKILL.md +824 -824
- package/.agent/skills/laravel-patterns/SKILL.md +415 -415
- package/.agent/skills/laravel-plugin-discovery/SKILL.md +229 -229
- package/.agent/skills/laravel-security/SKILL.md +285 -285
- package/.agent/skills/laravel-tdd/SKILL.md +283 -283
- package/.agent/skills/laravel-verification/SKILL.md +179 -179
- package/.agent/skills/lead-intelligence/SKILL.md +321 -321
- package/.agent/skills/lead-intelligence/agents/enrichment-agent.md +85 -85
- package/.agent/skills/lead-intelligence/agents/mutual-mapper.md +75 -75
- package/.agent/skills/lead-intelligence/agents/outreach-drafter.md +98 -98
- package/.agent/skills/lead-intelligence/agents/signal-scorer.md +60 -60
- package/.agent/skills/lint-and-validate/SKILL.md +45 -45
- package/.agent/skills/lint-and-validate/scripts/lint_runner.py +184 -184
- package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -173
- package/.agent/skills/liquid-glass-design/SKILL.md +279 -279
- package/.agent/skills/logistics-exception-management/SKILL.md +222 -222
- package/.agent/skills/manim-video/SKILL.md +89 -89
- package/.agent/skills/manim-video/assets/network-graph-scene.py +52 -52
- package/.agent/skills/market-research/SKILL.md +75 -75
- package/.agent/skills/mcp-server-patterns/SKILL.md +67 -67
- package/.agent/skills/mobile-design/SKILL.md +394 -394
- package/.agent/skills/mobile-design/decision-trees.md +516 -516
- package/.agent/skills/mobile-design/mobile-backend.md +491 -491
- package/.agent/skills/mobile-design/mobile-color-system.md +420 -420
- package/.agent/skills/mobile-design/mobile-debugging.md +122 -122
- package/.agent/skills/mobile-design/mobile-design-thinking.md +357 -357
- package/.agent/skills/mobile-design/mobile-navigation.md +458 -458
- package/.agent/skills/mobile-design/mobile-performance.md +767 -767
- package/.agent/skills/mobile-design/mobile-testing.md +356 -356
- package/.agent/skills/mobile-design/mobile-typography.md +433 -433
- package/.agent/skills/mobile-design/platform-android.md +666 -666
- package/.agent/skills/mobile-design/platform-ios.md +561 -561
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -670
- package/.agent/skills/mobile-design/touch-psychology.md +537 -537
- package/.agent/skills/nanoclaw-repl/SKILL.md +33 -33
- package/.agent/skills/nestjs-patterns/SKILL.md +230 -230
- package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +351 -351
- package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +240 -240
- package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +490 -490
- package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +264 -264
- package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -581
- package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +432 -432
- package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +684 -684
- package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +150 -150
- package/.agent/skills/nextjs-react-expert/9-cache-components.md +103 -103
- package/.agent/skills/nextjs-react-expert/SKILL.md +293 -293
- package/.agent/skills/nextjs-react-expert/scripts/convert_rules.py +222 -222
- package/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -252
- package/.agent/skills/nextjs-turbopack/SKILL.md +44 -44
- package/.agent/skills/nodejs-best-practices/SKILL.md +333 -333
- package/.agent/skills/nutrient-document-processing/SKILL.md +167 -167
- package/.agent/skills/nuxt4-patterns/SKILL.md +100 -100
- package/.agent/skills/openclaw-persona-forge/SKILL.md +296 -296
- package/.agent/skills/openclaw-persona-forge/gacha.py +224 -224
- package/.agent/skills/openclaw-persona-forge/gacha.sh +5 -5
- package/.agent/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
- package/.agent/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
- package/.agent/skills/openclaw-persona-forge/references/error-handling.md +53 -53
- package/.agent/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
- package/.agent/skills/openclaw-persona-forge/references/naming-system.md +39 -39
- package/.agent/skills/openclaw-persona-forge/references/output-template.md +166 -166
- package/.agent/skills/opensource-pipeline/SKILL.md +255 -255
- package/.agent/skills/parallel-agents/SKILL.md +175 -175
- package/.agent/skills/performance-profiling/SKILL.md +143 -143
- package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -76
- package/.agent/skills/perl-patterns/SKILL.md +504 -504
- package/.agent/skills/perl-security/SKILL.md +503 -503
- package/.agent/skills/perl-testing/SKILL.md +475 -475
- package/.agent/skills/plan-writing/SKILL.md +152 -152
- package/.agent/skills/plankton-code-quality/SKILL.md +236 -236
- package/.agent/skills/postgres-patterns/SKILL.md +147 -147
- package/.agent/skills/powershell-windows/SKILL.md +167 -167
- package/.agent/skills/product-lens/SKILL.md +85 -85
- package/.agent/skills/production-scheduling/SKILL.md +238 -238
- package/.agent/skills/project-flow-ops/SKILL.md +111 -111
- package/.agent/skills/project-guidelines-example/SKILL.md +349 -349
- package/.agent/skills/prompt-optimizer/SKILL.md +397 -397
- package/.agent/skills/python-patterns/SKILL.md +750 -750
- package/.agent/skills/python-testing/SKILL.md +816 -816
- package/.agent/skills/pytorch-patterns/SKILL.md +396 -396
- package/.agent/skills/quality-nonconformance/SKILL.md +260 -260
- package/.agent/skills/ralphinho-rfc-pipeline/SKILL.md +67 -67
- package/.agent/skills/red-team-tactics/SKILL.md +199 -199
- package/.agent/skills/regex-vs-llm-structured-text/SKILL.md +220 -220
- package/.agent/skills/remotion-video-creation/SKILL.md +43 -43
- package/.agent/skills/remotion-video-creation/rules/3d.md +86 -86
- package/.agent/skills/remotion-video-creation/rules/animations.md +29 -29
- package/.agent/skills/remotion-video-creation/rules/assets/charts-bar-chart.tsx +173 -173
- package/.agent/skills/remotion-video-creation/rules/assets/text-animations-typewriter.tsx +100 -100
- package/.agent/skills/remotion-video-creation/rules/assets/text-animations-word-highlight.tsx +108 -108
- package/.agent/skills/remotion-video-creation/rules/assets.md +78 -78
- package/.agent/skills/remotion-video-creation/rules/audio.md +172 -172
- package/.agent/skills/remotion-video-creation/rules/calculate-metadata.md +104 -104
- package/.agent/skills/remotion-video-creation/rules/can-decode.md +75 -75
- package/.agent/skills/remotion-video-creation/rules/charts.md +58 -58
- package/.agent/skills/remotion-video-creation/rules/compositions.md +146 -146
- package/.agent/skills/remotion-video-creation/rules/display-captions.md +126 -126
- package/.agent/skills/remotion-video-creation/rules/extract-frames.md +229 -229
- package/.agent/skills/remotion-video-creation/rules/fonts.md +152 -152
- package/.agent/skills/remotion-video-creation/rules/get-audio-duration.md +58 -58
- package/.agent/skills/remotion-video-creation/rules/get-video-dimensions.md +68 -68
- package/.agent/skills/remotion-video-creation/rules/get-video-duration.md +58 -58
- package/.agent/skills/remotion-video-creation/rules/gifs.md +138 -138
- package/.agent/skills/remotion-video-creation/rules/images.md +130 -130
- package/.agent/skills/remotion-video-creation/rules/import-srt-captions.md +67 -67
- package/.agent/skills/remotion-video-creation/rules/lottie.md +67 -67
- package/.agent/skills/remotion-video-creation/rules/measuring-dom-nodes.md +34 -34
- package/.agent/skills/remotion-video-creation/rules/measuring-text.md +143 -143
- package/.agent/skills/remotion-video-creation/rules/sequencing.md +106 -106
- package/.agent/skills/remotion-video-creation/rules/tailwind.md +11 -11
- package/.agent/skills/remotion-video-creation/rules/text-animations.md +20 -20
- package/.agent/skills/remotion-video-creation/rules/timing.md +179 -179
- package/.agent/skills/remotion-video-creation/rules/transcribe-captions.md +19 -19
- package/.agent/skills/remotion-video-creation/rules/transitions.md +122 -122
- package/.agent/skills/remotion-video-creation/rules/trimming.md +52 -52
- package/.agent/skills/remotion-video-creation/rules/videos.md +171 -171
- package/.agent/skills/repo-scan/SKILL.md +63 -63
- package/.agent/skills/returns-reverse-logistics/SKILL.md +240 -240
- package/.agent/skills/rules-distill/SKILL.md +264 -264
- package/.agent/skills/rules-distill/scripts/scan-rules.sh +58 -58
- package/.agent/skills/rules-distill/scripts/scan-skills.sh +129 -129
- package/.agent/skills/rust-patterns/SKILL.md +499 -499
- package/.agent/skills/rust-pro/SKILL.md +175 -175
- package/.agent/skills/rust-testing/SKILL.md +500 -500
- package/.agent/skills/safety-guard/SKILL.md +75 -75
- package/.agent/skills/santa-method/SKILL.md +306 -306
- package/.agent/skills/search-first/SKILL.md +161 -161
- package/.agent/skills/security-review/SKILL.md +495 -495
- package/.agent/skills/security-review/cloud-infrastructure-security.md +361 -361
- package/.agent/skills/security-scan/SKILL.md +165 -165
- package/.agent/skills/seo-fundamentals/SKILL.md +129 -129
- package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +219 -219
- package/.agent/skills/server-management/SKILL.md +161 -161
- package/.agent/skills/skill-comply/SKILL.md +58 -58
- package/.agent/skills/skill-comply/fixtures/compliant-trace.jsonl +5 -5
- package/.agent/skills/skill-comply/fixtures/noncompliant-trace.jsonl +3 -3
- package/.agent/skills/skill-comply/fixtures/tdd-spec.yaml +44 -44
- package/.agent/skills/skill-comply/prompts/classifier.md +24 -24
- package/.agent/skills/skill-comply/prompts/scenario-generator.md +62 -62
- package/.agent/skills/skill-comply/prompts/spec-generator.md +42 -42
- package/.agent/skills/skill-comply/pyproject.toml +15 -15
- package/.agent/skills/skill-comply/scripts/classifier.py +85 -85
- package/.agent/skills/skill-comply/scripts/grader.py +122 -122
- package/.agent/skills/skill-comply/scripts/parser.py +107 -107
- package/.agent/skills/skill-comply/scripts/report.py +170 -170
- package/.agent/skills/skill-comply/scripts/run.py +127 -127
- package/.agent/skills/skill-comply/scripts/runner.py +161 -161
- package/.agent/skills/skill-comply/scripts/scenario-generator.py +70 -70
- package/.agent/skills/skill-comply/scripts/spec-generator.py +72 -72
- package/.agent/skills/skill-comply/scripts/utils.py +13 -13
- package/.agent/skills/skill-comply/tests/test-grader.py +137 -137
- package/.agent/skills/skill-comply/tests/test-parser.py +90 -90
- package/.agent/skills/skill-stocktake/SKILL.md +193 -193
- package/.agent/skills/skill-stocktake/scripts/quick-diff.sh +87 -87
- package/.agent/skills/skill-stocktake/scripts/save-results.sh +56 -56
- package/.agent/skills/skill-stocktake/scripts/scan.sh +170 -170
- package/.agent/skills/social-graph-ranker/SKILL.md +154 -154
- package/.agent/skills/springboot-patterns/SKILL.md +314 -314
- package/.agent/skills/springboot-security/SKILL.md +272 -272
- package/.agent/skills/springboot-tdd/SKILL.md +158 -158
- package/.agent/skills/springboot-verification/SKILL.md +231 -231
- package/.agent/skills/strategic-compact/SKILL.md +131 -131
- package/.agent/skills/strategic-compact/suggest-compact.sh +54 -54
- package/.agent/skills/swift-actor-persistence/SKILL.md +143 -143
- package/.agent/skills/swift-concurrency-6-2/SKILL.md +216 -216
- package/.agent/skills/swift-protocol-di-testing/SKILL.md +190 -190
- package/.agent/skills/swiftui-patterns/SKILL.md +259 -259
- package/.agent/skills/systematic-debugging/SKILL.md +109 -109
- package/.agent/skills/tailwind-patterns/SKILL.md +269 -269
- package/.agent/skills/tdd-workflow/SKILL.md +463 -463
- package/.agent/skills/team-builder/SKILL.md +168 -168
- package/.agent/skills/testing-patterns/SKILL.md +178 -178
- package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -219
- package/.agent/skills/token-budget-advisor/SKILL.md +133 -133
- package/.agent/skills/ui-demo/SKILL.md +465 -465
- package/.agent/skills/ui-ux-pro-max/SKILL.md +292 -292
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -26
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -97
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -101
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -31
- package/.agent/skills/ui-ux-pro-max/data/products.csv +96 -96
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -68
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +57 -57
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -253
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -114
- package/.agent/skills/verification-loop/SKILL.md +126 -126
- package/.agent/skills/video-editing/SKILL.md +310 -310
- package/.agent/skills/videodb/SKILL.md +374 -374
- package/.agent/skills/videodb/reference/api-reference.md +550 -550
- package/.agent/skills/videodb/reference/capture-reference.md +407 -407
- package/.agent/skills/videodb/reference/capture.md +101 -101
- package/.agent/skills/videodb/reference/editor.md +443 -443
- package/.agent/skills/videodb/reference/generative.md +331 -331
- package/.agent/skills/videodb/reference/rtstream-reference.md +564 -564
- package/.agent/skills/videodb/reference/rtstream.md +65 -65
- package/.agent/skills/videodb/reference/search.md +230 -230
- package/.agent/skills/videodb/reference/streaming.md +406 -406
- package/.agent/skills/videodb/reference/use-cases.md +118 -118
- package/.agent/skills/videodb/scripts/ws-listener.py +282 -282
- package/.agent/skills/visa-doc-translate/SKILL.md +117 -117
- package/.agent/skills/visa-doc-translate/readme.md +86 -86
- package/.agent/skills/vulnerability-scanner/SKILL.md +276 -276
- package/.agent/skills/vulnerability-scanner/checklists.md +121 -121
- package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +458 -458
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -57
- package/.agent/skills/webapp-testing/SKILL.md +187 -187
- package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -173
- package/.agent/skills/workspace-surface-audit/SKILL.md +125 -125
- package/.agent/skills/x-api/SKILL.md +230 -230
- package/.agent/tasks/lessons.md +40 -40
- package/.agent/tasks/todo.md +33 -33
- package/.agent/tasks/two-track-merge-contract.md +1 -1
- package/.agent/workflows/aside.md +164 -164
- package/.agent/workflows/brainstorm.md +113 -113
- package/.agent/workflows/build-fix.md +62 -62
- package/.agent/workflows/checkpoint.md +74 -74
- package/.agent/workflows/claw.md +23 -23
- package/.agent/workflows/clean-memory.md +34 -34
- package/.agent/workflows/code-review.md +289 -289
- package/.agent/workflows/context-budget.md +23 -23
- package/.agent/workflows/cpp-build.md +173 -173
- package/.agent/workflows/cpp-review.md +132 -132
- package/.agent/workflows/cpp-test.md +251 -251
- package/.agent/workflows/create.md +59 -59
- package/.agent/workflows/debug.md +103 -103
- package/.agent/workflows/deploy.md +176 -176
- package/.agent/workflows/devfleet.md +23 -23
- package/.agent/workflows/docs.md +23 -23
- package/.agent/workflows/e2e.md +268 -268
- package/.agent/workflows/enhance.md +63 -63
- package/.agent/workflows/eval.md +23 -23
- package/.agent/workflows/evolve.md +178 -178
- package/.agent/workflows/flutter-build.md +164 -164
- package/.agent/workflows/flutter-review.md +116 -116
- package/.agent/workflows/flutter-test.md +144 -144
- package/.agent/workflows/gan-build.md +99 -99
- package/.agent/workflows/gan-design.md +35 -35
- package/.agent/workflows/go-build.md +183 -183
- package/.agent/workflows/go-review.md +148 -148
- package/.agent/workflows/go-test.md +268 -268
- package/.agent/workflows/gradle-build.md +70 -70
- package/.agent/workflows/harness-audit.md +73 -73
- package/.agent/workflows/init-docs.md +46 -46
- package/.agent/workflows/instinct-export.md +66 -66
- package/.agent/workflows/instinct-import.md +114 -114
- package/.agent/workflows/instinct-status.md +59 -59
- package/.agent/workflows/jira.md +106 -106
- package/.agent/workflows/kotlin-build.md +174 -174
- package/.agent/workflows/kotlin-review.md +140 -140
- package/.agent/workflows/kotlin-test.md +312 -312
- package/.agent/workflows/learn-eval.md +116 -116
- package/.agent/workflows/learn.md +70 -70
- package/.agent/workflows/loop-start.md +32 -32
- package/.agent/workflows/loop-status.md +24 -24
- package/.agent/workflows/model-route.md +26 -26
- package/.agent/workflows/multi-backend.md +158 -158
- package/.agent/workflows/multi-execute.md +315 -315
- package/.agent/workflows/multi-frontend.md +158 -158
- package/.agent/workflows/multi-plan.md +268 -268
- package/.agent/workflows/multi-workflow.md +191 -191
- package/.agent/workflows/orchestrate.md +135 -135
- package/.agent/workflows/plan.md +117 -117
- package/.agent/workflows/pm2.md +272 -272
- package/.agent/workflows/preview.md +81 -81
- package/.agent/workflows/projects.md +39 -39
- package/.agent/workflows/promote.md +41 -41
- package/.agent/workflows/prompt-optimize.md +23 -23
- package/.agent/workflows/prp-commit.md +112 -112
- package/.agent/workflows/prp-implement.md +385 -385
- package/.agent/workflows/prp-plan.md +502 -502
- package/.agent/workflows/prp-pr.md +184 -184
- package/.agent/workflows/prp-prd.md +447 -447
- package/.agent/workflows/prune.md +31 -31
- package/.agent/workflows/python-review.md +297 -297
- package/.agent/workflows/quality-gate.md +29 -29
- package/.agent/workflows/refactor-clean.md +80 -80
- package/.agent/workflows/resume-session.md +156 -156
- package/.agent/workflows/rules-distill.md +20 -20
- package/.agent/workflows/rust-build.md +187 -187
- package/.agent/workflows/rust-review.md +142 -142
- package/.agent/workflows/rust-test.md +308 -308
- package/.agent/workflows/santa-loop.md +175 -175
- package/.agent/workflows/save-session.md +275 -275
- package/.agent/workflows/sessions.md +333 -333
- package/.agent/workflows/setup-pm.md +80 -80
- package/.agent/workflows/skill-create.md +174 -174
- package/.agent/workflows/skill-health.md +54 -54
- package/.agent/workflows/status.md +86 -86
- package/.agent/workflows/tdd.md +231 -231
- package/.agent/workflows/test-coverage.md +69 -69
- package/.agent/workflows/test.md +144 -144
- package/.agent/workflows/ui-ux-pro-max.md +295 -295
- package/.agent/workflows/update-codemaps.md +72 -72
- package/.agent/workflows/update-docs.md +84 -84
- package/.agent/workflows/verify.md +23 -23
- package/LICENSE +176 -176
- package/README.md +144 -144
- package/package.json +1 -1
- package/scripts/release-check.js +55 -55
- package/src/bin/cli.js +424 -354
- package/src/lib/installer.js +223 -11
|
@@ -1,719 +1,719 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: kotlin-exposed-patterns
|
|
3
|
-
description: JetBrains Exposed ORM patterns including DSL queries, DAO pattern, transactions, HikariCP connection pooling, Flyway migrations, and repository pattern.
|
|
4
|
-
origin: ECC
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Kotlin Exposed Patterns
|
|
8
|
-
|
|
9
|
-
Comprehensive patterns for database access with JetBrains Exposed ORM, including DSL queries, DAO, transactions, and production-ready configuration.
|
|
10
|
-
|
|
11
|
-
## When to Use
|
|
12
|
-
|
|
13
|
-
- Setting up database access with Exposed
|
|
14
|
-
- Writing SQL queries using Exposed DSL or DAO
|
|
15
|
-
- Configuring connection pooling with HikariCP
|
|
16
|
-
- Creating database migrations with Flyway
|
|
17
|
-
- Implementing the repository pattern with Exposed
|
|
18
|
-
- Handling JSON columns and complex queries
|
|
19
|
-
|
|
20
|
-
## How It Works
|
|
21
|
-
|
|
22
|
-
Exposed provides two query styles: DSL for direct SQL-like expressions and DAO for entity lifecycle management. HikariCP manages a pool of reusable database connections configured via `HikariConfig`. Flyway runs versioned SQL migration scripts at startup to keep the schema in sync. All database operations run inside `newSuspendedTransaction` blocks for coroutine safety and atomicity. The repository pattern wraps Exposed queries behind an interface so business logic stays decoupled from the data layer and tests can use an in-memory H2 database.
|
|
23
|
-
|
|
24
|
-
## Examples
|
|
25
|
-
|
|
26
|
-
### DSL Query
|
|
27
|
-
|
|
28
|
-
```kotlin
|
|
29
|
-
suspend fun findUserById(id: UUID): UserRow? =
|
|
30
|
-
newSuspendedTransaction {
|
|
31
|
-
UsersTable.selectAll()
|
|
32
|
-
.where { UsersTable.id eq id }
|
|
33
|
-
.map { it.toUser() }
|
|
34
|
-
.singleOrNull()
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### DAO Entity Usage
|
|
39
|
-
|
|
40
|
-
```kotlin
|
|
41
|
-
suspend fun createUser(request: CreateUserRequest): User =
|
|
42
|
-
newSuspendedTransaction {
|
|
43
|
-
UserEntity.new {
|
|
44
|
-
name = request.name
|
|
45
|
-
email = request.email
|
|
46
|
-
role = request.role
|
|
47
|
-
}.toModel()
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### HikariCP Configuration
|
|
52
|
-
|
|
53
|
-
```kotlin
|
|
54
|
-
val hikariConfig = HikariConfig().apply {
|
|
55
|
-
driverClassName = config.driver
|
|
56
|
-
jdbcUrl = config.url
|
|
57
|
-
username = config.username
|
|
58
|
-
password = config.password
|
|
59
|
-
maximumPoolSize = config.maxPoolSize
|
|
60
|
-
isAutoCommit = false
|
|
61
|
-
transactionIsolation = "TRANSACTION_READ_COMMITTED"
|
|
62
|
-
validate()
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Database Setup
|
|
67
|
-
|
|
68
|
-
### HikariCP Connection Pooling
|
|
69
|
-
|
|
70
|
-
```kotlin
|
|
71
|
-
// DatabaseFactory.kt
|
|
72
|
-
object DatabaseFactory {
|
|
73
|
-
fun create(config: DatabaseConfig): Database {
|
|
74
|
-
val hikariConfig = HikariConfig().apply {
|
|
75
|
-
driverClassName = config.driver
|
|
76
|
-
jdbcUrl = config.url
|
|
77
|
-
username = config.username
|
|
78
|
-
password = config.password
|
|
79
|
-
maximumPoolSize = config.maxPoolSize
|
|
80
|
-
isAutoCommit = false
|
|
81
|
-
transactionIsolation = "TRANSACTION_READ_COMMITTED"
|
|
82
|
-
validate()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return Database.connect(HikariDataSource(hikariConfig))
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
data class DatabaseConfig(
|
|
90
|
-
val url: String,
|
|
91
|
-
val driver: String = "org.postgresql.Driver",
|
|
92
|
-
val username: String = "",
|
|
93
|
-
val password: String = "",
|
|
94
|
-
val maxPoolSize: Int = 10,
|
|
95
|
-
)
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Flyway Migrations
|
|
99
|
-
|
|
100
|
-
```kotlin
|
|
101
|
-
// FlywayMigration.kt
|
|
102
|
-
fun runMigrations(config: DatabaseConfig) {
|
|
103
|
-
Flyway.configure()
|
|
104
|
-
.dataSource(config.url, config.username, config.password)
|
|
105
|
-
.locations("classpath:db/migration")
|
|
106
|
-
.baselineOnMigrate(true)
|
|
107
|
-
.load()
|
|
108
|
-
.migrate()
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Application startup
|
|
112
|
-
fun Application.module() {
|
|
113
|
-
val config = DatabaseConfig(
|
|
114
|
-
url = environment.config.property("database.url").getString(),
|
|
115
|
-
username = environment.config.property("database.username").getString(),
|
|
116
|
-
password = environment.config.property("database.password").getString(),
|
|
117
|
-
)
|
|
118
|
-
runMigrations(config)
|
|
119
|
-
val database = DatabaseFactory.create(config)
|
|
120
|
-
// ...
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Migration Files
|
|
125
|
-
|
|
126
|
-
```sql
|
|
127
|
-
-- src/main/resources/db/migration/V1__create_users.sql
|
|
128
|
-
CREATE TABLE users (
|
|
129
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
130
|
-
name VARCHAR(100) NOT NULL,
|
|
131
|
-
email VARCHAR(255) NOT NULL UNIQUE,
|
|
132
|
-
role VARCHAR(20) NOT NULL DEFAULT 'USER',
|
|
133
|
-
metadata JSONB,
|
|
134
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
135
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
CREATE INDEX idx_users_email ON users(email);
|
|
139
|
-
CREATE INDEX idx_users_role ON users(role);
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## Table Definitions
|
|
143
|
-
|
|
144
|
-
### DSL Style Tables
|
|
145
|
-
|
|
146
|
-
```kotlin
|
|
147
|
-
// tables/UsersTable.kt
|
|
148
|
-
object UsersTable : UUIDTable("users") {
|
|
149
|
-
val name = varchar("name", 100)
|
|
150
|
-
val email = varchar("email", 255).uniqueIndex()
|
|
151
|
-
val role = enumerationByName<Role>("role", 20)
|
|
152
|
-
val metadata = jsonb<UserMetadata>("metadata", Json.Default).nullable()
|
|
153
|
-
val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
|
|
154
|
-
val updatedAt = timestampWithTimeZone("updated_at").defaultExpression(CurrentTimestampWithTimeZone)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
object OrdersTable : UUIDTable("orders") {
|
|
158
|
-
val userId = uuid("user_id").references(UsersTable.id)
|
|
159
|
-
val status = enumerationByName<OrderStatus>("status", 20)
|
|
160
|
-
val totalAmount = long("total_amount")
|
|
161
|
-
val currency = varchar("currency", 3)
|
|
162
|
-
val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
object OrderItemsTable : UUIDTable("order_items") {
|
|
166
|
-
val orderId = uuid("order_id").references(OrdersTable.id, onDelete = ReferenceOption.CASCADE)
|
|
167
|
-
val productId = uuid("product_id")
|
|
168
|
-
val quantity = integer("quantity")
|
|
169
|
-
val unitPrice = long("unit_price")
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Composite Tables
|
|
174
|
-
|
|
175
|
-
```kotlin
|
|
176
|
-
object UserRolesTable : Table("user_roles") {
|
|
177
|
-
val userId = uuid("user_id").references(UsersTable.id, onDelete = ReferenceOption.CASCADE)
|
|
178
|
-
val roleId = uuid("role_id").references(RolesTable.id, onDelete = ReferenceOption.CASCADE)
|
|
179
|
-
override val primaryKey = PrimaryKey(userId, roleId)
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
## DSL Queries
|
|
184
|
-
|
|
185
|
-
### Basic CRUD
|
|
186
|
-
|
|
187
|
-
```kotlin
|
|
188
|
-
// Insert
|
|
189
|
-
suspend fun insertUser(name: String, email: String, role: Role): UUID =
|
|
190
|
-
newSuspendedTransaction {
|
|
191
|
-
UsersTable.insertAndGetId {
|
|
192
|
-
it[UsersTable.name] = name
|
|
193
|
-
it[UsersTable.email] = email
|
|
194
|
-
it[UsersTable.role] = role
|
|
195
|
-
}.value
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Select by ID
|
|
199
|
-
suspend fun findUserById(id: UUID): UserRow? =
|
|
200
|
-
newSuspendedTransaction {
|
|
201
|
-
UsersTable.selectAll()
|
|
202
|
-
.where { UsersTable.id eq id }
|
|
203
|
-
.map { it.toUser() }
|
|
204
|
-
.singleOrNull()
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Select with conditions
|
|
208
|
-
suspend fun findActiveAdmins(): List<UserRow> =
|
|
209
|
-
newSuspendedTransaction {
|
|
210
|
-
UsersTable.selectAll()
|
|
211
|
-
.where { (UsersTable.role eq Role.ADMIN) }
|
|
212
|
-
.orderBy(UsersTable.name)
|
|
213
|
-
.map { it.toUser() }
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Update
|
|
217
|
-
suspend fun updateUserEmail(id: UUID, newEmail: String): Boolean =
|
|
218
|
-
newSuspendedTransaction {
|
|
219
|
-
UsersTable.update({ UsersTable.id eq id }) {
|
|
220
|
-
it[email] = newEmail
|
|
221
|
-
it[updatedAt] = CurrentTimestampWithTimeZone
|
|
222
|
-
} > 0
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Delete
|
|
226
|
-
suspend fun deleteUser(id: UUID): Boolean =
|
|
227
|
-
newSuspendedTransaction {
|
|
228
|
-
UsersTable.deleteWhere { UsersTable.id eq id } > 0
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Row mapping
|
|
232
|
-
private fun ResultRow.toUser() = UserRow(
|
|
233
|
-
id = this[UsersTable.id].value,
|
|
234
|
-
name = this[UsersTable.name],
|
|
235
|
-
email = this[UsersTable.email],
|
|
236
|
-
role = this[UsersTable.role],
|
|
237
|
-
metadata = this[UsersTable.metadata],
|
|
238
|
-
createdAt = this[UsersTable.createdAt],
|
|
239
|
-
updatedAt = this[UsersTable.updatedAt],
|
|
240
|
-
)
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### Advanced Queries
|
|
244
|
-
|
|
245
|
-
```kotlin
|
|
246
|
-
// Join queries
|
|
247
|
-
suspend fun findOrdersWithUser(userId: UUID): List<OrderWithUser> =
|
|
248
|
-
newSuspendedTransaction {
|
|
249
|
-
(OrdersTable innerJoin UsersTable)
|
|
250
|
-
.selectAll()
|
|
251
|
-
.where { OrdersTable.userId eq userId }
|
|
252
|
-
.orderBy(OrdersTable.createdAt, SortOrder.DESC)
|
|
253
|
-
.map { row ->
|
|
254
|
-
OrderWithUser(
|
|
255
|
-
orderId = row[OrdersTable.id].value,
|
|
256
|
-
status = row[OrdersTable.status],
|
|
257
|
-
totalAmount = row[OrdersTable.totalAmount],
|
|
258
|
-
userName = row[UsersTable.name],
|
|
259
|
-
)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Aggregation
|
|
264
|
-
suspend fun countUsersByRole(): Map<Role, Long> =
|
|
265
|
-
newSuspendedTransaction {
|
|
266
|
-
UsersTable
|
|
267
|
-
.select(UsersTable.role, UsersTable.id.count())
|
|
268
|
-
.groupBy(UsersTable.role)
|
|
269
|
-
.associate { row ->
|
|
270
|
-
row[UsersTable.role] to row[UsersTable.id.count()]
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Subqueries
|
|
275
|
-
suspend fun findUsersWithOrders(): List<UserRow> =
|
|
276
|
-
newSuspendedTransaction {
|
|
277
|
-
UsersTable.selectAll()
|
|
278
|
-
.where {
|
|
279
|
-
UsersTable.id inSubQuery
|
|
280
|
-
OrdersTable.select(OrdersTable.userId).withDistinct()
|
|
281
|
-
}
|
|
282
|
-
.map { it.toUser() }
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// LIKE and pattern matching — always escape user input to prevent wildcard injection
|
|
286
|
-
private fun escapeLikePattern(input: String): String =
|
|
287
|
-
input.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
|
288
|
-
|
|
289
|
-
suspend fun searchUsers(query: String): List<UserRow> =
|
|
290
|
-
newSuspendedTransaction {
|
|
291
|
-
val sanitized = escapeLikePattern(query.lowercase())
|
|
292
|
-
UsersTable.selectAll()
|
|
293
|
-
.where {
|
|
294
|
-
(UsersTable.name.lowerCase() like "%${sanitized}%") or
|
|
295
|
-
(UsersTable.email.lowerCase() like "%${sanitized}%")
|
|
296
|
-
}
|
|
297
|
-
.map { it.toUser() }
|
|
298
|
-
}
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### Pagination
|
|
302
|
-
|
|
303
|
-
```kotlin
|
|
304
|
-
data class Page<T>(
|
|
305
|
-
val data: List<T>,
|
|
306
|
-
val total: Long,
|
|
307
|
-
val page: Int,
|
|
308
|
-
val limit: Int,
|
|
309
|
-
) {
|
|
310
|
-
val totalPages: Int get() = ((total + limit - 1) / limit).toInt()
|
|
311
|
-
val hasNext: Boolean get() = page < totalPages
|
|
312
|
-
val hasPrevious: Boolean get() = page > 1
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
suspend fun findUsersPaginated(page: Int, limit: Int): Page<UserRow> =
|
|
316
|
-
newSuspendedTransaction {
|
|
317
|
-
val total = UsersTable.selectAll().count()
|
|
318
|
-
val data = UsersTable.selectAll()
|
|
319
|
-
.orderBy(UsersTable.createdAt, SortOrder.DESC)
|
|
320
|
-
.limit(limit)
|
|
321
|
-
.offset(((page - 1) * limit).toLong())
|
|
322
|
-
.map { it.toUser() }
|
|
323
|
-
|
|
324
|
-
Page(data = data, total = total, page = page, limit = limit)
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### Batch Operations
|
|
329
|
-
|
|
330
|
-
```kotlin
|
|
331
|
-
// Batch insert
|
|
332
|
-
suspend fun insertUsers(users: List<CreateUserRequest>): List<UUID> =
|
|
333
|
-
newSuspendedTransaction {
|
|
334
|
-
UsersTable.batchInsert(users) { user ->
|
|
335
|
-
this[UsersTable.name] = user.name
|
|
336
|
-
this[UsersTable.email] = user.email
|
|
337
|
-
this[UsersTable.role] = user.role
|
|
338
|
-
}.map { it[UsersTable.id].value }
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Upsert (insert or update on conflict)
|
|
342
|
-
suspend fun upsertUser(id: UUID, name: String, email: String) {
|
|
343
|
-
newSuspendedTransaction {
|
|
344
|
-
UsersTable.upsert(UsersTable.email) {
|
|
345
|
-
it[UsersTable.id] = EntityID(id, UsersTable)
|
|
346
|
-
it[UsersTable.name] = name
|
|
347
|
-
it[UsersTable.email] = email
|
|
348
|
-
it[updatedAt] = CurrentTimestampWithTimeZone
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## DAO Pattern
|
|
355
|
-
|
|
356
|
-
### Entity Definitions
|
|
357
|
-
|
|
358
|
-
```kotlin
|
|
359
|
-
// entities/UserEntity.kt
|
|
360
|
-
class UserEntity(id: EntityID<UUID>) : UUIDEntity(id) {
|
|
361
|
-
companion object : UUIDEntityClass<UserEntity>(UsersTable)
|
|
362
|
-
|
|
363
|
-
var name by UsersTable.name
|
|
364
|
-
var email by UsersTable.email
|
|
365
|
-
var role by UsersTable.role
|
|
366
|
-
var metadata by UsersTable.metadata
|
|
367
|
-
var createdAt by UsersTable.createdAt
|
|
368
|
-
var updatedAt by UsersTable.updatedAt
|
|
369
|
-
|
|
370
|
-
val orders by OrderEntity referrersOn OrdersTable.userId
|
|
371
|
-
|
|
372
|
-
fun toModel(): User = User(
|
|
373
|
-
id = id.value,
|
|
374
|
-
name = name,
|
|
375
|
-
email = email,
|
|
376
|
-
role = role,
|
|
377
|
-
metadata = metadata,
|
|
378
|
-
createdAt = createdAt,
|
|
379
|
-
updatedAt = updatedAt,
|
|
380
|
-
)
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
class OrderEntity(id: EntityID<UUID>) : UUIDEntity(id) {
|
|
384
|
-
companion object : UUIDEntityClass<OrderEntity>(OrdersTable)
|
|
385
|
-
|
|
386
|
-
var user by UserEntity referencedOn OrdersTable.userId
|
|
387
|
-
var status by OrdersTable.status
|
|
388
|
-
var totalAmount by OrdersTable.totalAmount
|
|
389
|
-
var currency by OrdersTable.currency
|
|
390
|
-
var createdAt by OrdersTable.createdAt
|
|
391
|
-
|
|
392
|
-
val items by OrderItemEntity referrersOn OrderItemsTable.orderId
|
|
393
|
-
}
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### DAO Operations
|
|
397
|
-
|
|
398
|
-
```kotlin
|
|
399
|
-
suspend fun findUserByEmail(email: String): User? =
|
|
400
|
-
newSuspendedTransaction {
|
|
401
|
-
UserEntity.find { UsersTable.email eq email }
|
|
402
|
-
.firstOrNull()
|
|
403
|
-
?.toModel()
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
suspend fun createUser(request: CreateUserRequest): User =
|
|
407
|
-
newSuspendedTransaction {
|
|
408
|
-
UserEntity.new {
|
|
409
|
-
name = request.name
|
|
410
|
-
email = request.email
|
|
411
|
-
role = request.role
|
|
412
|
-
}.toModel()
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
suspend fun updateUser(id: UUID, request: UpdateUserRequest): User? =
|
|
416
|
-
newSuspendedTransaction {
|
|
417
|
-
UserEntity.findById(id)?.apply {
|
|
418
|
-
request.name?.let { name = it }
|
|
419
|
-
request.email?.let { email = it }
|
|
420
|
-
updatedAt = OffsetDateTime.now(ZoneOffset.UTC)
|
|
421
|
-
}?.toModel()
|
|
422
|
-
}
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
## Transactions
|
|
426
|
-
|
|
427
|
-
### Suspend Transaction Support
|
|
428
|
-
|
|
429
|
-
```kotlin
|
|
430
|
-
// Good: Use newSuspendedTransaction for coroutine support
|
|
431
|
-
suspend fun performDatabaseOperation(): Result<User> =
|
|
432
|
-
runCatching {
|
|
433
|
-
newSuspendedTransaction {
|
|
434
|
-
val user = UserEntity.new {
|
|
435
|
-
name = "Alice"
|
|
436
|
-
email = "alice@example.com"
|
|
437
|
-
}
|
|
438
|
-
// All operations in this block are atomic
|
|
439
|
-
user.toModel()
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Good: Nested transactions with savepoints
|
|
444
|
-
suspend fun transferFunds(fromId: UUID, toId: UUID, amount: Long) {
|
|
445
|
-
newSuspendedTransaction {
|
|
446
|
-
val from = UserEntity.findById(fromId) ?: throw NotFoundException("User $fromId not found")
|
|
447
|
-
val to = UserEntity.findById(toId) ?: throw NotFoundException("User $toId not found")
|
|
448
|
-
|
|
449
|
-
// Debit
|
|
450
|
-
from.balance -= amount
|
|
451
|
-
// Credit
|
|
452
|
-
to.balance += amount
|
|
453
|
-
|
|
454
|
-
// Both succeed or both fail
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### Transaction Isolation
|
|
460
|
-
|
|
461
|
-
```kotlin
|
|
462
|
-
suspend fun readCommittedQuery(): List<User> =
|
|
463
|
-
newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {
|
|
464
|
-
UserEntity.all().map { it.toModel() }
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
suspend fun serializableOperation() {
|
|
468
|
-
newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_SERIALIZABLE) {
|
|
469
|
-
// Strictest isolation level for critical operations
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
## Repository Pattern
|
|
475
|
-
|
|
476
|
-
### Interface Definition
|
|
477
|
-
|
|
478
|
-
```kotlin
|
|
479
|
-
interface UserRepository {
|
|
480
|
-
suspend fun findById(id: UUID): User?
|
|
481
|
-
suspend fun findByEmail(email: String): User?
|
|
482
|
-
suspend fun findAll(page: Int, limit: Int): Page<User>
|
|
483
|
-
suspend fun search(query: String): List<User>
|
|
484
|
-
suspend fun create(request: CreateUserRequest): User
|
|
485
|
-
suspend fun update(id: UUID, request: UpdateUserRequest): User?
|
|
486
|
-
suspend fun delete(id: UUID): Boolean
|
|
487
|
-
suspend fun count(): Long
|
|
488
|
-
}
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
### Exposed Implementation
|
|
492
|
-
|
|
493
|
-
```kotlin
|
|
494
|
-
class ExposedUserRepository(
|
|
495
|
-
private val database: Database,
|
|
496
|
-
) : UserRepository {
|
|
497
|
-
|
|
498
|
-
override suspend fun findById(id: UUID): User? =
|
|
499
|
-
newSuspendedTransaction(db = database) {
|
|
500
|
-
UsersTable.selectAll()
|
|
501
|
-
.where { UsersTable.id eq id }
|
|
502
|
-
.map { it.toUser() }
|
|
503
|
-
.singleOrNull()
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
override suspend fun findByEmail(email: String): User? =
|
|
507
|
-
newSuspendedTransaction(db = database) {
|
|
508
|
-
UsersTable.selectAll()
|
|
509
|
-
.where { UsersTable.email eq email }
|
|
510
|
-
.map { it.toUser() }
|
|
511
|
-
.singleOrNull()
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
override suspend fun findAll(page: Int, limit: Int): Page<User> =
|
|
515
|
-
newSuspendedTransaction(db = database) {
|
|
516
|
-
val total = UsersTable.selectAll().count()
|
|
517
|
-
val data = UsersTable.selectAll()
|
|
518
|
-
.orderBy(UsersTable.createdAt, SortOrder.DESC)
|
|
519
|
-
.limit(limit)
|
|
520
|
-
.offset(((page - 1) * limit).toLong())
|
|
521
|
-
.map { it.toUser() }
|
|
522
|
-
Page(data = data, total = total, page = page, limit = limit)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
override suspend fun search(query: String): List<User> =
|
|
526
|
-
newSuspendedTransaction(db = database) {
|
|
527
|
-
val sanitized = escapeLikePattern(query.lowercase())
|
|
528
|
-
UsersTable.selectAll()
|
|
529
|
-
.where {
|
|
530
|
-
(UsersTable.name.lowerCase() like "%${sanitized}%") or
|
|
531
|
-
(UsersTable.email.lowerCase() like "%${sanitized}%")
|
|
532
|
-
}
|
|
533
|
-
.orderBy(UsersTable.name)
|
|
534
|
-
.map { it.toUser() }
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
override suspend fun create(request: CreateUserRequest): User =
|
|
538
|
-
newSuspendedTransaction(db = database) {
|
|
539
|
-
UsersTable.insert {
|
|
540
|
-
it[name] = request.name
|
|
541
|
-
it[email] = request.email
|
|
542
|
-
it[role] = request.role
|
|
543
|
-
}.resultedValues!!.first().toUser()
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
override suspend fun update(id: UUID, request: UpdateUserRequest): User? =
|
|
547
|
-
newSuspendedTransaction(db = database) {
|
|
548
|
-
val updated = UsersTable.update({ UsersTable.id eq id }) {
|
|
549
|
-
request.name?.let { name -> it[UsersTable.name] = name }
|
|
550
|
-
request.email?.let { email -> it[UsersTable.email] = email }
|
|
551
|
-
it[updatedAt] = CurrentTimestampWithTimeZone
|
|
552
|
-
}
|
|
553
|
-
if (updated > 0) findById(id) else null
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
override suspend fun delete(id: UUID): Boolean =
|
|
557
|
-
newSuspendedTransaction(db = database) {
|
|
558
|
-
UsersTable.deleteWhere { UsersTable.id eq id } > 0
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
override suspend fun count(): Long =
|
|
562
|
-
newSuspendedTransaction(db = database) {
|
|
563
|
-
UsersTable.selectAll().count()
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
private fun ResultRow.toUser() = User(
|
|
567
|
-
id = this[UsersTable.id].value,
|
|
568
|
-
name = this[UsersTable.name],
|
|
569
|
-
email = this[UsersTable.email],
|
|
570
|
-
role = this[UsersTable.role],
|
|
571
|
-
metadata = this[UsersTable.metadata],
|
|
572
|
-
createdAt = this[UsersTable.createdAt],
|
|
573
|
-
updatedAt = this[UsersTable.updatedAt],
|
|
574
|
-
)
|
|
575
|
-
}
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
## JSON Columns
|
|
579
|
-
|
|
580
|
-
### JSONB with kotlinx.serialization
|
|
581
|
-
|
|
582
|
-
```kotlin
|
|
583
|
-
// Custom column type for JSONB
|
|
584
|
-
inline fun <reified T : Any> Table.jsonb(
|
|
585
|
-
name: String,
|
|
586
|
-
json: Json,
|
|
587
|
-
): Column<T> = registerColumn(name, object : ColumnType<T>() {
|
|
588
|
-
override fun sqlType() = "JSONB"
|
|
589
|
-
|
|
590
|
-
override fun valueFromDB(value: Any): T = when (value) {
|
|
591
|
-
is String -> json.decodeFromString(value)
|
|
592
|
-
is PGobject -> {
|
|
593
|
-
val jsonString = value.value
|
|
594
|
-
?: throw IllegalArgumentException("PGobject value is null for column '$name'")
|
|
595
|
-
json.decodeFromString(jsonString)
|
|
596
|
-
}
|
|
597
|
-
else -> throw IllegalArgumentException("Unexpected value: $value")
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
override fun notNullValueToDB(value: T): Any =
|
|
601
|
-
PGobject().apply {
|
|
602
|
-
type = "jsonb"
|
|
603
|
-
this.value = json.encodeToString(value)
|
|
604
|
-
}
|
|
605
|
-
})
|
|
606
|
-
|
|
607
|
-
// Usage in table
|
|
608
|
-
@Serializable
|
|
609
|
-
data class UserMetadata(
|
|
610
|
-
val preferences: Map<String, String> = emptyMap(),
|
|
611
|
-
val tags: List<String> = emptyList(),
|
|
612
|
-
)
|
|
613
|
-
|
|
614
|
-
object UsersTable : UUIDTable("users") {
|
|
615
|
-
val metadata = jsonb<UserMetadata>("metadata", Json.Default).nullable()
|
|
616
|
-
}
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
## Testing with Exposed
|
|
620
|
-
|
|
621
|
-
### In-Memory Database for Tests
|
|
622
|
-
|
|
623
|
-
```kotlin
|
|
624
|
-
class UserRepositoryTest : FunSpec({
|
|
625
|
-
lateinit var database: Database
|
|
626
|
-
lateinit var repository: UserRepository
|
|
627
|
-
|
|
628
|
-
beforeSpec {
|
|
629
|
-
database = Database.connect(
|
|
630
|
-
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL",
|
|
631
|
-
driver = "org.h2.Driver",
|
|
632
|
-
)
|
|
633
|
-
transaction(database) {
|
|
634
|
-
SchemaUtils.create(UsersTable)
|
|
635
|
-
}
|
|
636
|
-
repository = ExposedUserRepository(database)
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
beforeTest {
|
|
640
|
-
transaction(database) {
|
|
641
|
-
UsersTable.deleteAll()
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
test("create and find user") {
|
|
646
|
-
val user = repository.create(CreateUserRequest("Alice", "alice@example.com"))
|
|
647
|
-
|
|
648
|
-
user.name shouldBe "Alice"
|
|
649
|
-
user.email shouldBe "alice@example.com"
|
|
650
|
-
|
|
651
|
-
val found = repository.findById(user.id)
|
|
652
|
-
found shouldBe user
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
test("findByEmail returns null for unknown email") {
|
|
656
|
-
val result = repository.findByEmail("unknown@example.com")
|
|
657
|
-
result.shouldBeNull()
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
test("pagination works correctly") {
|
|
661
|
-
repeat(25) { i ->
|
|
662
|
-
repository.create(CreateUserRequest("User $i", "user$i@example.com"))
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
val page1 = repository.findAll(page = 1, limit = 10)
|
|
666
|
-
page1.data shouldHaveSize 10
|
|
667
|
-
page1.total shouldBe 25
|
|
668
|
-
page1.hasNext shouldBe true
|
|
669
|
-
|
|
670
|
-
val page3 = repository.findAll(page = 3, limit = 10)
|
|
671
|
-
page3.data shouldHaveSize 5
|
|
672
|
-
page3.hasNext shouldBe false
|
|
673
|
-
}
|
|
674
|
-
})
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
## Gradle Dependencies
|
|
678
|
-
|
|
679
|
-
```kotlin
|
|
680
|
-
// build.gradle.kts
|
|
681
|
-
dependencies {
|
|
682
|
-
// Exposed
|
|
683
|
-
implementation("org.jetbrains.exposed:exposed-core:1.0.0")
|
|
684
|
-
implementation("org.jetbrains.exposed:exposed-dao:1.0.0")
|
|
685
|
-
implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0")
|
|
686
|
-
implementation("org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0")
|
|
687
|
-
implementation("org.jetbrains.exposed:exposed-json:1.0.0")
|
|
688
|
-
|
|
689
|
-
// Database driver
|
|
690
|
-
implementation("org.postgresql:postgresql:42.7.5")
|
|
691
|
-
|
|
692
|
-
// Connection pooling
|
|
693
|
-
implementation("com.zaxxer:HikariCP:6.2.1")
|
|
694
|
-
|
|
695
|
-
// Migrations
|
|
696
|
-
implementation("org.flywaydb:flyway-core:10.22.0")
|
|
697
|
-
implementation("org.flywaydb:flyway-database-postgresql:10.22.0")
|
|
698
|
-
|
|
699
|
-
// Testing
|
|
700
|
-
testImplementation("com.h2database:h2:2.3.232")
|
|
701
|
-
}
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
## Quick Reference: Exposed Patterns
|
|
705
|
-
|
|
706
|
-
| Pattern | Description |
|
|
707
|
-
|---------|-------------|
|
|
708
|
-
| `object Table : UUIDTable("name")` | Define table with UUID primary key |
|
|
709
|
-
| `newSuspendedTransaction { }` | Coroutine-safe transaction block |
|
|
710
|
-
| `Table.selectAll().where { }` | Query with conditions |
|
|
711
|
-
| `Table.insertAndGetId { }` | Insert and return generated ID |
|
|
712
|
-
| `Table.update({ condition }) { }` | Update matching rows |
|
|
713
|
-
| `Table.deleteWhere { }` | Delete matching rows |
|
|
714
|
-
| `Table.batchInsert(items) { }` | Efficient bulk insert |
|
|
715
|
-
| `innerJoin` / `leftJoin` | Join tables |
|
|
716
|
-
| `orderBy` / `limit` / `offset` | Sort and paginate |
|
|
717
|
-
| `count()` / `sum()` / `avg()` | Aggregation functions |
|
|
718
|
-
|
|
719
|
-
**Remember**: Use the DSL style for simple queries and the DAO style when you need entity lifecycle management. Always use `newSuspendedTransaction` for coroutine support, and wrap database operations behind a repository interface for testability.
|
|
1
|
+
---
|
|
2
|
+
name: kotlin-exposed-patterns
|
|
3
|
+
description: JetBrains Exposed ORM patterns including DSL queries, DAO pattern, transactions, HikariCP connection pooling, Flyway migrations, and repository pattern.
|
|
4
|
+
origin: ECC
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Kotlin Exposed Patterns
|
|
8
|
+
|
|
9
|
+
Comprehensive patterns for database access with JetBrains Exposed ORM, including DSL queries, DAO, transactions, and production-ready configuration.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
- Setting up database access with Exposed
|
|
14
|
+
- Writing SQL queries using Exposed DSL or DAO
|
|
15
|
+
- Configuring connection pooling with HikariCP
|
|
16
|
+
- Creating database migrations with Flyway
|
|
17
|
+
- Implementing the repository pattern with Exposed
|
|
18
|
+
- Handling JSON columns and complex queries
|
|
19
|
+
|
|
20
|
+
## How It Works
|
|
21
|
+
|
|
22
|
+
Exposed provides two query styles: DSL for direct SQL-like expressions and DAO for entity lifecycle management. HikariCP manages a pool of reusable database connections configured via `HikariConfig`. Flyway runs versioned SQL migration scripts at startup to keep the schema in sync. All database operations run inside `newSuspendedTransaction` blocks for coroutine safety and atomicity. The repository pattern wraps Exposed queries behind an interface so business logic stays decoupled from the data layer and tests can use an in-memory H2 database.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### DSL Query
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
suspend fun findUserById(id: UUID): UserRow? =
|
|
30
|
+
newSuspendedTransaction {
|
|
31
|
+
UsersTable.selectAll()
|
|
32
|
+
.where { UsersTable.id eq id }
|
|
33
|
+
.map { it.toUser() }
|
|
34
|
+
.singleOrNull()
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### DAO Entity Usage
|
|
39
|
+
|
|
40
|
+
```kotlin
|
|
41
|
+
suspend fun createUser(request: CreateUserRequest): User =
|
|
42
|
+
newSuspendedTransaction {
|
|
43
|
+
UserEntity.new {
|
|
44
|
+
name = request.name
|
|
45
|
+
email = request.email
|
|
46
|
+
role = request.role
|
|
47
|
+
}.toModel()
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### HikariCP Configuration
|
|
52
|
+
|
|
53
|
+
```kotlin
|
|
54
|
+
val hikariConfig = HikariConfig().apply {
|
|
55
|
+
driverClassName = config.driver
|
|
56
|
+
jdbcUrl = config.url
|
|
57
|
+
username = config.username
|
|
58
|
+
password = config.password
|
|
59
|
+
maximumPoolSize = config.maxPoolSize
|
|
60
|
+
isAutoCommit = false
|
|
61
|
+
transactionIsolation = "TRANSACTION_READ_COMMITTED"
|
|
62
|
+
validate()
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Database Setup
|
|
67
|
+
|
|
68
|
+
### HikariCP Connection Pooling
|
|
69
|
+
|
|
70
|
+
```kotlin
|
|
71
|
+
// DatabaseFactory.kt
|
|
72
|
+
object DatabaseFactory {
|
|
73
|
+
fun create(config: DatabaseConfig): Database {
|
|
74
|
+
val hikariConfig = HikariConfig().apply {
|
|
75
|
+
driverClassName = config.driver
|
|
76
|
+
jdbcUrl = config.url
|
|
77
|
+
username = config.username
|
|
78
|
+
password = config.password
|
|
79
|
+
maximumPoolSize = config.maxPoolSize
|
|
80
|
+
isAutoCommit = false
|
|
81
|
+
transactionIsolation = "TRANSACTION_READ_COMMITTED"
|
|
82
|
+
validate()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return Database.connect(HikariDataSource(hikariConfig))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
data class DatabaseConfig(
|
|
90
|
+
val url: String,
|
|
91
|
+
val driver: String = "org.postgresql.Driver",
|
|
92
|
+
val username: String = "",
|
|
93
|
+
val password: String = "",
|
|
94
|
+
val maxPoolSize: Int = 10,
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Flyway Migrations
|
|
99
|
+
|
|
100
|
+
```kotlin
|
|
101
|
+
// FlywayMigration.kt
|
|
102
|
+
fun runMigrations(config: DatabaseConfig) {
|
|
103
|
+
Flyway.configure()
|
|
104
|
+
.dataSource(config.url, config.username, config.password)
|
|
105
|
+
.locations("classpath:db/migration")
|
|
106
|
+
.baselineOnMigrate(true)
|
|
107
|
+
.load()
|
|
108
|
+
.migrate()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Application startup
|
|
112
|
+
fun Application.module() {
|
|
113
|
+
val config = DatabaseConfig(
|
|
114
|
+
url = environment.config.property("database.url").getString(),
|
|
115
|
+
username = environment.config.property("database.username").getString(),
|
|
116
|
+
password = environment.config.property("database.password").getString(),
|
|
117
|
+
)
|
|
118
|
+
runMigrations(config)
|
|
119
|
+
val database = DatabaseFactory.create(config)
|
|
120
|
+
// ...
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Migration Files
|
|
125
|
+
|
|
126
|
+
```sql
|
|
127
|
+
-- src/main/resources/db/migration/V1__create_users.sql
|
|
128
|
+
CREATE TABLE users (
|
|
129
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
130
|
+
name VARCHAR(100) NOT NULL,
|
|
131
|
+
email VARCHAR(255) NOT NULL UNIQUE,
|
|
132
|
+
role VARCHAR(20) NOT NULL DEFAULT 'USER',
|
|
133
|
+
metadata JSONB,
|
|
134
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
135
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
139
|
+
CREATE INDEX idx_users_role ON users(role);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Table Definitions
|
|
143
|
+
|
|
144
|
+
### DSL Style Tables
|
|
145
|
+
|
|
146
|
+
```kotlin
|
|
147
|
+
// tables/UsersTable.kt
|
|
148
|
+
object UsersTable : UUIDTable("users") {
|
|
149
|
+
val name = varchar("name", 100)
|
|
150
|
+
val email = varchar("email", 255).uniqueIndex()
|
|
151
|
+
val role = enumerationByName<Role>("role", 20)
|
|
152
|
+
val metadata = jsonb<UserMetadata>("metadata", Json.Default).nullable()
|
|
153
|
+
val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
|
|
154
|
+
val updatedAt = timestampWithTimeZone("updated_at").defaultExpression(CurrentTimestampWithTimeZone)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
object OrdersTable : UUIDTable("orders") {
|
|
158
|
+
val userId = uuid("user_id").references(UsersTable.id)
|
|
159
|
+
val status = enumerationByName<OrderStatus>("status", 20)
|
|
160
|
+
val totalAmount = long("total_amount")
|
|
161
|
+
val currency = varchar("currency", 3)
|
|
162
|
+
val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
object OrderItemsTable : UUIDTable("order_items") {
|
|
166
|
+
val orderId = uuid("order_id").references(OrdersTable.id, onDelete = ReferenceOption.CASCADE)
|
|
167
|
+
val productId = uuid("product_id")
|
|
168
|
+
val quantity = integer("quantity")
|
|
169
|
+
val unitPrice = long("unit_price")
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Composite Tables
|
|
174
|
+
|
|
175
|
+
```kotlin
|
|
176
|
+
object UserRolesTable : Table("user_roles") {
|
|
177
|
+
val userId = uuid("user_id").references(UsersTable.id, onDelete = ReferenceOption.CASCADE)
|
|
178
|
+
val roleId = uuid("role_id").references(RolesTable.id, onDelete = ReferenceOption.CASCADE)
|
|
179
|
+
override val primaryKey = PrimaryKey(userId, roleId)
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## DSL Queries
|
|
184
|
+
|
|
185
|
+
### Basic CRUD
|
|
186
|
+
|
|
187
|
+
```kotlin
|
|
188
|
+
// Insert
|
|
189
|
+
suspend fun insertUser(name: String, email: String, role: Role): UUID =
|
|
190
|
+
newSuspendedTransaction {
|
|
191
|
+
UsersTable.insertAndGetId {
|
|
192
|
+
it[UsersTable.name] = name
|
|
193
|
+
it[UsersTable.email] = email
|
|
194
|
+
it[UsersTable.role] = role
|
|
195
|
+
}.value
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Select by ID
|
|
199
|
+
suspend fun findUserById(id: UUID): UserRow? =
|
|
200
|
+
newSuspendedTransaction {
|
|
201
|
+
UsersTable.selectAll()
|
|
202
|
+
.where { UsersTable.id eq id }
|
|
203
|
+
.map { it.toUser() }
|
|
204
|
+
.singleOrNull()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Select with conditions
|
|
208
|
+
suspend fun findActiveAdmins(): List<UserRow> =
|
|
209
|
+
newSuspendedTransaction {
|
|
210
|
+
UsersTable.selectAll()
|
|
211
|
+
.where { (UsersTable.role eq Role.ADMIN) }
|
|
212
|
+
.orderBy(UsersTable.name)
|
|
213
|
+
.map { it.toUser() }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Update
|
|
217
|
+
suspend fun updateUserEmail(id: UUID, newEmail: String): Boolean =
|
|
218
|
+
newSuspendedTransaction {
|
|
219
|
+
UsersTable.update({ UsersTable.id eq id }) {
|
|
220
|
+
it[email] = newEmail
|
|
221
|
+
it[updatedAt] = CurrentTimestampWithTimeZone
|
|
222
|
+
} > 0
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Delete
|
|
226
|
+
suspend fun deleteUser(id: UUID): Boolean =
|
|
227
|
+
newSuspendedTransaction {
|
|
228
|
+
UsersTable.deleteWhere { UsersTable.id eq id } > 0
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Row mapping
|
|
232
|
+
private fun ResultRow.toUser() = UserRow(
|
|
233
|
+
id = this[UsersTable.id].value,
|
|
234
|
+
name = this[UsersTable.name],
|
|
235
|
+
email = this[UsersTable.email],
|
|
236
|
+
role = this[UsersTable.role],
|
|
237
|
+
metadata = this[UsersTable.metadata],
|
|
238
|
+
createdAt = this[UsersTable.createdAt],
|
|
239
|
+
updatedAt = this[UsersTable.updatedAt],
|
|
240
|
+
)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Advanced Queries
|
|
244
|
+
|
|
245
|
+
```kotlin
|
|
246
|
+
// Join queries
|
|
247
|
+
suspend fun findOrdersWithUser(userId: UUID): List<OrderWithUser> =
|
|
248
|
+
newSuspendedTransaction {
|
|
249
|
+
(OrdersTable innerJoin UsersTable)
|
|
250
|
+
.selectAll()
|
|
251
|
+
.where { OrdersTable.userId eq userId }
|
|
252
|
+
.orderBy(OrdersTable.createdAt, SortOrder.DESC)
|
|
253
|
+
.map { row ->
|
|
254
|
+
OrderWithUser(
|
|
255
|
+
orderId = row[OrdersTable.id].value,
|
|
256
|
+
status = row[OrdersTable.status],
|
|
257
|
+
totalAmount = row[OrdersTable.totalAmount],
|
|
258
|
+
userName = row[UsersTable.name],
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Aggregation
|
|
264
|
+
suspend fun countUsersByRole(): Map<Role, Long> =
|
|
265
|
+
newSuspendedTransaction {
|
|
266
|
+
UsersTable
|
|
267
|
+
.select(UsersTable.role, UsersTable.id.count())
|
|
268
|
+
.groupBy(UsersTable.role)
|
|
269
|
+
.associate { row ->
|
|
270
|
+
row[UsersTable.role] to row[UsersTable.id.count()]
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Subqueries
|
|
275
|
+
suspend fun findUsersWithOrders(): List<UserRow> =
|
|
276
|
+
newSuspendedTransaction {
|
|
277
|
+
UsersTable.selectAll()
|
|
278
|
+
.where {
|
|
279
|
+
UsersTable.id inSubQuery
|
|
280
|
+
OrdersTable.select(OrdersTable.userId).withDistinct()
|
|
281
|
+
}
|
|
282
|
+
.map { it.toUser() }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// LIKE and pattern matching — always escape user input to prevent wildcard injection
|
|
286
|
+
private fun escapeLikePattern(input: String): String =
|
|
287
|
+
input.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
|
288
|
+
|
|
289
|
+
suspend fun searchUsers(query: String): List<UserRow> =
|
|
290
|
+
newSuspendedTransaction {
|
|
291
|
+
val sanitized = escapeLikePattern(query.lowercase())
|
|
292
|
+
UsersTable.selectAll()
|
|
293
|
+
.where {
|
|
294
|
+
(UsersTable.name.lowerCase() like "%${sanitized}%") or
|
|
295
|
+
(UsersTable.email.lowerCase() like "%${sanitized}%")
|
|
296
|
+
}
|
|
297
|
+
.map { it.toUser() }
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Pagination
|
|
302
|
+
|
|
303
|
+
```kotlin
|
|
304
|
+
data class Page<T>(
|
|
305
|
+
val data: List<T>,
|
|
306
|
+
val total: Long,
|
|
307
|
+
val page: Int,
|
|
308
|
+
val limit: Int,
|
|
309
|
+
) {
|
|
310
|
+
val totalPages: Int get() = ((total + limit - 1) / limit).toInt()
|
|
311
|
+
val hasNext: Boolean get() = page < totalPages
|
|
312
|
+
val hasPrevious: Boolean get() = page > 1
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
suspend fun findUsersPaginated(page: Int, limit: Int): Page<UserRow> =
|
|
316
|
+
newSuspendedTransaction {
|
|
317
|
+
val total = UsersTable.selectAll().count()
|
|
318
|
+
val data = UsersTable.selectAll()
|
|
319
|
+
.orderBy(UsersTable.createdAt, SortOrder.DESC)
|
|
320
|
+
.limit(limit)
|
|
321
|
+
.offset(((page - 1) * limit).toLong())
|
|
322
|
+
.map { it.toUser() }
|
|
323
|
+
|
|
324
|
+
Page(data = data, total = total, page = page, limit = limit)
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Batch Operations
|
|
329
|
+
|
|
330
|
+
```kotlin
|
|
331
|
+
// Batch insert
|
|
332
|
+
suspend fun insertUsers(users: List<CreateUserRequest>): List<UUID> =
|
|
333
|
+
newSuspendedTransaction {
|
|
334
|
+
UsersTable.batchInsert(users) { user ->
|
|
335
|
+
this[UsersTable.name] = user.name
|
|
336
|
+
this[UsersTable.email] = user.email
|
|
337
|
+
this[UsersTable.role] = user.role
|
|
338
|
+
}.map { it[UsersTable.id].value }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Upsert (insert or update on conflict)
|
|
342
|
+
suspend fun upsertUser(id: UUID, name: String, email: String) {
|
|
343
|
+
newSuspendedTransaction {
|
|
344
|
+
UsersTable.upsert(UsersTable.email) {
|
|
345
|
+
it[UsersTable.id] = EntityID(id, UsersTable)
|
|
346
|
+
it[UsersTable.name] = name
|
|
347
|
+
it[UsersTable.email] = email
|
|
348
|
+
it[updatedAt] = CurrentTimestampWithTimeZone
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## DAO Pattern
|
|
355
|
+
|
|
356
|
+
### Entity Definitions
|
|
357
|
+
|
|
358
|
+
```kotlin
|
|
359
|
+
// entities/UserEntity.kt
|
|
360
|
+
class UserEntity(id: EntityID<UUID>) : UUIDEntity(id) {
|
|
361
|
+
companion object : UUIDEntityClass<UserEntity>(UsersTable)
|
|
362
|
+
|
|
363
|
+
var name by UsersTable.name
|
|
364
|
+
var email by UsersTable.email
|
|
365
|
+
var role by UsersTable.role
|
|
366
|
+
var metadata by UsersTable.metadata
|
|
367
|
+
var createdAt by UsersTable.createdAt
|
|
368
|
+
var updatedAt by UsersTable.updatedAt
|
|
369
|
+
|
|
370
|
+
val orders by OrderEntity referrersOn OrdersTable.userId
|
|
371
|
+
|
|
372
|
+
fun toModel(): User = User(
|
|
373
|
+
id = id.value,
|
|
374
|
+
name = name,
|
|
375
|
+
email = email,
|
|
376
|
+
role = role,
|
|
377
|
+
metadata = metadata,
|
|
378
|
+
createdAt = createdAt,
|
|
379
|
+
updatedAt = updatedAt,
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
class OrderEntity(id: EntityID<UUID>) : UUIDEntity(id) {
|
|
384
|
+
companion object : UUIDEntityClass<OrderEntity>(OrdersTable)
|
|
385
|
+
|
|
386
|
+
var user by UserEntity referencedOn OrdersTable.userId
|
|
387
|
+
var status by OrdersTable.status
|
|
388
|
+
var totalAmount by OrdersTable.totalAmount
|
|
389
|
+
var currency by OrdersTable.currency
|
|
390
|
+
var createdAt by OrdersTable.createdAt
|
|
391
|
+
|
|
392
|
+
val items by OrderItemEntity referrersOn OrderItemsTable.orderId
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### DAO Operations
|
|
397
|
+
|
|
398
|
+
```kotlin
|
|
399
|
+
suspend fun findUserByEmail(email: String): User? =
|
|
400
|
+
newSuspendedTransaction {
|
|
401
|
+
UserEntity.find { UsersTable.email eq email }
|
|
402
|
+
.firstOrNull()
|
|
403
|
+
?.toModel()
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
suspend fun createUser(request: CreateUserRequest): User =
|
|
407
|
+
newSuspendedTransaction {
|
|
408
|
+
UserEntity.new {
|
|
409
|
+
name = request.name
|
|
410
|
+
email = request.email
|
|
411
|
+
role = request.role
|
|
412
|
+
}.toModel()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
suspend fun updateUser(id: UUID, request: UpdateUserRequest): User? =
|
|
416
|
+
newSuspendedTransaction {
|
|
417
|
+
UserEntity.findById(id)?.apply {
|
|
418
|
+
request.name?.let { name = it }
|
|
419
|
+
request.email?.let { email = it }
|
|
420
|
+
updatedAt = OffsetDateTime.now(ZoneOffset.UTC)
|
|
421
|
+
}?.toModel()
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Transactions
|
|
426
|
+
|
|
427
|
+
### Suspend Transaction Support
|
|
428
|
+
|
|
429
|
+
```kotlin
|
|
430
|
+
// Good: Use newSuspendedTransaction for coroutine support
|
|
431
|
+
suspend fun performDatabaseOperation(): Result<User> =
|
|
432
|
+
runCatching {
|
|
433
|
+
newSuspendedTransaction {
|
|
434
|
+
val user = UserEntity.new {
|
|
435
|
+
name = "Alice"
|
|
436
|
+
email = "alice@example.com"
|
|
437
|
+
}
|
|
438
|
+
// All operations in this block are atomic
|
|
439
|
+
user.toModel()
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Good: Nested transactions with savepoints
|
|
444
|
+
suspend fun transferFunds(fromId: UUID, toId: UUID, amount: Long) {
|
|
445
|
+
newSuspendedTransaction {
|
|
446
|
+
val from = UserEntity.findById(fromId) ?: throw NotFoundException("User $fromId not found")
|
|
447
|
+
val to = UserEntity.findById(toId) ?: throw NotFoundException("User $toId not found")
|
|
448
|
+
|
|
449
|
+
// Debit
|
|
450
|
+
from.balance -= amount
|
|
451
|
+
// Credit
|
|
452
|
+
to.balance += amount
|
|
453
|
+
|
|
454
|
+
// Both succeed or both fail
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Transaction Isolation
|
|
460
|
+
|
|
461
|
+
```kotlin
|
|
462
|
+
suspend fun readCommittedQuery(): List<User> =
|
|
463
|
+
newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {
|
|
464
|
+
UserEntity.all().map { it.toModel() }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
suspend fun serializableOperation() {
|
|
468
|
+
newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_SERIALIZABLE) {
|
|
469
|
+
// Strictest isolation level for critical operations
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## Repository Pattern
|
|
475
|
+
|
|
476
|
+
### Interface Definition
|
|
477
|
+
|
|
478
|
+
```kotlin
|
|
479
|
+
interface UserRepository {
|
|
480
|
+
suspend fun findById(id: UUID): User?
|
|
481
|
+
suspend fun findByEmail(email: String): User?
|
|
482
|
+
suspend fun findAll(page: Int, limit: Int): Page<User>
|
|
483
|
+
suspend fun search(query: String): List<User>
|
|
484
|
+
suspend fun create(request: CreateUserRequest): User
|
|
485
|
+
suspend fun update(id: UUID, request: UpdateUserRequest): User?
|
|
486
|
+
suspend fun delete(id: UUID): Boolean
|
|
487
|
+
suspend fun count(): Long
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Exposed Implementation
|
|
492
|
+
|
|
493
|
+
```kotlin
|
|
494
|
+
class ExposedUserRepository(
|
|
495
|
+
private val database: Database,
|
|
496
|
+
) : UserRepository {
|
|
497
|
+
|
|
498
|
+
override suspend fun findById(id: UUID): User? =
|
|
499
|
+
newSuspendedTransaction(db = database) {
|
|
500
|
+
UsersTable.selectAll()
|
|
501
|
+
.where { UsersTable.id eq id }
|
|
502
|
+
.map { it.toUser() }
|
|
503
|
+
.singleOrNull()
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
override suspend fun findByEmail(email: String): User? =
|
|
507
|
+
newSuspendedTransaction(db = database) {
|
|
508
|
+
UsersTable.selectAll()
|
|
509
|
+
.where { UsersTable.email eq email }
|
|
510
|
+
.map { it.toUser() }
|
|
511
|
+
.singleOrNull()
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
override suspend fun findAll(page: Int, limit: Int): Page<User> =
|
|
515
|
+
newSuspendedTransaction(db = database) {
|
|
516
|
+
val total = UsersTable.selectAll().count()
|
|
517
|
+
val data = UsersTable.selectAll()
|
|
518
|
+
.orderBy(UsersTable.createdAt, SortOrder.DESC)
|
|
519
|
+
.limit(limit)
|
|
520
|
+
.offset(((page - 1) * limit).toLong())
|
|
521
|
+
.map { it.toUser() }
|
|
522
|
+
Page(data = data, total = total, page = page, limit = limit)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
override suspend fun search(query: String): List<User> =
|
|
526
|
+
newSuspendedTransaction(db = database) {
|
|
527
|
+
val sanitized = escapeLikePattern(query.lowercase())
|
|
528
|
+
UsersTable.selectAll()
|
|
529
|
+
.where {
|
|
530
|
+
(UsersTable.name.lowerCase() like "%${sanitized}%") or
|
|
531
|
+
(UsersTable.email.lowerCase() like "%${sanitized}%")
|
|
532
|
+
}
|
|
533
|
+
.orderBy(UsersTable.name)
|
|
534
|
+
.map { it.toUser() }
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
override suspend fun create(request: CreateUserRequest): User =
|
|
538
|
+
newSuspendedTransaction(db = database) {
|
|
539
|
+
UsersTable.insert {
|
|
540
|
+
it[name] = request.name
|
|
541
|
+
it[email] = request.email
|
|
542
|
+
it[role] = request.role
|
|
543
|
+
}.resultedValues!!.first().toUser()
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
override suspend fun update(id: UUID, request: UpdateUserRequest): User? =
|
|
547
|
+
newSuspendedTransaction(db = database) {
|
|
548
|
+
val updated = UsersTable.update({ UsersTable.id eq id }) {
|
|
549
|
+
request.name?.let { name -> it[UsersTable.name] = name }
|
|
550
|
+
request.email?.let { email -> it[UsersTable.email] = email }
|
|
551
|
+
it[updatedAt] = CurrentTimestampWithTimeZone
|
|
552
|
+
}
|
|
553
|
+
if (updated > 0) findById(id) else null
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
override suspend fun delete(id: UUID): Boolean =
|
|
557
|
+
newSuspendedTransaction(db = database) {
|
|
558
|
+
UsersTable.deleteWhere { UsersTable.id eq id } > 0
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
override suspend fun count(): Long =
|
|
562
|
+
newSuspendedTransaction(db = database) {
|
|
563
|
+
UsersTable.selectAll().count()
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private fun ResultRow.toUser() = User(
|
|
567
|
+
id = this[UsersTable.id].value,
|
|
568
|
+
name = this[UsersTable.name],
|
|
569
|
+
email = this[UsersTable.email],
|
|
570
|
+
role = this[UsersTable.role],
|
|
571
|
+
metadata = this[UsersTable.metadata],
|
|
572
|
+
createdAt = this[UsersTable.createdAt],
|
|
573
|
+
updatedAt = this[UsersTable.updatedAt],
|
|
574
|
+
)
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## JSON Columns
|
|
579
|
+
|
|
580
|
+
### JSONB with kotlinx.serialization
|
|
581
|
+
|
|
582
|
+
```kotlin
|
|
583
|
+
// Custom column type for JSONB
|
|
584
|
+
inline fun <reified T : Any> Table.jsonb(
|
|
585
|
+
name: String,
|
|
586
|
+
json: Json,
|
|
587
|
+
): Column<T> = registerColumn(name, object : ColumnType<T>() {
|
|
588
|
+
override fun sqlType() = "JSONB"
|
|
589
|
+
|
|
590
|
+
override fun valueFromDB(value: Any): T = when (value) {
|
|
591
|
+
is String -> json.decodeFromString(value)
|
|
592
|
+
is PGobject -> {
|
|
593
|
+
val jsonString = value.value
|
|
594
|
+
?: throw IllegalArgumentException("PGobject value is null for column '$name'")
|
|
595
|
+
json.decodeFromString(jsonString)
|
|
596
|
+
}
|
|
597
|
+
else -> throw IllegalArgumentException("Unexpected value: $value")
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
override fun notNullValueToDB(value: T): Any =
|
|
601
|
+
PGobject().apply {
|
|
602
|
+
type = "jsonb"
|
|
603
|
+
this.value = json.encodeToString(value)
|
|
604
|
+
}
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
// Usage in table
|
|
608
|
+
@Serializable
|
|
609
|
+
data class UserMetadata(
|
|
610
|
+
val preferences: Map<String, String> = emptyMap(),
|
|
611
|
+
val tags: List<String> = emptyList(),
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
object UsersTable : UUIDTable("users") {
|
|
615
|
+
val metadata = jsonb<UserMetadata>("metadata", Json.Default).nullable()
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## Testing with Exposed
|
|
620
|
+
|
|
621
|
+
### In-Memory Database for Tests
|
|
622
|
+
|
|
623
|
+
```kotlin
|
|
624
|
+
class UserRepositoryTest : FunSpec({
|
|
625
|
+
lateinit var database: Database
|
|
626
|
+
lateinit var repository: UserRepository
|
|
627
|
+
|
|
628
|
+
beforeSpec {
|
|
629
|
+
database = Database.connect(
|
|
630
|
+
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL",
|
|
631
|
+
driver = "org.h2.Driver",
|
|
632
|
+
)
|
|
633
|
+
transaction(database) {
|
|
634
|
+
SchemaUtils.create(UsersTable)
|
|
635
|
+
}
|
|
636
|
+
repository = ExposedUserRepository(database)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
beforeTest {
|
|
640
|
+
transaction(database) {
|
|
641
|
+
UsersTable.deleteAll()
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
test("create and find user") {
|
|
646
|
+
val user = repository.create(CreateUserRequest("Alice", "alice@example.com"))
|
|
647
|
+
|
|
648
|
+
user.name shouldBe "Alice"
|
|
649
|
+
user.email shouldBe "alice@example.com"
|
|
650
|
+
|
|
651
|
+
val found = repository.findById(user.id)
|
|
652
|
+
found shouldBe user
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
test("findByEmail returns null for unknown email") {
|
|
656
|
+
val result = repository.findByEmail("unknown@example.com")
|
|
657
|
+
result.shouldBeNull()
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
test("pagination works correctly") {
|
|
661
|
+
repeat(25) { i ->
|
|
662
|
+
repository.create(CreateUserRequest("User $i", "user$i@example.com"))
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
val page1 = repository.findAll(page = 1, limit = 10)
|
|
666
|
+
page1.data shouldHaveSize 10
|
|
667
|
+
page1.total shouldBe 25
|
|
668
|
+
page1.hasNext shouldBe true
|
|
669
|
+
|
|
670
|
+
val page3 = repository.findAll(page = 3, limit = 10)
|
|
671
|
+
page3.data shouldHaveSize 5
|
|
672
|
+
page3.hasNext shouldBe false
|
|
673
|
+
}
|
|
674
|
+
})
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
## Gradle Dependencies
|
|
678
|
+
|
|
679
|
+
```kotlin
|
|
680
|
+
// build.gradle.kts
|
|
681
|
+
dependencies {
|
|
682
|
+
// Exposed
|
|
683
|
+
implementation("org.jetbrains.exposed:exposed-core:1.0.0")
|
|
684
|
+
implementation("org.jetbrains.exposed:exposed-dao:1.0.0")
|
|
685
|
+
implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0")
|
|
686
|
+
implementation("org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0")
|
|
687
|
+
implementation("org.jetbrains.exposed:exposed-json:1.0.0")
|
|
688
|
+
|
|
689
|
+
// Database driver
|
|
690
|
+
implementation("org.postgresql:postgresql:42.7.5")
|
|
691
|
+
|
|
692
|
+
// Connection pooling
|
|
693
|
+
implementation("com.zaxxer:HikariCP:6.2.1")
|
|
694
|
+
|
|
695
|
+
// Migrations
|
|
696
|
+
implementation("org.flywaydb:flyway-core:10.22.0")
|
|
697
|
+
implementation("org.flywaydb:flyway-database-postgresql:10.22.0")
|
|
698
|
+
|
|
699
|
+
// Testing
|
|
700
|
+
testImplementation("com.h2database:h2:2.3.232")
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
## Quick Reference: Exposed Patterns
|
|
705
|
+
|
|
706
|
+
| Pattern | Description |
|
|
707
|
+
|---------|-------------|
|
|
708
|
+
| `object Table : UUIDTable("name")` | Define table with UUID primary key |
|
|
709
|
+
| `newSuspendedTransaction { }` | Coroutine-safe transaction block |
|
|
710
|
+
| `Table.selectAll().where { }` | Query with conditions |
|
|
711
|
+
| `Table.insertAndGetId { }` | Insert and return generated ID |
|
|
712
|
+
| `Table.update({ condition }) { }` | Update matching rows |
|
|
713
|
+
| `Table.deleteWhere { }` | Delete matching rows |
|
|
714
|
+
| `Table.batchInsert(items) { }` | Efficient bulk insert |
|
|
715
|
+
| `innerJoin` / `leftJoin` | Join tables |
|
|
716
|
+
| `orderBy` / `limit` / `offset` | Sort and paginate |
|
|
717
|
+
| `count()` / `sum()` / `avg()` | Aggregation functions |
|
|
718
|
+
|
|
719
|
+
**Remember**: Use the DSL style for simple queries and the DAO style when you need entity lifecycle management. Always use `newSuspendedTransaction` for coroutine support, and wrap database operations behind a repository interface for testability.
|