autoworkflow 3.1.5 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/analyze.md +19 -0
- package/.claude/commands/audit.md +26 -0
- package/.claude/commands/build.md +39 -0
- package/.claude/commands/commit.md +25 -0
- package/.claude/commands/fix.md +23 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/suggest.md +23 -0
- package/.claude/commands/verify.md +18 -0
- package/.claude/hooks/post-bash-router.sh +20 -0
- package/.claude/hooks/post-commit.sh +140 -0
- package/.claude/hooks/post-edit.sh +190 -17
- package/.claude/hooks/pre-edit.sh +221 -0
- package/.claude/hooks/session-check.sh +90 -0
- package/.claude/settings.json +56 -6
- package/.claude/settings.local.json +5 -1
- package/.claude/skills/actix.md +337 -0
- package/.claude/skills/alembic.md +504 -0
- package/.claude/skills/angular.md +237 -0
- package/.claude/skills/api-design.md +187 -0
- package/.claude/skills/aspnet-core.md +377 -0
- package/.claude/skills/astro.md +245 -0
- package/.claude/skills/auth-clerk.md +327 -0
- package/.claude/skills/auth-firebase.md +367 -0
- package/.claude/skills/auth-nextauth.md +359 -0
- package/.claude/skills/auth-supabase.md +368 -0
- package/.claude/skills/axum.md +386 -0
- package/.claude/skills/blazor.md +456 -0
- package/.claude/skills/chi.md +348 -0
- package/.claude/skills/code-review.md +133 -0
- package/.claude/skills/csharp.md +296 -0
- package/.claude/skills/css-modules.md +325 -0
- package/.claude/skills/cypress.md +343 -0
- package/.claude/skills/debugging.md +133 -0
- package/.claude/skills/diesel.md +392 -0
- package/.claude/skills/django.md +301 -0
- package/.claude/skills/docker.md +319 -0
- package/.claude/skills/doctrine.md +473 -0
- package/.claude/skills/documentation.md +182 -0
- package/.claude/skills/dotnet.md +409 -0
- package/.claude/skills/drizzle.md +293 -0
- package/.claude/skills/echo.md +321 -0
- package/.claude/skills/eloquent.md +256 -0
- package/.claude/skills/emotion.md +426 -0
- package/.claude/skills/entity-framework.md +370 -0
- package/.claude/skills/express.md +316 -0
- package/.claude/skills/fastapi.md +329 -0
- package/.claude/skills/fastify.md +299 -0
- package/.claude/skills/fiber.md +315 -0
- package/.claude/skills/flask.md +322 -0
- package/.claude/skills/gin.md +342 -0
- package/.claude/skills/git.md +116 -0
- package/.claude/skills/github-actions.md +353 -0
- package/.claude/skills/go.md +377 -0
- package/.claude/skills/gorm.md +409 -0
- package/.claude/skills/graphql.md +478 -0
- package/.claude/skills/hibernate.md +379 -0
- package/.claude/skills/hono.md +306 -0
- package/.claude/skills/java.md +400 -0
- package/.claude/skills/jest.md +313 -0
- package/.claude/skills/jpa.md +282 -0
- package/.claude/skills/kotlin.md +347 -0
- package/.claude/skills/kubernetes.md +363 -0
- package/.claude/skills/laravel.md +414 -0
- package/.claude/skills/mcp-browser.md +320 -0
- package/.claude/skills/mcp-database.md +219 -0
- package/.claude/skills/mcp-fetch.md +241 -0
- package/.claude/skills/mcp-filesystem.md +204 -0
- package/.claude/skills/mcp-github.md +217 -0
- package/.claude/skills/mcp-memory.md +240 -0
- package/.claude/skills/mcp-search.md +218 -0
- package/.claude/skills/mcp-slack.md +262 -0
- package/.claude/skills/micronaut.md +388 -0
- package/.claude/skills/mongodb.md +319 -0
- package/.claude/skills/mongoose.md +355 -0
- package/.claude/skills/mysql.md +281 -0
- package/.claude/skills/nestjs.md +335 -0
- package/.claude/skills/nextjs-app-router.md +260 -0
- package/.claude/skills/nextjs-pages.md +172 -0
- package/.claude/skills/nuxt.md +202 -0
- package/.claude/skills/openapi.md +489 -0
- package/.claude/skills/performance.md +199 -0
- package/.claude/skills/php.md +398 -0
- package/.claude/skills/playwright.md +371 -0
- package/.claude/skills/postgresql.md +257 -0
- package/.claude/skills/prisma.md +293 -0
- package/.claude/skills/pydantic.md +304 -0
- package/.claude/skills/pytest.md +313 -0
- package/.claude/skills/python.md +272 -0
- package/.claude/skills/quarkus.md +377 -0
- package/.claude/skills/react.md +230 -0
- package/.claude/skills/redis.md +391 -0
- package/.claude/skills/refactoring.md +143 -0
- package/.claude/skills/remix.md +246 -0
- package/.claude/skills/rest-api.md +490 -0
- package/.claude/skills/rocket.md +366 -0
- package/.claude/skills/rust.md +341 -0
- package/.claude/skills/sass.md +380 -0
- package/.claude/skills/sea-orm.md +382 -0
- package/.claude/skills/security.md +167 -0
- package/.claude/skills/sequelize.md +395 -0
- package/.claude/skills/spring-boot.md +416 -0
- package/.claude/skills/sqlalchemy.md +269 -0
- package/.claude/skills/sqlx-rust.md +408 -0
- package/.claude/skills/state-jotai.md +346 -0
- package/.claude/skills/state-mobx.md +353 -0
- package/.claude/skills/state-pinia.md +431 -0
- package/.claude/skills/state-redux.md +337 -0
- package/.claude/skills/state-tanstack-query.md +434 -0
- package/.claude/skills/state-zustand.md +340 -0
- package/.claude/skills/styled-components.md +403 -0
- package/.claude/skills/svelte.md +238 -0
- package/.claude/skills/sveltekit.md +207 -0
- package/.claude/skills/symfony.md +437 -0
- package/.claude/skills/tailwind.md +279 -0
- package/.claude/skills/terraform.md +394 -0
- package/.claude/skills/testing-library.md +371 -0
- package/.claude/skills/trpc.md +426 -0
- package/.claude/skills/typeorm.md +368 -0
- package/.claude/skills/vitest.md +330 -0
- package/.claude/skills/vue.md +202 -0
- package/.claude/skills/warp.md +365 -0
- package/README.md +163 -52
- package/package.json +1 -1
- package/system/triggers.md +256 -17
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# JPA Skill (Spring Data JPA)
|
|
2
|
+
|
|
3
|
+
## Repository Interface
|
|
4
|
+
\`\`\`java
|
|
5
|
+
public interface UserRepository extends JpaRepository<User, String> {
|
|
6
|
+
|
|
7
|
+
// Derived query methods
|
|
8
|
+
Optional<User> findByEmail(String email);
|
|
9
|
+
|
|
10
|
+
List<User> findByIsActiveTrueOrderByCreatedAtDesc();
|
|
11
|
+
|
|
12
|
+
boolean existsByEmail(String email);
|
|
13
|
+
|
|
14
|
+
long countByIsActiveTrue();
|
|
15
|
+
|
|
16
|
+
// Query methods with @Query
|
|
17
|
+
@Query("SELECT u FROM User u WHERE u.isActive = true")
|
|
18
|
+
List<User> findActiveUsers();
|
|
19
|
+
|
|
20
|
+
@Query("SELECT u FROM User u LEFT JOIN FETCH u.posts WHERE u.id = :id")
|
|
21
|
+
Optional<User> findByIdWithPosts(@Param("id") String id);
|
|
22
|
+
|
|
23
|
+
@Query(value = "SELECT * FROM users WHERE email ILIKE %:query%", nativeQuery = true)
|
|
24
|
+
List<User> searchByEmail(@Param("query") String query);
|
|
25
|
+
|
|
26
|
+
// Modifying queries
|
|
27
|
+
@Modifying
|
|
28
|
+
@Query("UPDATE User u SET u.isActive = false WHERE u.id = :id")
|
|
29
|
+
int deactivateUser(@Param("id") String id);
|
|
30
|
+
|
|
31
|
+
@Modifying
|
|
32
|
+
@Query("DELETE FROM User u WHERE u.isActive = false AND u.createdAt < :date")
|
|
33
|
+
int deleteInactiveOlderThan(@Param("date") Instant date);
|
|
34
|
+
|
|
35
|
+
// Pagination
|
|
36
|
+
Page<User> findByIsActiveTrue(Pageable pageable);
|
|
37
|
+
|
|
38
|
+
Slice<User> findByRolesName(String roleName, Pageable pageable);
|
|
39
|
+
}
|
|
40
|
+
\`\`\`
|
|
41
|
+
|
|
42
|
+
## Specifications (Dynamic Queries)
|
|
43
|
+
\`\`\`java
|
|
44
|
+
// Repository with Specification support
|
|
45
|
+
public interface UserRepository extends
|
|
46
|
+
JpaRepository<User, String>,
|
|
47
|
+
JpaSpecificationExecutor<User> {
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Specification definitions
|
|
51
|
+
public class UserSpecifications {
|
|
52
|
+
|
|
53
|
+
public static Specification<User> hasEmail(String email) {
|
|
54
|
+
return (root, query, cb) -> email == null ? null :
|
|
55
|
+
cb.equal(root.get("email"), email);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public static Specification<User> emailContains(String search) {
|
|
59
|
+
return (root, query, cb) -> search == null ? null :
|
|
60
|
+
cb.like(cb.lower(root.get("email")), "%" + search.toLowerCase() + "%");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public static Specification<User> isActive() {
|
|
64
|
+
return (root, query, cb) -> cb.isTrue(root.get("isActive"));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public static Specification<User> createdAfter(Instant date) {
|
|
68
|
+
return (root, query, cb) -> date == null ? null :
|
|
69
|
+
cb.greaterThan(root.get("createdAt"), date);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public static Specification<User> hasRole(String roleName) {
|
|
73
|
+
return (root, query, cb) -> {
|
|
74
|
+
if (roleName == null) return null;
|
|
75
|
+
Join<User, Role> roles = root.join("roles");
|
|
76
|
+
return cb.equal(roles.get("name"), roleName);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Usage in service
|
|
82
|
+
public Page<User> searchUsers(UserSearchCriteria criteria, Pageable pageable) {
|
|
83
|
+
Specification<User> spec = Specification
|
|
84
|
+
.where(emailContains(criteria.getEmail()))
|
|
85
|
+
.and(isActive())
|
|
86
|
+
.and(createdAfter(criteria.getCreatedAfter()))
|
|
87
|
+
.and(hasRole(criteria.getRole()));
|
|
88
|
+
|
|
89
|
+
return userRepository.findAll(spec, pageable);
|
|
90
|
+
}
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
## Projections
|
|
94
|
+
\`\`\`java
|
|
95
|
+
// Interface-based projection (closed)
|
|
96
|
+
public interface UserSummary {
|
|
97
|
+
String getId();
|
|
98
|
+
String getEmail();
|
|
99
|
+
String getName();
|
|
100
|
+
|
|
101
|
+
// Computed value
|
|
102
|
+
@Value("#{\${target.firstName} + ' ' + \${target.lastName}}")
|
|
103
|
+
String getFullName();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Class-based projection (DTO)
|
|
107
|
+
public record UserDto(String id, String email, String name) {}
|
|
108
|
+
|
|
109
|
+
// Dynamic projection
|
|
110
|
+
public interface UserRepository extends JpaRepository<User, String> {
|
|
111
|
+
|
|
112
|
+
// Returns different projections based on type parameter
|
|
113
|
+
<T> Optional<T> findById(String id, Class<T> type);
|
|
114
|
+
|
|
115
|
+
<T> List<T> findByIsActiveTrue(Class<T> type);
|
|
116
|
+
|
|
117
|
+
// Interface projection
|
|
118
|
+
List<UserSummary> findAllProjectedBy();
|
|
119
|
+
|
|
120
|
+
// DTO projection with JPQL
|
|
121
|
+
@Query("SELECT new com.example.dto.UserDto(u.id, u.email, u.name) FROM User u WHERE u.isActive = true")
|
|
122
|
+
List<UserDto> findActiveUserDtos();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Usage
|
|
126
|
+
UserSummary summary = userRepository.findById("123", UserSummary.class).orElseThrow();
|
|
127
|
+
UserDto dto = userRepository.findById("123", UserDto.class).orElseThrow();
|
|
128
|
+
\`\`\`
|
|
129
|
+
|
|
130
|
+
## Auditing
|
|
131
|
+
\`\`\`java
|
|
132
|
+
// Enable auditing
|
|
133
|
+
@Configuration
|
|
134
|
+
@EnableJpaAuditing
|
|
135
|
+
public class JpaConfig {
|
|
136
|
+
|
|
137
|
+
@Bean
|
|
138
|
+
public AuditorAware<String> auditorProvider() {
|
|
139
|
+
return () -> Optional.ofNullable(SecurityContextHolder.getContext())
|
|
140
|
+
.map(SecurityContext::getAuthentication)
|
|
141
|
+
.filter(Authentication::isAuthenticated)
|
|
142
|
+
.map(Authentication::getName);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Auditable base entity
|
|
147
|
+
@MappedSuperclass
|
|
148
|
+
@EntityListeners(AuditingEntityListener.class)
|
|
149
|
+
public abstract class AuditableEntity {
|
|
150
|
+
|
|
151
|
+
@CreatedDate
|
|
152
|
+
@Column(name = "created_at", nullable = false, updatable = false)
|
|
153
|
+
private Instant createdAt;
|
|
154
|
+
|
|
155
|
+
@LastModifiedDate
|
|
156
|
+
@Column(name = "updated_at")
|
|
157
|
+
private Instant updatedAt;
|
|
158
|
+
|
|
159
|
+
@CreatedBy
|
|
160
|
+
@Column(name = "created_by", updatable = false)
|
|
161
|
+
private String createdBy;
|
|
162
|
+
|
|
163
|
+
@LastModifiedBy
|
|
164
|
+
@Column(name = "updated_by")
|
|
165
|
+
private String updatedBy;
|
|
166
|
+
|
|
167
|
+
// Getters...
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Entity extending auditable
|
|
171
|
+
@Entity
|
|
172
|
+
public class User extends AuditableEntity {
|
|
173
|
+
@Id
|
|
174
|
+
@GeneratedValue(strategy = GenerationType.UUID)
|
|
175
|
+
private String id;
|
|
176
|
+
|
|
177
|
+
private String email;
|
|
178
|
+
private String name;
|
|
179
|
+
}
|
|
180
|
+
\`\`\`
|
|
181
|
+
|
|
182
|
+
## Query by Example
|
|
183
|
+
\`\`\`java
|
|
184
|
+
public interface UserRepository extends
|
|
185
|
+
JpaRepository<User, String>,
|
|
186
|
+
QueryByExampleExecutor<User> {
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Usage
|
|
190
|
+
User probe = new User();
|
|
191
|
+
probe.setIsActive(true);
|
|
192
|
+
probe.setEmail("@example.com");
|
|
193
|
+
|
|
194
|
+
ExampleMatcher matcher = ExampleMatcher.matching()
|
|
195
|
+
.withStringMatcher(ExampleMatcher.StringMatcher.ENDING)
|
|
196
|
+
.withIgnoreCase()
|
|
197
|
+
.withIgnoreNullValues();
|
|
198
|
+
|
|
199
|
+
Example<User> example = Example.of(probe, matcher);
|
|
200
|
+
|
|
201
|
+
List<User> users = userRepository.findAll(example);
|
|
202
|
+
\`\`\`
|
|
203
|
+
|
|
204
|
+
## Custom Repository Implementation
|
|
205
|
+
\`\`\`java
|
|
206
|
+
// Custom repository interface
|
|
207
|
+
public interface UserRepositoryCustom {
|
|
208
|
+
List<User> findUsersWithComplexCriteria(UserSearchCriteria criteria);
|
|
209
|
+
void batchUpdateStatus(List<String> ids, boolean isActive);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Implementation
|
|
213
|
+
public class UserRepositoryImpl implements UserRepositoryCustom {
|
|
214
|
+
|
|
215
|
+
@PersistenceContext
|
|
216
|
+
private EntityManager em;
|
|
217
|
+
|
|
218
|
+
@Override
|
|
219
|
+
public List<User> findUsersWithComplexCriteria(UserSearchCriteria criteria) {
|
|
220
|
+
CriteriaBuilder cb = em.getCriteriaBuilder();
|
|
221
|
+
CriteriaQuery<User> cq = cb.createQuery(User.class);
|
|
222
|
+
Root<User> user = cq.from(User.class);
|
|
223
|
+
|
|
224
|
+
List<Predicate> predicates = new ArrayList<>();
|
|
225
|
+
// Build predicates...
|
|
226
|
+
|
|
227
|
+
cq.where(predicates.toArray(new Predicate[0]));
|
|
228
|
+
return em.createQuery(cq).getResultList();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@Override
|
|
232
|
+
@Transactional
|
|
233
|
+
public void batchUpdateStatus(List<String> ids, boolean isActive) {
|
|
234
|
+
em.createQuery("UPDATE User u SET u.isActive = :status WHERE u.id IN :ids")
|
|
235
|
+
.setParameter("status", isActive)
|
|
236
|
+
.setParameter("ids", ids)
|
|
237
|
+
.executeUpdate();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Main repository extends both
|
|
242
|
+
public interface UserRepository extends
|
|
243
|
+
JpaRepository<User, String>,
|
|
244
|
+
UserRepositoryCustom {
|
|
245
|
+
}
|
|
246
|
+
\`\`\`
|
|
247
|
+
|
|
248
|
+
## Stream Results for Large Datasets
|
|
249
|
+
\`\`\`java
|
|
250
|
+
public interface UserRepository extends JpaRepository<User, String> {
|
|
251
|
+
|
|
252
|
+
@Query("SELECT u FROM User u WHERE u.isActive = true")
|
|
253
|
+
Stream<User> findAllActiveAsStream();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Usage - must be in transaction
|
|
257
|
+
@Transactional(readOnly = true)
|
|
258
|
+
public void processAllUsers() {
|
|
259
|
+
try (Stream<User> stream = userRepository.findAllActiveAsStream()) {
|
|
260
|
+
stream.forEach(user -> {
|
|
261
|
+
// Process each user
|
|
262
|
+
// EntityManager clears automatically with @Modifying(clearAutomatically = true)
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
\`\`\`
|
|
267
|
+
|
|
268
|
+
## ✅ DO
|
|
269
|
+
- Use derived query methods for simple queries
|
|
270
|
+
- Use \`@Query\` for complex queries
|
|
271
|
+
- Use Specifications for dynamic queries
|
|
272
|
+
- Use projections to fetch only needed fields
|
|
273
|
+
- Use \`@Transactional(readOnly = true)\` for read operations
|
|
274
|
+
- Use pagination for large result sets
|
|
275
|
+
- Use \`@EntityGraph\` or \`JOIN FETCH\` to avoid N+1
|
|
276
|
+
|
|
277
|
+
## ❌ DON'T
|
|
278
|
+
- Don't return entities directly from controllers (use DTOs)
|
|
279
|
+
- Don't use \`findAll()\` without pagination on large tables
|
|
280
|
+
- Don't forget \`@Modifying\` on UPDATE/DELETE queries
|
|
281
|
+
- Don't use \`@Query\` with \`nativeQuery=true\` unless necessary
|
|
282
|
+
- Don't ignore the N+1 problem - use eager fetching strategically
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Kotlin Skill
|
|
2
|
+
|
|
3
|
+
## Data Classes and Records
|
|
4
|
+
\`\`\`kotlin
|
|
5
|
+
// Immutable data class
|
|
6
|
+
data class User(
|
|
7
|
+
val id: String,
|
|
8
|
+
val email: String,
|
|
9
|
+
val name: String,
|
|
10
|
+
val isActive: Boolean = true,
|
|
11
|
+
val createdAt: Instant = Instant.now()
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// With validation in init block
|
|
15
|
+
data class Email(val value: String) {
|
|
16
|
+
init {
|
|
17
|
+
require(value.contains("@")) { "Invalid email format" }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Copy with modifications
|
|
22
|
+
val updatedUser = user.copy(name = "New Name", isActive = false)
|
|
23
|
+
|
|
24
|
+
// Destructuring
|
|
25
|
+
val (id, email, name) = user
|
|
26
|
+
users.forEach { (id, email) -> println("$id: $email") }
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
## Null Safety
|
|
30
|
+
\`\`\`kotlin
|
|
31
|
+
// Nullable types
|
|
32
|
+
var name: String? = null
|
|
33
|
+
|
|
34
|
+
// Safe call operator
|
|
35
|
+
val length = name?.length // Returns null if name is null
|
|
36
|
+
|
|
37
|
+
// Elvis operator
|
|
38
|
+
val displayName = user.name ?: "Anonymous"
|
|
39
|
+
val email = user.email ?: throw IllegalStateException("Email required")
|
|
40
|
+
|
|
41
|
+
// Safe cast
|
|
42
|
+
val result = obj as? String // Returns null if cast fails
|
|
43
|
+
|
|
44
|
+
// Not-null assertion (use sparingly!)
|
|
45
|
+
val length = name!!.length // Throws NPE if null
|
|
46
|
+
|
|
47
|
+
// let for null checks
|
|
48
|
+
user?.let {
|
|
49
|
+
sendEmail(it.email)
|
|
50
|
+
logAction(it.id)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Early return with Elvis
|
|
54
|
+
fun process(user: User?): Result {
|
|
55
|
+
val u = user ?: return Result.failure("User required")
|
|
56
|
+
return processUser(u)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Platform types (Java interop)
|
|
60
|
+
// Use @Nullable/@NotNull annotations or treat as nullable
|
|
61
|
+
fun handleJavaResult(result: String?) {
|
|
62
|
+
result?.let { process(it) }
|
|
63
|
+
}
|
|
64
|
+
\`\`\`
|
|
65
|
+
|
|
66
|
+
## Sealed Classes and When
|
|
67
|
+
\`\`\`kotlin
|
|
68
|
+
// Sealed class for restricted hierarchies
|
|
69
|
+
sealed class Result<out T> {
|
|
70
|
+
data class Success<T>(val data: T) : Result<T>()
|
|
71
|
+
data class Error(val message: String, val cause: Throwable? = null) : Result<Nothing>()
|
|
72
|
+
data object Loading : Result<Nothing>()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Exhaustive when (compiler ensures all cases handled)
|
|
76
|
+
fun handleResult(result: Result<User>): String = when (result) {
|
|
77
|
+
is Result.Success -> "Got user: \${result.data.name}"
|
|
78
|
+
is Result.Error -> "Error: \${result.message}"
|
|
79
|
+
is Result.Loading -> "Loading..."
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sealed interface
|
|
83
|
+
sealed interface ApiResponse {
|
|
84
|
+
data class Success(val body: String) : ApiResponse
|
|
85
|
+
data class Error(val code: Int, val message: String) : ApiResponse
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// When with complex conditions
|
|
89
|
+
fun describe(user: User) = when {
|
|
90
|
+
user.isActive && user.roles.contains("ADMIN") -> "Active Admin"
|
|
91
|
+
user.isActive -> "Active User"
|
|
92
|
+
else -> "Inactive"
|
|
93
|
+
}
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
96
|
+
## Scope Functions
|
|
97
|
+
\`\`\`kotlin
|
|
98
|
+
// let - transform nullable, use 'it'
|
|
99
|
+
val email = user?.email?.let { validateEmail(it) }
|
|
100
|
+
|
|
101
|
+
// run - execute block with 'this', return result
|
|
102
|
+
val result = user.run {
|
|
103
|
+
validate()
|
|
104
|
+
save()
|
|
105
|
+
toResponse()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// apply - configure object, return 'this'
|
|
109
|
+
val user = User().apply {
|
|
110
|
+
email = "test@example.com"
|
|
111
|
+
name = "Test"
|
|
112
|
+
isActive = true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// also - side effects, return 'this'
|
|
116
|
+
val user = createUser()
|
|
117
|
+
.also { log.info("Created user: \${it.id}") }
|
|
118
|
+
.also { metrics.increment("users.created") }
|
|
119
|
+
|
|
120
|
+
// with - operate on object, return result
|
|
121
|
+
val description = with(user) {
|
|
122
|
+
"User $name ($email) - Active: $isActive"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// takeIf / takeUnless
|
|
126
|
+
val activeUser = user.takeIf { it.isActive }
|
|
127
|
+
val inactiveUser = user.takeUnless { it.isActive }
|
|
128
|
+
|
|
129
|
+
// Chaining
|
|
130
|
+
val result = input
|
|
131
|
+
?.takeIf { it.isNotBlank() }
|
|
132
|
+
?.let { parse(it) }
|
|
133
|
+
?.also { validate(it) }
|
|
134
|
+
?: defaultValue
|
|
135
|
+
\`\`\`
|
|
136
|
+
|
|
137
|
+
## Extension Functions
|
|
138
|
+
\`\`\`kotlin
|
|
139
|
+
// Add functions to existing types
|
|
140
|
+
fun String.toSlug(): String =
|
|
141
|
+
lowercase()
|
|
142
|
+
.replace(Regex("[^a-z0-9\\\\s-]"), "")
|
|
143
|
+
.replace(Regex("\\\\s+"), "-")
|
|
144
|
+
|
|
145
|
+
fun String.isValidEmail(): Boolean =
|
|
146
|
+
matches(Regex("^[A-Za-z0-9+_.-]+@(.+)$"))
|
|
147
|
+
|
|
148
|
+
// Extension on nullable type
|
|
149
|
+
fun String?.orEmpty(): String = this ?: ""
|
|
150
|
+
|
|
151
|
+
fun <T> List<T>?.orEmpty(): List<T> = this ?: emptyList()
|
|
152
|
+
|
|
153
|
+
// Extension properties
|
|
154
|
+
val String.firstWord: String
|
|
155
|
+
get() = split(" ").first()
|
|
156
|
+
|
|
157
|
+
// Generic extensions
|
|
158
|
+
fun <T> T.log(tag: String = "DEBUG"): T = also {
|
|
159
|
+
println("[$tag] $it")
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Scoped extensions
|
|
163
|
+
class UserService {
|
|
164
|
+
// Only available within UserService
|
|
165
|
+
private fun User.toResponse() = UserResponse(id, email, name)
|
|
166
|
+
|
|
167
|
+
fun getUser(id: String): UserResponse =
|
|
168
|
+
repository.findById(id).toResponse()
|
|
169
|
+
}
|
|
170
|
+
\`\`\`
|
|
171
|
+
|
|
172
|
+
## Collections
|
|
173
|
+
\`\`\`kotlin
|
|
174
|
+
// Immutable by default
|
|
175
|
+
val list = listOf(1, 2, 3)
|
|
176
|
+
val set = setOf("a", "b", "c")
|
|
177
|
+
val map = mapOf("key1" to "value1", "key2" to "value2")
|
|
178
|
+
|
|
179
|
+
// Mutable when needed
|
|
180
|
+
val mutableList = mutableListOf(1, 2, 3)
|
|
181
|
+
mutableList.add(4)
|
|
182
|
+
|
|
183
|
+
// Transformations
|
|
184
|
+
val emails = users
|
|
185
|
+
.filter { it.isActive }
|
|
186
|
+
.map { it.email }
|
|
187
|
+
.distinct()
|
|
188
|
+
.sorted()
|
|
189
|
+
|
|
190
|
+
// Grouping
|
|
191
|
+
val byDomain = users.groupBy { it.email.substringAfter("@") }
|
|
192
|
+
|
|
193
|
+
// Aggregation
|
|
194
|
+
val totalAge = users.sumOf { it.age }
|
|
195
|
+
val averageAge = users.map { it.age }.average()
|
|
196
|
+
val oldest = users.maxByOrNull { it.age }
|
|
197
|
+
|
|
198
|
+
// Partition
|
|
199
|
+
val (active, inactive) = users.partition { it.isActive }
|
|
200
|
+
|
|
201
|
+
// Associate
|
|
202
|
+
val userById = users.associateBy { it.id }
|
|
203
|
+
val idToEmail = users.associate { it.id to it.email }
|
|
204
|
+
|
|
205
|
+
// Sequences for large collections (lazy evaluation)
|
|
206
|
+
val result = users.asSequence()
|
|
207
|
+
.filter { it.isActive }
|
|
208
|
+
.map { it.email }
|
|
209
|
+
.take(10)
|
|
210
|
+
.toList()
|
|
211
|
+
|
|
212
|
+
// Null-safe operations
|
|
213
|
+
val firstActive = users.firstOrNull { it.isActive }
|
|
214
|
+
val emails = users.mapNotNull { it.email } // Filters nulls
|
|
215
|
+
\`\`\`
|
|
216
|
+
|
|
217
|
+
## Coroutines
|
|
218
|
+
\`\`\`kotlin
|
|
219
|
+
import kotlinx.coroutines.*
|
|
220
|
+
|
|
221
|
+
// Suspend function
|
|
222
|
+
suspend fun fetchUser(id: String): User = withContext(Dispatchers.IO) {
|
|
223
|
+
repository.findById(id)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Launching coroutines
|
|
227
|
+
fun main() = runBlocking {
|
|
228
|
+
val job = launch {
|
|
229
|
+
delay(1000)
|
|
230
|
+
println("World!")
|
|
231
|
+
}
|
|
232
|
+
println("Hello,")
|
|
233
|
+
job.join()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Async for concurrent operations
|
|
237
|
+
suspend fun fetchUserWithPosts(userId: String): UserWithPosts = coroutineScope {
|
|
238
|
+
val userDeferred = async { fetchUser(userId) }
|
|
239
|
+
val postsDeferred = async { fetchPosts(userId) }
|
|
240
|
+
|
|
241
|
+
UserWithPosts(
|
|
242
|
+
user = userDeferred.await(),
|
|
243
|
+
posts = postsDeferred.await()
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Error handling
|
|
248
|
+
suspend fun safeFetch(id: String): Result<User> = try {
|
|
249
|
+
Result.success(fetchUser(id))
|
|
250
|
+
} catch (e: Exception) {
|
|
251
|
+
Result.failure(e)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Flow for streams
|
|
255
|
+
fun observeUsers(): Flow<User> = flow {
|
|
256
|
+
while (true) {
|
|
257
|
+
emit(fetchLatestUser())
|
|
258
|
+
delay(5000)
|
|
259
|
+
}
|
|
260
|
+
}.flowOn(Dispatchers.IO)
|
|
261
|
+
|
|
262
|
+
// Collecting flows
|
|
263
|
+
suspend fun collectUsers() {
|
|
264
|
+
observeUsers()
|
|
265
|
+
.filter { it.isActive }
|
|
266
|
+
.map { it.email }
|
|
267
|
+
.catch { e -> log.error("Error", e) }
|
|
268
|
+
.collect { email -> sendNotification(email) }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// StateFlow and SharedFlow
|
|
272
|
+
class UserViewModel {
|
|
273
|
+
private val _users = MutableStateFlow<List<User>>(emptyList())
|
|
274
|
+
val users: StateFlow<List<User>> = _users.asStateFlow()
|
|
275
|
+
|
|
276
|
+
fun loadUsers() {
|
|
277
|
+
viewModelScope.launch {
|
|
278
|
+
_users.value = repository.getUsers()
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
\`\`\`
|
|
283
|
+
|
|
284
|
+
## Spring Boot with Kotlin
|
|
285
|
+
\`\`\`kotlin
|
|
286
|
+
@RestController
|
|
287
|
+
@RequestMapping("/api/v1/users")
|
|
288
|
+
class UserController(
|
|
289
|
+
private val userService: UserService // Constructor injection
|
|
290
|
+
) {
|
|
291
|
+
@GetMapping
|
|
292
|
+
suspend fun listUsers(): List<UserResponse> =
|
|
293
|
+
userService.findAll()
|
|
294
|
+
|
|
295
|
+
@GetMapping("/{id}")
|
|
296
|
+
suspend fun getUser(@PathVariable id: String): UserResponse =
|
|
297
|
+
userService.findById(id)
|
|
298
|
+
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
|
299
|
+
|
|
300
|
+
@PostMapping
|
|
301
|
+
suspend fun createUser(@Valid @RequestBody request: CreateUserRequest): ResponseEntity<UserResponse> {
|
|
302
|
+
val user = userService.create(request)
|
|
303
|
+
return ResponseEntity.created(URI.create("/api/v1/users/\${user.id}"))
|
|
304
|
+
.body(user)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
@Service
|
|
309
|
+
class UserService(
|
|
310
|
+
private val userRepository: UserRepository
|
|
311
|
+
) {
|
|
312
|
+
suspend fun findAll(): List<UserResponse> =
|
|
313
|
+
userRepository.findAll().map { it.toResponse() }
|
|
314
|
+
|
|
315
|
+
suspend fun findById(id: String): UserResponse? =
|
|
316
|
+
userRepository.findById(id)?.toResponse()
|
|
317
|
+
|
|
318
|
+
@Transactional
|
|
319
|
+
suspend fun create(request: CreateUserRequest): UserResponse {
|
|
320
|
+
val user = User(
|
|
321
|
+
email = request.email,
|
|
322
|
+
name = request.name,
|
|
323
|
+
passwordHash = passwordEncoder.encode(request.password)
|
|
324
|
+
)
|
|
325
|
+
return userRepository.save(user).toResponse()
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private fun User.toResponse() = UserResponse(id, email, name, isActive)
|
|
329
|
+
}
|
|
330
|
+
\`\`\`
|
|
331
|
+
|
|
332
|
+
## ✅ DO
|
|
333
|
+
- Use \`val\` by default, \`var\` only when necessary
|
|
334
|
+
- Use data classes for DTOs and value objects
|
|
335
|
+
- Use sealed classes for restricted type hierarchies
|
|
336
|
+
- Use scope functions appropriately (let, run, apply, also, with)
|
|
337
|
+
- Use \`?.let\` instead of null checks
|
|
338
|
+
- Use extension functions to enhance readability
|
|
339
|
+
- Use coroutines for async operations
|
|
340
|
+
|
|
341
|
+
## ❌ DON'T
|
|
342
|
+
- Don't use \`!!\` without good reason (prefer safe calls)
|
|
343
|
+
- Don't use mutable collections when immutable suffice
|
|
344
|
+
- Don't use \`lateinit\` when nullable is appropriate
|
|
345
|
+
- Don't ignore nullability in Java interop
|
|
346
|
+
- Don't block coroutines with blocking calls (use \`withContext(Dispatchers.IO)\`)
|
|
347
|
+
- Don't use \`GlobalScope\` (prefer structured concurrency)
|