diff --git a/README.md b/README.md
index de2d64630..50b980055 100644
--- a/README.md
+++ b/README.md
@@ -57,21 +57,20 @@ data class User(
@FK val city: City
) : Entity
-// DSL—query nested properties like city.name in one go
-val users = orm.findAll { User_.city.name eq "Sunnyvale" }
+// Type-safe predicates — query nested properties like city.name in one go
+val users = orm.findAll(User_.city.name eq "Sunnyvale")
-// Custom repository—inherits all CRUD operations, add your own queries
+// Custom repository — inherits all CRUD operations, add your own queries
interface UserRepository : EntityRepository {
- fun findByCityName(name: String) = findAll { User_.city.name eq name }
+ fun findByCityName(name: String) = findAll(User_.city.name eq name)
}
val users = userRepository.findByCityName("Sunnyvale")
-// Query Builder for more complex operations
-val users = orm.entity(User::class)
- .select()
- .where(User_.city.name eq "Sunnyvale")
- .orderBy(User_.name)
- .resultList
+// Block DSL — build queries with where, orderBy, joins, pagination
+val users = userRepository.select {
+ where(User_.city.name eq "Sunnyvale")
+ orderBy(User_.name)
+}.resultList
// SQL Template for full control; parameterized by default, SQL injection safe
val users = orm.query { """
@@ -85,7 +84,7 @@ Full coroutine support with `Flow` for streaming and programmatic transactions:
```kotlin
// Streaming with Flow
-val users: Flow = orm.entity(User::class).selectAll()
+val users: Flow = orm.entity(User::class).select().resultFlow
users.collect { user -> println(user.name) }
// Programmatic transactions
diff --git a/docs/common-patterns.md b/docs/common-patterns.md
index 2a88a6ad5..6c013887c 100644
--- a/docs/common-patterns.md
+++ b/docs/common-patterns.md
@@ -75,7 +75,7 @@ data class OrderWithItems(
fun findOrderWithItems(orderId: Long): OrderWithItems? {
val order = orm.entity(Order::class).findById(orderId) ?: return null
- val lineItems = orm.entity(LineItem::class).findAll { LineItem_.order eq order }
+ val lineItems = orm.entity(LineItem::class).findAll(LineItem_.order eq order)
return OrderWithItems(order, lineItems)
}
```
@@ -261,11 +261,11 @@ interface CustomerRepository : EntityRepository {
/** Find only non-deleted customers. */
fun findActive(): List =
- findAll { Customer_.deletedAt.isNull() }
+ findAll(Customer_.deletedAt.isNull())
/** Find a non-deleted customer by ID. */
fun findActiveOrNull(customerId: Int): Customer? =
- find { (Customer_.id eq customerId) and Customer_.deletedAt.isNull() }
+ find((Customer_.id eq customerId) and Customer_.deletedAt.isNull())
/** Soft-delete a customer by setting the deletedAt timestamp. */
fun softDelete(customer: Customer): Customer {
diff --git a/docs/dirty-checking.md b/docs/dirty-checking.md
index bf6f96d5e..f78987927 100644
--- a/docs/dirty-checking.md
+++ b/docs/dirty-checking.md
@@ -101,7 +101,7 @@ The selected update mode controls:
In `OFF` mode, Storm bypasses dirty checking entirely. Every call to `update()` generates a full UPDATE statement that writes all columns, regardless of whether values have actually changed.
```kotlin
-val user = orm.find { User_.id eq 1 }
+val user = orm.find(User_.id eq 1)
val updatedUser = user.copy(name = "New Name")
// Always generates: UPDATE user SET email=?, name=?, city_id=? WHERE id=?
@@ -144,7 +144,7 @@ userRepository.update(users.map { processUser(it) })
- If **any field changed**: A full-row UPDATE is executed (all columns are written)
```kotlin
-val user = orm.get { User_.id eq 1 } // Storm observes: {id=1, email="a@b.com", name="Alice"}
+val user = orm.get(User_.id eq 1) // Storm observes: {id=1, email="a@b.com", name="Alice"}
// Scenario 1: No changes
orm update user // No SQL executed - entity unchanged
@@ -163,7 +163,7 @@ When multiple entities of the same type are updated in a transaction, JDBC can b
```kotlin
// All updates have identical SQL shape - JDBC batches them
-val users = userRepository.findAll { User_.city eq city }
+val users = userRepository.findAll(User_.city eq city)
userRepository.update(users.map { it.copy(lastLogin = now()) })
```
@@ -174,7 +174,7 @@ userRepository.update(users.map { it.copy(lastLogin = now()) })
**Read-modify-write patterns:** When you load an entity and pass it back to update without modifications, ENTITY mode skips the UPDATE entirely.
```kotlin
-val user = orm.get { User_.id eq userId }
+val user = orm.get(User_.id eq userId)
// No changes made - UPDATE is skipped
orm update user
@@ -201,7 +201,7 @@ orm update updated
`FIELD` mode provides the most granular control. Storm compares each field individually and generates UPDATE statements that include only the columns that actually changed. Like ENTITY mode, if no fields changed, Storm skips the UPDATE entirely.
```kotlin
-val user = orm.get { User_.id eq 1 } // {id=1, email="a@b.com", name="Alice", bio="...", settings="..."}
+val user = orm.get(User_.id eq 1) // {id=1, email="a@b.com", name="Alice", bio="...", settings="..."}
// Only name changed
val updated = user.copy(name = "Bob")
@@ -221,7 +221,7 @@ orm update updated2
```kotlin
// Article has 20 columns including large 'content' field
// But we're only updating the view count
-val article = orm.find { Article_.id eq articleId }
+val article = orm.find(Article_.id eq articleId)
orm update article.copy(viewCount = article.viewCount + 1)
// UPDATE article SET view_count=? WHERE id=?
// The large 'content' column is NOT written
@@ -561,7 +561,7 @@ Dirty checking determines *what to update*. Optimistic locking determines *wheth
```kotlin
// This does NOT prevent lost updates:
-val user = orm.find { User_.id eq 1 }
+val user = orm.find(User_.id eq 1)
// ... another transaction modifies the same user ...
orm update user.copy(name = "New Name") // May overwrite other transaction's changes!
@@ -582,8 +582,8 @@ orm update user.copy(name = "New Name")
Storm tracks which entity types are affected by each mutation so it can selectively invalidate observed state. For template-based updates (using `orm update entity`), Storm knows the entity type and only clears observed state of that type. However, when you execute raw SQL mutations without entity type information, Storm cannot determine which entities may have been affected. Rather than risk stale comparisons that could silently skip a necessary UPDATE, Storm clears all observed state in the current transaction:
```kotlin
-val user = orm.get { User_.id eq 1 } // Storm observes User state
-val city = orm.get { City_.id eq 100 } // Storm observes City state
+val user = orm.get(User_.id eq 1) // Storm observes User state
+val city = orm.get(City_.id eq 100) // Storm observes City state
// Raw SQL mutation - Storm clears all observed state
orm.execute("UPDATE user SET name = 'Changed' WHERE id = 1")
@@ -608,7 +608,7 @@ data class User(
@Embedded val address: Address
) : Entity
-val user = orm.find { User_.id eq 1 }
+val user = orm.find(User_.id eq 1)
// With FIELD mode: only changed columns in Address are updated
orm update user.copy(address = user.address.copy(city = "New City"))
// UPDATE user SET city=? WHERE id=?
diff --git a/docs/entity-cache.md b/docs/entity-cache.md
index 47cc0a69b..d05d62213 100644
--- a/docs/entity-cache.md
+++ b/docs/entity-cache.md
@@ -306,7 +306,7 @@ Even without transaction-level caching, Storm preserves entity identity within a
```kotlin
// Even at READ_COMMITTED in a read-write transaction:
-val orders = orderRepository.findAll { Order_.status eq "pending" }
+val orders = orderRepository.findAll(Order_.status eq "pending")
// If order1 and order2 have the same customer, they share the instance
val customer1 = orders[0].customer.fetch()
@@ -355,7 +355,7 @@ When navigating relationships, `Ref.fetch()` automatically uses the cache:
```kotlin
transaction {
// Load orders with their users
- val orders = orderRepository.findAll { Order_.status eq "pending" }
+ val orders = orderRepository.findAll(Order_.status eq "pending")
// If multiple orders share the same user, only one query per unique user
orders.forEach { order ->
diff --git a/docs/faq.md b/docs/faq.md
index f84905c2d..23d048549 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -418,7 +418,7 @@ Storm does not store collections on entities. This is intentional: collection fi
```kotlin
// Instead of user.orders (not supported)
-val orders = orm.findAll { Order_.user eq user }
+val orders = orm.findAll(Order_.user eq user)
```
### Why doesn't Storm support lazy loading?
diff --git a/docs/first-entity.md b/docs/first-entity.md
index 4dad4d9ec..d1d05c058 100644
--- a/docs/first-entity.md
+++ b/docs/first-entity.md
@@ -161,7 +161,7 @@ The `insertAndFetch` method sends an INSERT statement, retrieves the auto-genera
val user: User? = orm.entity().findById(userId)
// Find by field value using the metamodel (requires storm-metamodel-processor)
-val user: User? = orm.find { User_.email eq "alice@example.com" }
+val user: User? = orm.find(User_.email eq "alice@example.com")
```
diff --git a/docs/first-query.md b/docs/first-query.md
index 2fdb59547..075f1fe1f 100644
--- a/docs/first-query.md
+++ b/docs/first-query.md
@@ -16,15 +16,15 @@ The simplest way to query is with predicate methods directly on the ORM template
val users = orm.entity(User::class)
// Find all users in a city
-val usersInCity: List = users.findAll { User_.city eq city }
+val usersInCity: List = users.findAll(User_.city eq city)
// Find a single user by email
-val user: User? = users.find { User_.email eq "alice@example.com" }
+val user: User? = users.find(User_.email eq "alice@example.com")
// Combine conditions with and / or
-val results: List = users.findAll {
+val results: List = users.findAll(
(User_.city eq city) and (User_.name like "A%")
-}
+)
// Check existence
val exists: Boolean = users.existsById(userId)
@@ -78,7 +78,7 @@ For domain-specific queries that you will reuse, define a custom repository inte
interface UserRepository : EntityRepository {
fun findByEmail(email: String): User? =
- find { User_.email eq email }
+ find(User_.email eq email)
fun findByNameInCity(name: String, city: City): List =
findAll((User_.city eq city) and (User_.name eq name))
diff --git a/docs/hydration.md b/docs/hydration.md
index cbe919fe5..f52b469b6 100644
--- a/docs/hydration.md
+++ b/docs/hydration.md
@@ -428,7 +428,7 @@ When a cache hit occurs, Storm skips the entire construction process for that en
```kotlin
// Query returns 1000 users, but only 50 unique cities
-val users = userRepository.findAll { User_.city eq city }
+val users = userRepository.findAll(User_.city eq city)
```
Without early lookup, Storm would construct 1000 `City` objects and then deduplicate. With early lookup:
diff --git a/docs/index.md b/docs/index.md
index a296c415b..62aa9dc5c 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -62,21 +62,20 @@ data class User(
@FK val city: City
) : Entity
-// DSL—query nested properties like city.name in one go
-val users = orm.findAll { User_.city.name eq "Sunnyvale" }
+// Type-safe predicates — query nested properties like city.name in one go
+val users = orm.findAll(User_.city.name eq "Sunnyvale")
-// Custom repository—inherits all CRUD operations, add your own queries
+// Custom repository — inherits all CRUD operations, add your own queries
interface UserRepository : EntityRepository {
- fun findByCityName(name: String) = findAll { User_.city.name eq name }
+ fun findByCityName(name: String) = findAll(User_.city.name eq name)
}
val users = userRepository.findByCityName("Sunnyvale")
-// Query Builder for more complex operations
-val users = orm.entity(User::class)
- .select()
- .where(User_.city.name eq "Sunnyvale")
- .orderBy(User_.name)
- .resultList
+// Block DSL — build queries with where, orderBy, joins, pagination
+val users = userRepository.select {
+ where(User_.city.name eq "Sunnyvale")
+ orderBy(User_.name)
+}.resultList
// SQL Template for full control; parameterized by default, SQL injection safe
val users = orm.query { """
diff --git a/docs/json.md b/docs/json.md
index 79292c7a6..5e772d153 100644
--- a/docs/json.md
+++ b/docs/json.md
@@ -272,7 +272,7 @@ Query separately:
val users = orm.findAll()
// Batch fetch roles and group by user
-val rolesByUser = orm.findAll { UserRole_.user inList users }
+val rolesByUser = orm.findAll(UserRole_.user inList users)
.groupBy({ it.user }, { it.role })
```
diff --git a/docs/metamodel.md b/docs/metamodel.md
index fc8cf4db6..4c3e47c33 100644
--- a/docs/metamodel.md
+++ b/docs/metamodel.md
@@ -81,10 +81,10 @@ Once the metamodel is generated, you use the `_` suffixed classes in place of st
```kotlin
// Type-safe field reference
-val users = orm.findAll { User_.email eq email }
+val users = orm.findAll(User_.email eq email)
// Type-safe access to nested fields throughout the entire entity graph
-val users = orm.findAll { User_.city.country.code eq "US" }
+val users = orm.findAll(User_.city.country.code eq "US")
// Multiple conditions
val users = orm.entity(User::class)
diff --git a/docs/migration-from-jpa.md b/docs/migration-from-jpa.md
index 7d0503947..a739f669f 100644
--- a/docs/migration-from-jpa.md
+++ b/docs/migration-from-jpa.md
@@ -113,7 +113,7 @@ public interface UserRepository extends JpaRepository {
interface UserRepository : EntityRepository {
fun findByEmail(email: String): User? =
- find { User_.email eq email }
+ find(User_.email eq email)
fun findByCity(city: City): List =
select()
@@ -122,7 +122,7 @@ interface UserRepository : EntityRepository {
.resultList
fun findRecentUsers(since: LocalDateTime): List =
- findAll { User_.createdAt gt since }
+ findAll(User_.createdAt gt since)
}
```
@@ -195,10 +195,10 @@ public interface UserRepository extends JpaRepository {
interface UserRepository : EntityRepository {
fun findByEmail(email: String): User? =
- find { User_.email eq email }
+ find(User_.email eq email)
fun findByCity(city: City): List =
- findAll { User_.city eq city }
+ findAll(User_.city eq city)
}
```
@@ -243,7 +243,7 @@ Optional findByEmail(@Param("email") String email);
```kotlin
// Storm
fun findByEmail(email: String): User? =
- find { User_.email eq email }
+ find(User_.email eq email)
```
@@ -281,7 +281,7 @@ return em.createQuery(cq).getResultList();
```kotlin
// Storm
-orm.findAll { User_.city eq city }
+orm.findAll(User_.city eq city)
```
@@ -355,7 +355,7 @@ private List orders;
Storm approach (query the "many" side):
```kotlin
-val orders = orm.findAll { Order_.user eq user }
+val orders = orm.findAll(Order_.user eq user)
```
## Transaction Migration
@@ -504,7 +504,7 @@ data class CustomerProfile(
// Storm repository (new)
interface CustomerProfileRepository : EntityRepository {
fun findByCustomerId(customerId: Long): CustomerProfile? =
- find { CustomerProfile_.customerId eq customerId }
+ find(CustomerProfile_.customerId eq customerId)
}
// Service that uses both
@@ -642,7 +642,7 @@ Storm intentionally does not support collection fields on entities. This is a de
val orders = user.orders // Not supported
// Right approach
-val orders = orm.findAll { Order_.user eq user }
+val orders = orm.findAll(Order_.user eq user)
```
## Schema Validation
diff --git a/docs/queries.md b/docs/queries.md
index 78ecb29d5..b913c5833 100644
--- a/docs/queries.md
+++ b/docs/queries.md
@@ -38,10 +38,10 @@ For simple queries, use methods directly on the ORM template:
```kotlin
// Find single entity with predicate
-val user: User? = orm.find { User_.email eq email }
+val user: User? = orm.find(User_.email eq email)
// Find all matching
-val users: List = orm.findAll { User_.city eq city }
+val users: List = orm.findAll(User_.city eq city)
// Find by field value
val user: User? = orm.findBy(User_.email, email)
@@ -94,10 +94,10 @@ val users = orm.entity(User::class)
val user: User? = users.findById(userId)
// Find with predicate
-val user: User? = users.find { User_.email eq email }
+val user: User? = users.find(User_.email eq email)
// Find all matching
-val usersInCity: List = users.findAll { User_.city eq city }
+val usersInCity: List = users.findAll(User_.city eq city)
// Count
val count: Long = users.count()
@@ -148,14 +148,14 @@ Combine conditions with `and` and `or`:
```kotlin
// AND condition
-val users = orm.findAll {
+val users = orm.findAll(
(User_.city eq city) and (User_.birthDate less LocalDate.of(2000, 1, 1))
-}
+)
// OR condition
-val users = orm.findAll {
+val users = orm.findAll(
(User_.role eq adminRole) or (User_.role eq superUserRole)
-}
+)
// Complex conditions
val users = orm.entity(User::class)
@@ -186,9 +186,9 @@ val users = orm.entity(User::class)
| `notInList` | NOT IN (list) |
```kotlin
-val users = orm.findAll { User_.email like "%@example.com" }
-val users = orm.findAll { User_.deletedAt.isNull() }
-val users = orm.findAll { User_.role inList listOf(adminRole, userRole) }
+val users = orm.findAll(User_.email like "%@example.com")
+val users = orm.findAll(User_.deletedAt.isNull())
+val users = orm.findAll(User_.role inList listOf(adminRole, userRole))
```
@@ -845,7 +845,7 @@ When you expect at most one matching row, use `find` (Kotlin, returns `null` if
```kotlin
-val user: User? = orm.find { User_.email eq email }
+val user: User? = orm.find(User_.email eq email)
```
diff --git a/docs/refs.md b/docs/refs.md
index 26fddd74c..9ffe4c085 100644
--- a/docs/refs.md
+++ b/docs/refs.md
@@ -51,7 +51,7 @@ When you need the full referenced entity, call `fetch()`. This triggers a databa
```kotlin
-val user = orm.get { User_.id eq userId }
+val user = orm.get(User_.id eq userId)
val city: City = user.city.fetch() // Loads from database
```
@@ -338,7 +338,7 @@ data class Report(
) : Entity
// Later, when you need the author
-val report = orm.find { Report_.id eq reportId }
+val report = orm.find(Report_.id eq reportId)
if (needsAuthorInfo) {
val author = report?.author?.fetch()
}
diff --git a/docs/relationships.md b/docs/relationships.md
index 9d302b778..ad891c745 100644
--- a/docs/relationships.md
+++ b/docs/relationships.md
@@ -32,7 +32,7 @@ data class User(
) : Entity
// Query with type-safe access to nested fields throughout the entire entity graph
-val users = orm.findAll { User_.city.country.code eq "US" }
+val users = orm.findAll(User_.city.country.code eq "US")
// Result: fully populated User with City and Country included
users.forEach { println("${it.name} lives in ${it.city.name}, ${it.city.country.name}") }
@@ -68,7 +68,7 @@ data class User(
When you query a `User`, the related `City` is automatically loaded:
```kotlin
-val user = orm.find { User_.id eq userId }
+val user = orm.find(User_.id eq userId)
println(user?.city.name) // City is already loaded
```
@@ -146,7 +146,7 @@ Storm does not store collections on the "one" side of a relationship. Instead, q
```kotlin
// Find all users in a city
-val usersInCity: List = orm.findAll { User_.city eq city }
+val usersInCity: List = orm.findAll(User_.city eq city)
```
@@ -191,11 +191,11 @@ Query through the join entity:
```kotlin
// Find all roles for a user
-val userRoles: List = orm.findAll { UserRole_.user eq user }
+val userRoles: List = orm.findAll(UserRole_.user eq user)
val roles: List = userRoles.map { it.role }
// Find all users with a specific role
-val userRoles: List = orm.findAll { UserRole_.role eq role }
+val userRoles: List = orm.findAll(UserRole_.role eq role)
val users: List = userRoles.map { it.user }
```
diff --git a/docs/repositories.md b/docs/repositories.md
index 0536751f9..24f1cb812 100644
--- a/docs/repositories.md
+++ b/docs/repositories.md
@@ -58,8 +58,8 @@ val user = orm insert User(
// Read
val found: User? = orm.entity().findById(user.id)
-val alice: User? = orm.find { User_.name eq "Alice" }
-val all: List = orm.findAll { User_.city eq city }
+val alice: User? = orm.find(User_.name eq "Alice")
+val all: List = orm.findAll(User_.city eq city)
// Update
orm update user.copy(name = "Alice Johnson")
@@ -68,7 +68,7 @@ orm update user.copy(name = "Alice Johnson")
orm delete user
// Delete by condition
-orm.delete { User_.city eq city }
+orm.delete(User_.city eq city)
// Delete all
orm.deleteAll()
@@ -474,7 +474,7 @@ interface UserRepository : EntityRepository {
// Custom query method
fun findByEmail(email: String): User? =
- find { User_.email eq email }
+ find(User_.email eq email)
// Custom query with multiple conditions
fun findByNameInCity(name: String, city: City): List =
diff --git a/docs/security.md b/docs/security.md
index 5cb979ae1..f29ee965c 100644
--- a/docs/security.md
+++ b/docs/security.md
@@ -20,7 +20,7 @@ The most important security property of Storm is that **all values are parameter
```kotlin
// The 'email' value is sent as a JDBC parameter, not interpolated into SQL.
-val user = userRepository.find { User_.email eq email }
+val user = userRepository.find(User_.email eq email)
```
Generated SQL:
diff --git a/docs/serialization.md b/docs/serialization.md
index 6c35743d8..05028bdc5 100644
--- a/docs/serialization.md
+++ b/docs/serialization.md
@@ -180,7 +180,7 @@ data class Pet(
@FK @Contextual val owner: Ref?,
) : Entity
-val pet = orm.get { Pet_.id eq 1 }
+val pet = orm.get(Pet_.id eq 1)
val json = Json { serializersModule = StormSerializers }
.encodeToString(pet)
// {"id":1,"name":"Leo","owner":1}
@@ -217,7 +217,7 @@ When the application calls `fetch()` on a ref before serialization, the referenc
```kotlin
-val pet = orm.get { Pet_.id eq 1 }
+val pet = orm.get(Pet_.id eq 1)
pet.owner?.fetch() // Load the owner into the ref
val json = Json { serializersModule = StormSerializers }
@@ -266,7 +266,7 @@ data class PetWithProjectionOwner(
@FK @Contextual val owner: Ref?,
) : Entity
-val pet = orm.get { PetWithProjectionOwner_.id eq 1 }
+val pet = orm.get(PetWithProjectionOwner_.id eq 1)
pet.owner?.fetch()
val json = Json { serializersModule = StormSerializers }
diff --git a/docs/spring-integration.md b/docs/spring-integration.md
index 198771a6a..c80c16835 100644
--- a/docs/spring-integration.md
+++ b/docs/spring-integration.md
@@ -159,7 +159,7 @@ Define repositories:
interface UserRepository : EntityRepository {
fun findByEmail(email: String): User? =
- find { User_.email eq email }
+ find(User_.email eq email)
}
```
diff --git a/docs/sql-logging.md b/docs/sql-logging.md
index b7cb23f5e..4c8a7f674 100644
--- a/docs/sql-logging.md
+++ b/docs/sql-logging.md
@@ -23,10 +23,10 @@ The simplest way to enable SQL logging is to annotate the repository interface i
interface UserRepository : EntityRepository {
fun findByEmail(email: String): User? =
- find { User_.email eq email }
+ find(User_.email eq email)
fun findActiveUsers(): List =
- findAll { User_.active eq true }
+ findAll(User_.active eq true)
}
```
@@ -75,12 +75,12 @@ interface OrderRepository : EntityRepository {
// No logging
fun findById(id: Int): Order? =
- find { Order_.id eq id }
+ find(Order_.id eq id)
// Logged
@SqlLog
fun findExpiredOrders(cutoff: LocalDate): List =
- findAll { Order_.expiresAt lt cutoff }
+ findAll(Order_.expiresAt lt cutoff)
}
```
@@ -124,7 +124,7 @@ Setting `inlineParameters = true` replaces the `?` placeholders with the actual
interface UserRepository : EntityRepository {
fun findByEmail(email: String): User? =
- find { User_.email eq email }
+ find(User_.email eq email)
}
```
diff --git a/docs/sql-templates.md b/docs/sql-templates.md
index d83b2fef2..66a55364b 100644
--- a/docs/sql-templates.md
+++ b/docs/sql-templates.md
@@ -476,11 +476,11 @@ Storm caches compiled templates to eliminate even this small overhead on repeate
```kotlin
// First execution: full compilation + binding
-userRepository.find { User_.email eq "alice@example.com" }
+userRepository.find(User_.email eq "alice@example.com")
// Subsequent executions: cache hit, binding only
-userRepository.find { User_.email eq "bob@example.com" }
-userRepository.find { User_.email eq "charlie@example.com" }
+userRepository.find(User_.email eq "bob@example.com")
+userRepository.find(User_.email eq "charlie@example.com")
```
This applies to all Storm operations. Repository methods like `findAll()`, `insert()`, and `update()` benefit from the same caching mechanism. Once a query pattern has been compiled, repeated use across your application reuses the cached compilation.
diff --git a/storm-bom/pom.xml b/storm-bom/pom.xml
index 9493dc363..db7303409 100644
--- a/storm-bom/pom.xml
+++ b/storm-bom/pom.xml
@@ -96,6 +96,26 @@
storm-metamodel-processor
${project.version}
+
+ st.orm
+ storm-compiler-plugin-2.0
+ ${project.version}
+
+
+ st.orm
+ storm-compiler-plugin-2.1
+ ${project.version}
+
+
+ st.orm
+ storm-compiler-plugin-2.2
+ ${project.version}
+
+
+ st.orm
+ storm-compiler-plugin-2.3
+ ${project.version}
+
st.orm
storm-metamodel-ksp
diff --git a/storm-cli/storm.mjs b/storm-cli/storm.mjs
index a8921d0f2..4cb4126e7 100644
--- a/storm-cli/storm.mjs
+++ b/storm-cli/storm.mjs
@@ -45,28 +45,26 @@ process.on('exit', () => process.stdout.write(SHOW_CURSOR));
// ─── Welcome screen ─────────────────────────────────────────────────────────
const BODY_SOURCE = `
-
-
- @@@@@@@@@@@@@@@@@@@@
- @@@@ @@@@
- @@@ # @@@
- @@@@@ ### @@@@
- @@@@@@@@@@@@@@ ### @@@@@@@@@
- @@@@@@@@@@@@ ### @@@@@@@@@
- @ @@@@@@@@ ### @@@@@@@ @
- @@@@ #### @@@@
- @@@@@@@@@ #### @@@@@@@@@
- @@@@@@@ ########## @@@@@@@
- @ @@ ######### @@@ @
- @@@@@ #### @@@@@
- @@@@@@@@@@ #### @@@@@@@@@@@@
- @@@@@@@@@ ### @@@@@@@@@@@@@
- @@@@@ ### @@@@@@@@@@@
- ###
- ##
+ @@@@@@@@@@@@@@@@@@@@
+ @@@@ @@@@
+@@@ # @@@
+@@@@@ ### @@@@
+@@@@@@@@@@@@@@ ### @@@@@@@@@
+ @@@@@@@@@@@@ ### @@@@@@@@@
+@ @@@@@@@@ ### @@@@@@@ @
+@@@@ #### @@@@
+@@@@@@@@@ #### @@@@@@@@@
+ @@@@@@@ ########## @@@@@@@
+@ @@ ######### @@@ @
+@@@@@ #### @@@@@
+@@@@@@@@@@ #### @@@@@@@@@@@@
+ @@@@@@@@@ ### @@@@@@@@@@@@@
+ @@@@@ ### @@@@@@@@@@@
+ ###
+ ##
`.trimEnd();
-const bodyLines = BODY_SOURCE.split('\n');
+const bodyLines = BODY_SOURCE.replace(/^\n/, '').split('\n');
const artWidth = Math.max(...bodyLines.map(s => s.length));
const artHeight = bodyLines.length;
const paddedLines = Array.from({ length: artHeight }, (_, i) =>
@@ -189,6 +187,90 @@ function ringGlow(x, y, now) {
return false;
}
+// --- Matrix rain (demo mode) ---
+
+let demoMode = false;
+const matrixColumns = [];
+const MATRIX_MARGIN = 12;
+
+function newDrop() {
+ return {
+ y: -1 - Math.random() * artHeight * 0.6,
+ speed: 0.06 + Math.random() * 0.14,
+ trailLength: 3 + Math.floor(Math.random() * 7),
+ };
+}
+
+function initMatrixRain() {
+ matrixColumns.length = 0;
+ const totalWidth = artWidth + 2 * MATRIX_MARGIN;
+ for (let x = 0; x < totalWidth; x++) {
+ const drops = [];
+ const count = 1 + Math.floor(Math.random() * 3);
+ for (let i = 0; i < count; i++) {
+ const drop = newDrop();
+ drop.y = Math.random() * artHeight * 2 - artHeight;
+ drops.push(drop);
+ }
+ matrixColumns.push(drops);
+ }
+}
+
+const MATRIX_GLYPHS = 'ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン';
+
+function matrixGlyph(x, y, now) {
+ const idx = (x * 17 + y * 31 + Math.floor(now / 80)) % MATRIX_GLYPHS.length;
+ return MATRIX_GLYPHS[idx];
+}
+
+function updateMatrixRain() {
+ for (const drops of matrixColumns) {
+ for (const drop of drops) {
+ drop.y += drop.speed;
+ if (drop.y - drop.trailLength > artHeight + 3) {
+ drop.y = -1 - Math.random() * artHeight * 0.6;
+ drop.speed = 0.06 + Math.random() * 0.14;
+ drop.trailLength = 3 + Math.floor(Math.random() * 7);
+ }
+ }
+ }
+}
+
+function matrixIntensityAt(x, y) {
+ const drops = matrixColumns[x];
+ if (!drops) return 0;
+ let best = 0;
+ for (const drop of drops) {
+ const dist = drop.y - y;
+ if (dist < -0.5 || dist > drop.trailLength) continue;
+ const t = Math.max(0, dist) / drop.trailLength;
+ let intensity;
+ if (dist < 0.5) intensity = 5;
+ else if (t < 0.15) intensity = 4;
+ else if (t < 0.35) intensity = 3;
+ else if (t < 0.6) intensity = 2;
+ else intensity = 1;
+ if (intensity > best) best = intensity;
+ }
+ return best;
+}
+
+function matrixColor(intensity) {
+ if (intensity === 1) return DIM + BOLT_BASE;
+ if (intensity === 2) return BOLD + BOLT_BASE;
+ if (intensity === 3) return BOLD + BOLT_WARM;
+ if (intensity === 4) return BOLD + BOLT_HOT;
+ return BOLD + BOLT_WHITE;
+}
+
+function matrixDbColor(intensity) {
+ if (intensity === 1) return '\x1b[38;5;244m';
+ if (intensity === 2) return '\x1b[38;5;245m';
+ if (intensity === 3) return '\x1b[38;5;247m';
+ if (intensity === 4) return '\x1b[38;5;248m';
+ return '\x1b[38;5;249m';
+}
+
// --- Text overlay ---
function stripAnsi(s) { return s.replace(/\x1b\[[0-9;]*m/g, ''); }
@@ -198,16 +280,28 @@ function centerLine(line, cols) {
return ' '.repeat(pad) + line;
}
-const textLines = [
+const INIT_TEXT_LINES = [
'',
`${GRAY}Bootstrap your project for Storm ORM${RESET}`,
'',
- `${BOLD}${BOLT_HOT}\u2022${WHITE} Configure Storm rules${RESET}`,
- `${BOLD}${BOLT_HOT}\u2022${WHITE} Install Storm skills${RESET}`,
+ `${BOLD}${BOLT_HOT}\u2022${WHITE} Install Storm rules and skills${RESET}`,
+ `${BOLD}${BOLT_HOT}\u2022${WHITE} Storm MCP for database awareness and validation (optional)${RESET}`,
'',
- `${GRAY}Press Enter to select tools...${RESET}`,
+ `${GRAY}Press Enter to select tools${RESET}`,
];
+const DEMO_TEXT_LINES = [
+ '',
+ `${GRAY}Storm Fu - Training Program${RESET}`,
+ '',
+ `${BOLD}${BOLT_HOT}\u2022${WHITE} Live demo of building an app with the Storm AI workflow${RESET}`,
+ `${BOLD}${BOLT_HOT}\u2022${WHITE} Storm MCP enables database awareness and validation${RESET}`,
+ '',
+ `${GRAY}Press Enter to follow the white rabbit${RESET}`,
+];
+
+let activeTextLines = INIT_TEXT_LINES;
+
// --- Render ---
function renderFrame(now) {
@@ -215,17 +309,44 @@ function renderFrame(now) {
const rows = process.stdout.rows || 40;
const rendered = [];
+ if (demoMode) updateMatrixRain();
+
+ const startX = demoMode ? -MATRIX_MARGIN : 0;
+ const endX = demoMode ? artWidth + MATRIX_MARGIN : artWidth;
+
for (let y = 0; y < artHeight; y++) {
let out = '';
- for (let x = 0; x < artWidth; x++) {
- if (!hasBody(x, y)) { out += ' '; continue; }
- if (hasBolt(x, y)) out += boltColorCode(x, y, now) + '#' + RESET;
- else out += dbColor(y, ringGlow(x, y, now)) + '@' + RESET;
+ for (let x = startX; x < endX; x++) {
+ const inArt = x >= 0 && x < artWidth;
+ if (!inArt || !hasBody(x, y)) {
+ if (demoMode) {
+ const mx = x + MATRIX_MARGIN;
+ const mi = matrixIntensityAt(mx, y);
+ if (mi >= 4) out += '\x1b[38;5;237m' + matrixGlyph(mx, y, now) + RESET;
+ else if (mi >= 2) out += '\x1b[38;5;235m' + matrixGlyph(mx, y, now) + RESET;
+ else out += '\x1b[38;5;233m' + matrixGlyph(mx, y, now) + RESET;
+ } else out += ' ';
+ continue;
+ }
+ if (demoMode) {
+ const mx = x + MATRIX_MARGIN;
+ const mi = matrixIntensityAt(mx, y);
+ if (hasBolt(x, y)) {
+ if (mi >= 3) out += matrixColor(mi) + matrixGlyph(mx, y, now) + RESET;
+ else if (mi > 0) out += matrixColor(mi) + '#' + RESET;
+ else out += dbColor(y, false) + '#' + RESET;
+ } else {
+ out += dbColor(y, false) + '@' + RESET;
+ }
+ } else {
+ if (hasBolt(x, y)) out += boltColorCode(x, y, now) + '#' + RESET;
+ else out += dbColor(y, ringGlow(x, y, now)) + '@' + RESET;
+ }
}
if (out.trim().length > 0) rendered.push(centerLine(out, cols));
}
- for (const tl of textLines) rendered.push(centerLine(tl, cols));
+ for (const tl of activeTextLines) rendered.push(centerLine(tl, cols));
const topPad = Math.max(0, Math.floor((rows - rendered.length) / 3));
const blankLine = ' '.repeat(cols);
@@ -238,8 +359,14 @@ function renderFrame(now) {
return frame;
}
-async function printWelcome() {
- nextStrike = Date.now() + Math.random() * 100;
+async function printWelcome(customTextLines) {
+ activeTextLines = customTextLines || INIT_TEXT_LINES;
+ demoMode = customTextLines === DEMO_TEXT_LINES;
+ if (demoMode) {
+ initMatrixRain();
+ } else {
+ nextStrike = Date.now() + Math.random() * 100;
+ }
process.stdout.write(HIDE_CURSOR + CLEAR);
const onResize = () => process.stdout.write(CLEAR);
@@ -247,10 +374,12 @@ async function printWelcome() {
const timer = setInterval(() => {
const now = Date.now();
- if (!strikeActive && now > nextStrike) startStrike(now);
- if (strikeActive && now > strikeStart + strikeDuration) {
- strikeActive = false;
- scheduleStrike(now);
+ if (!demoMode) {
+ if (!strikeActive && now > nextStrike) startStrike(now);
+ if (strikeActive && now > strikeStart + strikeDuration) {
+ strikeActive = false;
+ scheduleStrike(now);
+ }
}
process.stdout.write(HOME);
process.stdout.write(renderFrame(now));
@@ -543,13 +672,43 @@ async function textInput({ message, defaultValue = '', mask = false }) {
});
}
+// ─── Project config (.storm.json) ────────────────────────────────────────────
+
+const CONFIG_FILE = '.storm.json';
+
+function readProjectConfig() {
+ const configPath = join(process.cwd(), CONFIG_FILE);
+ try {
+ return JSON.parse(readFileSync(configPath, 'utf-8'));
+ } catch {
+ return null;
+ }
+}
+
+function writeProjectConfig(tools, languages) {
+ const configPath = join(process.cwd(), CONFIG_FILE);
+ writeFileSync(configPath, JSON.stringify({ tools, languages }, null, 2) + '\n');
+}
+
// ─── Content (fetched from orm.st at runtime) ───────────────────────────────
const SKILLS_BASE_URL = 'https://orm.st/skills';
const STORM_SKILL_MARKER = '';
+// --dev flag: read skills from a local directory instead of orm.st.
+let devSkillsDir = null;
+{
+ const devIdx = process.argv.indexOf('--dev');
+ if (devIdx !== -1 && process.argv[devIdx + 1]) {
+ devSkillsDir = process.argv[devIdx + 1];
+ }
+}
+
async function fetchRules() {
try {
+ if (devSkillsDir) {
+ return readFileSync(join(devSkillsDir, 'storm-rules.md'), 'utf-8');
+ }
const res = await fetch(`${SKILLS_BASE_URL}/storm-rules.md`);
if (!res.ok) throw new Error(`${res.status}`);
return await res.text();
@@ -561,6 +720,9 @@ async function fetchRules() {
async function fetchSkillIndex(language) {
try {
+ if (devSkillsDir) {
+ return JSON.parse(readFileSync(join(devSkillsDir, `index-${language}.json`), 'utf-8'));
+ }
const res = await fetch(`${SKILLS_BASE_URL}/index-${language}.json`);
if (!res.ok) throw new Error(`${res.status}`);
return await res.json();
@@ -569,9 +731,28 @@ async function fetchSkillIndex(language) {
}
}
+const DEV_SETUP_APPEND = `
+
+## Development Mode
+
+This project uses a local (unpublished) version of Storm. Add \`mavenLocal()\` as the first repository in Gradle so it resolves Storm artifacts from the local Maven cache (\`~/.m2/repository\`):
+
+\`\`\`kotlin
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+\`\`\`
+`;
+
async function fetchSkill(name) {
- const url = `${SKILLS_BASE_URL}/${name}.md`;
try {
+ if (devSkillsDir) {
+ let content = readFileSync(join(devSkillsDir, `${name}.md`), 'utf-8');
+ if (name === 'storm-setup') content = content.trimEnd() + '\n' + DEV_SETUP_APPEND;
+ return content.trimEnd() + '\n\n' + STORM_SKILL_MARKER + '\n';
+ }
+ const url = `${SKILLS_BASE_URL}/${name}.md`;
const res = await fetch(url);
if (!res.ok) throw new Error(`${res.status}`);
const content = await res.text();
@@ -1109,79 +1290,85 @@ function registerMcp(toolConfig, stormDir, created, appended) {
// ─── Database setup ──────────────────────────────────────────────────────────
-async function setupDatabase() {
+async function setupDatabase(preConfigured) {
const stormDir = join(homedir(), '.storm');
const connectionPath = join(stormDir, 'connection.json');
- // Check for existing config.
- if (existsSync(connectionPath)) {
- try {
- const existing = JSON.parse(readFileSync(connectionPath, 'utf-8'));
- console.log(dimText(` Current connection: ${existing.dialect}://${existing.host || ''}${existing.port ? ':' + existing.port : ''}/${existing.database}`));
- console.log();
- const reconfigure = await confirm({ message: 'Reconfigure?', defaultValue: false });
- if (!reconfigure) return stormDir;
- console.log();
- } catch {}
- }
-
- const dialect = await select({
- message: 'Database dialect',
- choices: [
- { name: 'PostgreSQL', value: 'postgresql' },
- { name: 'MySQL', value: 'mysql' },
- { name: 'MariaDB', value: 'mariadb' },
- { name: 'Oracle', value: 'oracle' },
- { name: 'SQL Server', value: 'mssqlserver' },
- { name: 'SQLite', value: 'sqlite' },
- { name: 'H2', value: 'h2' },
- ],
- });
-
- const dialectConfig = DIALECTS[dialect];
let connection;
- if (dialectConfig.fileBased) {
- const database = await textInput({ message: 'Database file path' });
- if (!database) {
- console.log(boltYellow('\n Database file path is required.'));
- return null;
- }
- connection = { dialect, database };
+ if (preConfigured) {
+ // Non-interactive: use the provided connection details directly.
+ connection = preConfigured;
} else {
- if (dialect === 'h2') {
- console.log(dimText(' Note: Requires H2 running with PG wire protocol (-pgPort)'));
- }
- if (dialect === 'oracle') {
- console.log(dimText(' Note: Uses oracledb thin mode (no Oracle Client required)'));
+ // Interactive: prompt the user.
+ if (existsSync(connectionPath)) {
+ try {
+ const existing = JSON.parse(readFileSync(connectionPath, 'utf-8'));
+ console.log(dimText(` Current connection: ${existing.dialect}://${existing.host || ''}${existing.port ? ':' + existing.port : ''}/${existing.database}`));
+ console.log();
+ const reconfigure = await confirm({ message: 'Reconfigure?', defaultValue: false });
+ if (!reconfigure) return stormDir;
+ console.log();
+ } catch {}
}
- const defaultPort = String(dialectConfig.defaultPort);
- const host = await textInput({ message: 'Host', defaultValue: 'localhost' });
- const port = await textInput({ message: 'Port', defaultValue: defaultPort });
- const database = await textInput({ message: 'Database' });
- const username = await textInput({ message: 'Username', defaultValue: 'storm' });
- const password = await textInput({ message: 'Password', mask: true });
+ const dialect = await select({
+ message: 'Database dialect',
+ choices: [
+ { name: 'PostgreSQL', value: 'postgresql' },
+ { name: 'MySQL', value: 'mysql' },
+ { name: 'MariaDB', value: 'mariadb' },
+ { name: 'Oracle', value: 'oracle' },
+ { name: 'SQL Server', value: 'mssqlserver' },
+ { name: 'SQLite', value: 'sqlite' },
+ { name: 'H2', value: 'h2' },
+ ],
+ });
- if (!database) {
- console.log(boltYellow('\n Database name is required.'));
- return null;
- }
+ const dialectConfig = DIALECTS[dialect];
- connection = {
- dialect,
- host: host || 'localhost',
- port: parseInt(port || defaultPort, 10),
- database,
- username: username || 'storm',
- password: password || '',
- };
+ if (dialectConfig.fileBased) {
+ const database = await textInput({ message: 'Database file path' });
+ if (!database) {
+ console.log(boltYellow('\n Database file path is required.'));
+ return null;
+ }
+ connection = { dialect, database };
+ } else {
+ if (dialect === 'h2') {
+ console.log(dimText(' Note: Requires H2 running with PG wire protocol (-pgPort)'));
+ }
+ if (dialect === 'oracle') {
+ console.log(dimText(' Note: Uses oracledb thin mode (no Oracle Client required)'));
+ }
+
+ const defaultPort = String(dialectConfig.defaultPort);
+ const host = await textInput({ message: 'Host', defaultValue: 'localhost' });
+ const port = await textInput({ message: 'Port', defaultValue: defaultPort });
+ const database = await textInput({ message: 'Database' });
+ const username = await textInput({ message: 'Username', defaultValue: 'storm' });
+ const password = await textInput({ message: 'Password', mask: true });
+
+ if (!database) {
+ console.log(boltYellow('\n Database name is required.'));
+ return null;
+ }
+
+ connection = {
+ dialect,
+ host: host || 'localhost',
+ port: parseInt(port || defaultPort, 10),
+ database,
+ username: username || 'storm',
+ password: password || '',
+ };
+ }
}
// Install driver.
- const driverPackage = dialectConfig.driver;
+ const driverPackage = DIALECTS[connection.dialect].driver;
console.log();
- console.log(dimText(` Installing ${dialectConfig.name} driver...`));
+ console.log(dimText(` Installing ${DIALECTS[connection.dialect].name} driver...`));
mkdirSync(stormDir, { recursive: true });
@@ -1266,7 +1453,9 @@ function detectConfiguredLanguages(toolConfigs) {
}
async function update() {
- const tools = detectConfiguredTools();
+ const projectConfig = readProjectConfig();
+
+ const tools = projectConfig?.tools ?? detectConfiguredTools();
if (tools.length === 0) {
console.log(boltYellow('\n No configured AI tools found. Run `storm init` first.\n'));
return;
@@ -1275,10 +1464,12 @@ async function update() {
const toolConfigs = tools.map(t => TOOL_CONFIGS[t]);
const skillToolConfigs = toolConfigs.filter(c => c.skillPath);
- // Detect languages from existing installed skills.
- let languages = detectConfiguredLanguages(skillToolConfigs);
+ // Use persisted language choice, fall back to detection from skill markers, then both.
+ let languages = projectConfig?.languages;
+ if (!languages || languages.length === 0) {
+ languages = detectConfiguredLanguages(skillToolConfigs);
+ }
if (languages.length === 0) {
- // Fallback: if we can't detect languages, fetch both indexes and use whichever has skills.
languages = ['kotlin', 'java'];
}
@@ -1287,7 +1478,7 @@ async function update() {
console.log();
// Fetch rules and skill indexes.
- console.log(dimText(' Fetching content from https://orm.st...'));
+ console.log(dimText(` Fetching content from ${devSkillsDir ? devSkillsDir : 'https://orm.st'}...`));
const fetchPromises = [fetchRules(), ...languages.map(l => fetchSkillIndex(l))];
const [stormRules, ...skillIndexes] = await Promise.all(fetchPromises);
if (!stormRules) {
@@ -1313,7 +1504,7 @@ async function update() {
// Fetch and install skills.
if (skillToolConfigs.length > 0) {
- console.log(dimText(' Fetching skills from https://orm.st...'));
+ console.log(dimText(` Fetching skills from ${devSkillsDir ? devSkillsDir : 'https://orm.st'}...`));
const fetchedSkills = new Map();
for (const skillName of skillNames) {
const content = await fetchSkill(skillName);
@@ -1481,6 +1672,16 @@ async function setup() {
return;
}
+ // Persist tool and language choices so `storm update` can use them.
+ writeProjectConfig(tools, languages);
+
+ // Add .storm.json to .gitignore (machine-specific config).
+ const gitignorePath = join(process.cwd(), '.gitignore');
+ let gitignore = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
+ if (!gitignore.includes(CONFIG_FILE)) {
+ appendFileSync(gitignorePath, `\n# Storm config (machine-specific)\n${CONFIG_FILE}\n`);
+ }
+
console.log();
// Step 2: Fetch and install rules.
@@ -1488,7 +1689,7 @@ async function setup() {
const appended = [];
const skipped = [];
- console.log(dimText(' Fetching content from https://orm.st...'));
+ console.log(dimText(` Fetching content from ${devSkillsDir ? devSkillsDir : 'https://orm.st'}...`));
const fetchPromises = [fetchRules(), ...languages.map(l => fetchSkillIndex(l))];
const [stormRules, ...skillIndexes] = await Promise.all(fetchPromises);
if (!stormRules) {
@@ -1512,7 +1713,7 @@ async function setup() {
const fetchedSkills = new Map();
if (skillToolConfigs.length > 0) {
- console.log(dimText(' Fetching skills from https://orm.st...'));
+ console.log(dimText(` Fetching skills from ${devSkillsDir ? devSkillsDir : 'https://orm.st'}...`));
for (const skillName of skillNames) {
const content = await fetchSkill(skillName);
if (!content) { skipped.push(skillName + ' (fetch failed)'); continue; }
@@ -1645,6 +1846,209 @@ async function setup() {
console.log();
}
+// ─── Demo ─────────────────────────────────────────────────────────────────────
+
+async function demo() {
+ await printWelcome(DEMO_TEXT_LINES);
+
+ // Step 1: Select AI tool.
+ console.log(' Storm Demo creates a Kotlin project in this empty directory and');
+ console.log(' installs a skill that guides your AI tool to build a web application');
+ console.log(' demonstrating Storm ORM using the public IMDB dataset.');
+ console.log();
+ const tool = await select({
+ message: 'Which AI tool will you use?',
+ choices: [
+ { name: 'Claude Code', value: 'claude' },
+ { name: 'Cursor', value: 'cursor' },
+ { name: 'GitHub Copilot', value: 'copilot' },
+ { name: 'Windsurf', value: 'windsurf' },
+ ],
+ });
+
+ // Step 2: Check if directory is empty (ignoring hidden files/directories).
+ const cwd = process.cwd();
+ const entries = readdirSync(cwd).filter(e => !e.startsWith('.'));
+ if (entries.length > 0) {
+ console.log();
+ console.log(boltYellow(' This directory is not empty.'));
+ console.log(dimText(' Storm Demo requires an empty directory. Create a new directory and'));
+ console.log(dimText(' run storm demo from there.'));
+ console.log();
+ return;
+ }
+
+ const tools = [tool];
+ const config = TOOL_CONFIGS[tool];
+ const created = [];
+ const appended = [];
+ const skipped = [];
+
+ // Step 3: Fetch rules, Kotlin skill index, and demo skill index.
+ console.log();
+ console.log(dimText(` Fetching content from ${devSkillsDir ? devSkillsDir : 'https://orm.st'}...`));
+ const [stormRules, skillIndex, demoIndex] = await Promise.all([
+ fetchRules(),
+ fetchSkillIndex('kotlin'),
+ fetchSkillIndex('demo'),
+ ]);
+
+ if (!stormRules) {
+ console.log(boltYellow('\n Could not fetch Storm rules from https://orm.st. Check your connection.'));
+ return;
+ }
+
+ const skillNames = skillIndex?.skills ?? [];
+ const demoSkillNames = demoIndex?.skills ?? [];
+
+ // Step 4: Install rules block.
+ if (config.rulesFile) {
+ installRulesBlock(join(cwd, config.rulesFile), stormRules, created, appended);
+ }
+
+ // Step 5: Fetch and install Kotlin skills + demo skills.
+ const installedSkillNames = [];
+ if (config.skillPath) {
+ console.log(dimText(` Fetching skills from ${devSkillsDir ? devSkillsDir : 'https://orm.st'}...`));
+ const fetchedSkills = new Map();
+ for (const skillName of [...skillNames, ...demoSkillNames]) {
+ const content = await fetchSkill(skillName);
+ if (!content) { skipped.push(skillName + ' (fetch failed)'); continue; }
+ fetchedSkills.set(skillName, content);
+ installedSkillNames.push(skillName);
+ }
+ for (const [name, content] of fetchedSkills) {
+ installSkill(name, content, config, created);
+ }
+ }
+
+ // Step 6: Choose database and set up MCP server.
+ const dialect = await select({
+ message: 'Which database?',
+ choices: [
+ { name: 'PostgreSQL', value: 'postgresql' },
+ { name: 'MySQL', value: 'mysql' },
+ { name: 'MariaDB', value: 'mariadb' },
+ { name: 'Oracle', value: 'oracle' },
+ { name: 'SQL Server', value: 'mssqlserver' },
+ { name: 'SQLite', value: 'sqlite' },
+ { name: 'H2', value: 'h2' },
+ ],
+ });
+ const dialectInfo = DIALECTS[dialect] || DIALECTS.postgresql;
+ let demoConnection;
+ if (dialectInfo.fileBased) {
+ demoConnection = { dialect, database: 'imdb.db' };
+ } else {
+ demoConnection = {
+ dialect,
+ host: 'localhost',
+ port: dialectInfo.defaultPort,
+ database: 'imdb',
+ username: 'storm',
+ password: 'storm',
+ };
+ }
+
+ console.log(dimText(' Setting up MCP server for database schema access...'));
+ const stormDir = await setupDatabase(demoConnection);
+ const dbConfigured = stormDir !== null;
+
+ if (dbConfigured) {
+ // Register MCP for the selected tool.
+ registerMcp(config, stormDir, created, appended);
+
+ // Add MCP config file to .gitignore.
+ if (config.mcpFile) {
+ const gitignorePath = join(cwd, '.gitignore');
+ let gitignore = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
+ if (!gitignore.includes(config.mcpFile)) {
+ appendFileSync(gitignorePath, `\n# Storm MCP (machine-specific paths)\n${config.mcpFile}\n`);
+ }
+ }
+
+ // Fetch and install schema rules into the rules block.
+ const schemaRules = await fetchSkill('storm-schema-rules');
+ if (config.rulesFile && schemaRules) {
+ const rulesPath = join(cwd, config.rulesFile);
+ if (existsSync(rulesPath)) {
+ const existing = readFileSync(rulesPath, 'utf-8');
+ if (!existing.includes('Database Schema Access')) {
+ const endMarker = existing.indexOf(MARKER_END);
+ if (endMarker !== -1) {
+ const updated = existing.substring(0, endMarker) + '\n' + schemaRules.replace('\n' + STORM_SKILL_MARKER, '') + '\n' + existing.substring(endMarker);
+ writeFileSync(rulesPath, updated);
+ appended.push(config.rulesFile);
+ }
+ }
+ }
+ }
+
+ // Fetch and install schema-dependent skills.
+ const schemaSkillNames = skillIndex?.schemaSkills ?? [];
+ if (config.skillPath) {
+ for (const skillName of schemaSkillNames) {
+ const content = await fetchSkill(skillName);
+ if (!content) { skipped.push(skillName + ' (fetch failed)'); continue; }
+ installSkill(skillName, content, config, created);
+ installedSkillNames.push(skillName);
+ }
+ }
+ }
+
+ // Step 7: Write project config and .gitignore.
+ writeProjectConfig(tools, ['kotlin']);
+
+ const gitignorePath = join(cwd, '.gitignore');
+ if (!existsSync(gitignorePath)) {
+ writeFileSync(gitignorePath, `# Storm config (machine-specific)\n${CONFIG_FILE}\n`);
+ created.push('.gitignore');
+ } else {
+ let gitignore = readFileSync(gitignorePath, 'utf-8');
+ if (!gitignore.includes(CONFIG_FILE)) {
+ appendFileSync(gitignorePath, `\n# Storm config (machine-specific)\n${CONFIG_FILE}\n`);
+ }
+ }
+
+ // Step 8: Clean stale skills.
+ if (config.skillPath) {
+ cleanStaleSkills([config], installedSkillNames, skipped);
+ }
+
+ // Summary.
+ const uniqueCreated = [...new Set(created)];
+ const uniqueAppended = [...new Set(appended)];
+
+ console.log();
+ if (uniqueCreated.length > 0) {
+ console.log(boltYellow(' Created:'));
+ uniqueCreated.forEach(f => console.log(boltYellow(` + ${f}`)));
+ }
+ if (uniqueAppended.length > 0) {
+ console.log(boltYellow(' Updated:'));
+ uniqueAppended.forEach(f => console.log(boltYellow(` ~ ${f}`)));
+ }
+ if (skipped.length > 0) {
+ console.log(dimText(' Skipped:'));
+ skipped.forEach(f => console.log(dimText(` - ${f}`)));
+ }
+
+ // Instructions.
+ console.log();
+ console.log(bold(" You're all set!"));
+ console.log();
+ if (tool === 'claude') {
+ console.log(` Start ${boltYellow('Claude Code')} in this directory and type:`);
+ console.log();
+ console.log(` ${bold('/storm-demo')}`);
+ } else {
+ console.log(` Open this directory in ${boltYellow(config.name)} and ask:`);
+ console.log();
+ console.log(` ${bold('Run the Storm demo')}`);
+ }
+ console.log();
+}
+
// ─── Entry ───────────────────────────────────────────────────────────────────
async function run() {
@@ -1657,12 +2061,14 @@ async function run() {
${dimText('Usage:')}
storm init Configure rules, skills, and database (default)
+ storm demo Create a demo project in an empty directory
storm update Update rules and skills (non-interactive)
storm mcp Re-register MCP servers for configured tools
${dimText('Options:')}
--help, -h Show this help message
--version, -v Show version
+ --dev Read skills from a local directory instead of orm.st
${dimText('Learn more:')} https://orm.st/ai
`);
@@ -1678,6 +2084,8 @@ async function run() {
await update();
} else if (command === 'mcp') {
await updateMcp();
+ } else if (command === 'demo') {
+ await demo();
} else {
await setup();
}
diff --git a/storm-core/src/main/java/module-info.java b/storm-core/src/main/java/module-info.java
index 0ffedc6bd..f74372b6d 100644
--- a/storm-core/src/main/java/module-info.java
+++ b/storm-core/src/main/java/module-info.java
@@ -15,7 +15,7 @@
exports st.orm.core.repository.impl;
requires java.management;
requires java.sql;
- requires jakarta.persistence;
+ requires static jakarta.persistence;
requires jakarta.annotation;
requires java.compiler;
requires storm.foundation;
diff --git a/storm-core/src/main/java/st/orm/core/repository/EntityRepository.java b/storm-core/src/main/java/st/orm/core/repository/EntityRepository.java
index 30d7549e9..31a2e5a06 100644
--- a/storm-core/src/main/java/st/orm/core/repository/EntityRepository.java
+++ b/storm-core/src/main/java/st/orm/core/repository/EntityRepository.java
@@ -990,25 +990,6 @@ default Window scroll(@Nonnull Scrollable scrollable) {
// Stream based methods.
- /**
- * Returns a stream of all entities of the type supported by this repository. Each element in the stream represents
- * an entity in the database, encapsulating all relevant data as mapped by the entity model.
- *
- * The resulting stream is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the stream. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the stream holds resources open
- * while in use, it must be closed after usage to prevent resource leaks. As the stream is {@code AutoCloseable}, it
- * is recommended to use it within a {@code try-with-resources} block.
- *
- * @return a stream of all entities of the type supported by this repository.
- * @throws PersistenceException if the selection operation fails due to underlying database issues, such as
- * connectivity.
- */
- Stream selectAll();
-
/**
* Retrieves a stream of entities based on their primary keys.
*
diff --git a/storm-core/src/main/java/st/orm/core/repository/ProjectionRepository.java b/storm-core/src/main/java/st/orm/core/repository/ProjectionRepository.java
index f781bd4c1..2841233a7 100644
--- a/storm-core/src/main/java/st/orm/core/repository/ProjectionRepository.java
+++ b/storm-core/src/main/java/st/orm/core/repository/ProjectionRepository.java
@@ -429,25 +429,6 @@ default Window scroll(@Nonnull Scrollable
scrollable) {
// Stream based methods.
- /**
- * Returns a stream of all projections of the type supported by this repository. Each element in the stream represents
- * a projection in the database, encapsulating all relevant data as mapped by the projection model.
- *
- *
The resulting stream is lazily loaded, meaning that the projections are only retrieved from the database as they
- * are consumed by the stream. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of projections.
- *
- * Note: Calling this method does trigger the execution of the underlying query, so it should
- * only be invoked when the query is intended to run. Since the stream holds resources open while in use, it must be
- * closed after usage to prevent resource leaks. As the stream is {@code AutoCloseable}, it is recommended to use it
- * within a {@code try-with-resources} block.
- *
- * @return a stream of all projections of the type supported by this repository.
- * @throws PersistenceException if the selection operation fails due to underlying database issues, such as
- * connectivity.
- */
- Stream selectAll();
-
/**
* Retrieves a stream of projections based on their primary keys.
*
diff --git a/storm-core/src/main/java/st/orm/core/repository/impl/BaseRepositoryImpl.java b/storm-core/src/main/java/st/orm/core/repository/impl/BaseRepositoryImpl.java
index c2fc8c218..97d273d4c 100644
--- a/storm-core/src/main/java/st/orm/core/repository/impl/BaseRepositoryImpl.java
+++ b/storm-core/src/main/java/st/orm/core/repository/impl/BaseRepositoryImpl.java
@@ -435,27 +435,6 @@ public List findAllByRef(@Nonnull Iterable[> refs) {
// Stream based methods. These methods operate in multiple batches.
- /**
- * Returns a stream of all entities of the type supported by this repository. Each element in the stream represents
- * an entity in the database, encapsulating all relevant data as mapped by the entity model.
- *
- * ]The resulting stream is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the stream. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying query, so it should
- * only be invoked when the query is intended to run. Since the stream holds resources open while in use, it must be
- * closed after usage to prevent resource leaks. As the stream is {@code AutoCloseable}, it is recommended to use it
- * within a {@code try-with-resources} block.
- *
- * @return a stream of all entities of the type supported by this repository.
- * @throws PersistenceException if the selection operation fails due to underlying database issues, such as
- * connectivity.
- */
- public Stream selectAll() {
- return select().getResultStream();
- }
-
/**
* Retrieves a stream of entities based on their primary keys.
*
diff --git a/storm-core/src/main/java/st/orm/core/spi/DefaultTransactionTemplateProviderImpl.java b/storm-core/src/main/java/st/orm/core/spi/DefaultTransactionTemplateProviderImpl.java
index d0178dfda..fa0a8e636 100644
--- a/storm-core/src/main/java/st/orm/core/spi/DefaultTransactionTemplateProviderImpl.java
+++ b/storm-core/src/main/java/st/orm/core/spi/DefaultTransactionTemplateProviderImpl.java
@@ -18,7 +18,6 @@
import static java.util.Optional.empty;
import jakarta.annotation.Nonnull;
-import jakarta.persistence.PersistenceException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
@@ -26,6 +25,7 @@
import java.util.Map;
import java.util.Optional;
import st.orm.Entity;
+import st.orm.PersistenceException;
import st.orm.core.spi.Orderable.AfterAny;
@AfterAny
diff --git a/storm-core/src/main/java/st/orm/core/spi/QueryFactory.java b/storm-core/src/main/java/st/orm/core/spi/QueryFactory.java
index c504129fd..aabc57c78 100644
--- a/storm-core/src/main/java/st/orm/core/spi/QueryFactory.java
+++ b/storm-core/src/main/java/st/orm/core/spi/QueryFactory.java
@@ -60,7 +60,7 @@ public interface QueryFactory {
/**
* Returns the {@link DataSource} backing this factory, or {@code null} if the factory was created from a raw
- * {@link java.sql.Connection} or JPA {@link jakarta.persistence.EntityManager}.
+ * {@link java.sql.Connection} or JPA {@code EntityManager}.
*
* @return the data source, or {@code null}.
* @since 1.9
diff --git a/storm-core/src/main/java/st/orm/core/spi/TransactionTemplate.java b/storm-core/src/main/java/st/orm/core/spi/TransactionTemplate.java
index a7317b49a..3564691cb 100644
--- a/storm-core/src/main/java/st/orm/core/spi/TransactionTemplate.java
+++ b/storm-core/src/main/java/st/orm/core/spi/TransactionTemplate.java
@@ -18,8 +18,8 @@
import static java.util.Optional.ofNullable;
import jakarta.annotation.Nonnull;
-import jakarta.persistence.PersistenceException;
import java.util.Optional;
+import st.orm.PersistenceException;
/**
* The transaction template is a functional interface that allows callers to let logic be executed in the scope of a
diff --git a/storm-core/src/main/java/st/orm/core/spi/TypeDiscovery.java b/storm-core/src/main/java/st/orm/core/spi/TypeDiscovery.java
index e7c236255..e6d832e7f 100644
--- a/storm-core/src/main/java/st/orm/core/spi/TypeDiscovery.java
+++ b/storm-core/src/main/java/st/orm/core/spi/TypeDiscovery.java
@@ -15,6 +15,7 @@
*/
package st.orm.core.spi;
+import jakarta.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -22,7 +23,6 @@
import java.util.*;
import st.orm.Converter;
import st.orm.Data;
-import st.orm.core.repository.Repository;
public final class TypeDiscovery {
@@ -55,25 +55,52 @@ public static List> getDataTypes() {
* The index is generated at compile time by the Storm metamodel processor (annotation processor or KSP).
* It contains all interfaces in the user's project that extend {@code EntityRepository} or
* {@code ProjectionRepository}.
+ *
+ * The type check is intentionally lenient: the index is already curated at compile time, and the
+ * repository interface may come from different modules ({@code st.orm.core.repository.Repository},
+ * {@code st.orm.repository.Repository} in Java 21 or Kotlin). Checking against a single base type
+ * would silently reject valid entries.
*/
- public static List> getRepositoryTypes() {
- return loadTypes(REPOSITORY_TYPE, Repository.class);
+ public static List> getRepositoryTypes() {
+ return loadClasses(REPOSITORY_TYPE);
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private static List> loadClasses(@Nonnull String typeFqName) {
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ if (classLoader == null) {
+ classLoader = TypeDiscovery.class.getClassLoader();
+ }
+ String resourceName = INDEX_DIRECTORY + typeFqName + ".idx";
+ List classNames = loadResourceLines(classLoader, resourceName);
+ if (classNames.isEmpty()) {
+ return List.of();
+ }
+ List> result = new ArrayList<>();
+ for (String fqClassName : new LinkedHashSet<>(classNames)) {
+ try {
+ result.add(Class.forName(fqClassName, false, classLoader));
+ } catch (Throwable ignore) {
+ // Skip bad entries or missing classes.
+ }
+ }
+ return result;
}
- private static List> loadTypes(String typeFqName, Class expectedType) {
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- if (cl == null) {
- cl = TypeDiscovery.class.getClassLoader();
+ private static List> loadTypes(@Nonnull String typeFqName, @Nonnull Class expectedType) {
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ if (classLoader == null) {
+ classLoader = TypeDiscovery.class.getClassLoader();
}
String resourceName = INDEX_DIRECTORY + typeFqName + ".idx";
- List classNames = loadResourceLines(cl, resourceName);
+ List classNames = loadResourceLines(classLoader, resourceName);
if (classNames.isEmpty()) {
return List.of();
}
List> result = new ArrayList<>();
- for (String fqcn : new LinkedHashSet<>(classNames)) {
+ for (String fqClassName : new LinkedHashSet<>(classNames)) {
try {
- Class> cls = Class.forName(fqcn, false, cl);
+ Class> cls = Class.forName(fqClassName, false, classLoader);
if (expectedType.isAssignableFrom(cls)) {
@SuppressWarnings("unchecked")
Class extends T> cast = (Class extends T>) cls;
@@ -86,9 +113,9 @@ private static List> loadTypes(String typeFqName, Class loadResourceLines(ClassLoader cl, String resourceName) {
+ private static List loadResourceLines(@Nonnull ClassLoader classLoader, @Nonnull String resourceName) {
try {
- Enumeration resources = cl.getResources(resourceName);
+ Enumeration resources = classLoader.getResources(resourceName);
if (!resources.hasMoreElements()) {
return List.of();
}
diff --git a/storm-core/src/main/java/st/orm/core/template/JpaTemplate.java b/storm-core/src/main/java/st/orm/core/template/JpaTemplate.java
index ac4712381..9aaa98ca9 100644
--- a/storm-core/src/main/java/st/orm/core/template/JpaTemplate.java
+++ b/storm-core/src/main/java/st/orm/core/template/JpaTemplate.java
@@ -19,6 +19,8 @@
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import st.orm.PersistenceException;
import st.orm.StormConfig;
import st.orm.core.spi.Provider;
import st.orm.core.template.impl.JpaTemplateImpl;
@@ -63,6 +65,23 @@ static ORMTemplate ORM(@Nonnull EntityManager entityManager) {
return new JpaTemplateImpl(entityManager).toORM();
}
+ /**
+ * Creates a new ORM template for the given entity manager, with a custom template decorator.
+ *
+ * @param entityManager the entity manager.
+ * @param decorator a function that transforms the {@link TemplateDecorator} to customize template processing.
+ * @return the ORM template.
+ */
+ static ORMTemplate ORM(@Nonnull EntityManager entityManager,
+ @Nonnull UnaryOperator decorator) {
+ var template = new JpaTemplateImpl(entityManager);
+ var decorated = decorator.apply(template);
+ if (!(decorated instanceof JpaTemplateImpl)) {
+ throw new PersistenceException("Decorator must return the same template type.");
+ }
+ return ((JpaTemplateImpl) decorated).toORM();
+ }
+
/**
* Creates a new ORM template for the given entity manager, configured with the provided {@link StormConfig}.
*
@@ -74,6 +93,25 @@ static ORMTemplate ORM(@Nonnull EntityManager entityManager, @Nonnull StormConfi
return new JpaTemplateImpl(entityManager, config).toORM();
}
+ /**
+ * Creates a new ORM template for the given entity manager, configured with the provided {@link StormConfig}
+ * and a custom template decorator.
+ *
+ * @param entityManager the entity manager.
+ * @param config the Storm configuration to apply.
+ * @param decorator a function that transforms the {@link TemplateDecorator} to customize template processing.
+ * @return the ORM template.
+ */
+ static ORMTemplate ORM(@Nonnull EntityManager entityManager, @Nonnull StormConfig config,
+ @Nonnull UnaryOperator decorator) {
+ var template = new JpaTemplateImpl(entityManager, config);
+ var decorated = decorator.apply(template);
+ if (!(decorated instanceof JpaTemplateImpl)) {
+ throw new PersistenceException("Decorator must return the same template type.");
+ }
+ return ((JpaTemplateImpl) decorated).toORM();
+ }
+
/**
* Returns an ORM template for this JPA template.
*/
diff --git a/storm-core/src/main/java/st/orm/core/template/ORMTemplate.java b/storm-core/src/main/java/st/orm/core/template/ORMTemplate.java
index 29480cb7d..39ea6bd7d 100644
--- a/storm-core/src/main/java/st/orm/core/template/ORMTemplate.java
+++ b/storm-core/src/main/java/st/orm/core/template/ORMTemplate.java
@@ -16,7 +16,6 @@
package st.orm.core.template;
import jakarta.annotation.Nonnull;
-import jakarta.persistence.EntityManager;
import java.sql.Connection;
import java.util.List;
import java.util.function.UnaryOperator;
@@ -28,7 +27,6 @@
import st.orm.core.repository.EntityRepository;
import st.orm.core.repository.ProjectionRepository;
import st.orm.core.repository.RepositoryLookup;
-import st.orm.core.template.impl.JpaTemplateImpl;
import st.orm.core.template.impl.PreparedStatementTemplateImpl;
import st.orm.mapping.TemplateDecorator;
@@ -96,7 +94,7 @@ default ORMTemplate withEntityCallback(@Nonnull EntityCallback> callback) {
* confirmation message and returns an empty list.
*
* This method requires a DataSource-backed template. Templates created from a raw
- * {@link Connection} or {@link jakarta.persistence.EntityManager} do not support schema validation.
+ * {@link Connection} or {@code EntityManager} do not support schema validation.
*
* @return the list of validation error messages (empty on success).
* @throws PersistenceException if the template does not support schema validation.
@@ -113,7 +111,7 @@ default List validateSchema() {
* confirmation message and returns an empty list.
*
* This method requires a DataSource-backed template. Templates created from a raw
- * {@link Connection} or {@link jakarta.persistence.EntityManager} do not support schema validation.
+ * {@link Connection} or {@code EntityManager} do not support schema validation.
*
* @param types the entity and projection types to validate.
* @return the list of validation error messages (empty on success).
@@ -128,7 +126,7 @@ default List validateSchema(@Nonnull Iterable> typ
* Validates all discovered types and throws if any errors are found.
*
* This method requires a DataSource-backed template. Templates created from a raw
- * {@link Connection} or {@link jakarta.persistence.EntityManager} do not support schema validation.
+ * {@link Connection} or {@code EntityManager} do not support schema validation.
*
* @throws PersistenceException if validation fails or the template does not support schema validation.
* @since 1.9
@@ -141,7 +139,7 @@ default void validateSchemaOrThrow() {
* Validates the specified types and throws if any errors are found.
*
* This method requires a DataSource-backed template. Templates created from a raw
- * {@link Connection} or {@link jakarta.persistence.EntityManager} do not support schema validation.
+ * {@link Connection} or {@code EntityManager} do not support schema validation.
*
* @param types the entity and projection types to validate.
* @throws PersistenceException if validation fails or the template does not support schema validation.
@@ -151,59 +149,6 @@ default void validateSchemaOrThrow(@Nonnull Iterable> type
throw new PersistenceException("Schema validation is not supported by this template.");
}
- /**
- * Returns an {@link ORMTemplate} for use with JPA.
- *
- * This method creates an ORM repository template using the provided {@link EntityManager}.
- * It allows you to perform database operations using JPA in a type-safe manner.
- *
- *
Example usage:
- *
{@code
- * EntityManager entityManager = ...;
- * ORMTemplate orm = Templates.ORM(entityManager);
- * List otherTables = orm.query(RAW."""
- * SELECT \{MyTable.class}
- * FROM \{MyTable.class}
- * WHERE \{MyTable_.name} = \{"ABC"}""")
- * .getResultList(MyTable.class);
- * }
- *
- * @param entityManager the {@link EntityManager} to use for database operations; must not be {@code null}.
- * @return an {@link ORMTemplate} configured for use with JPA.
- */
- static ORMTemplate of(@Nonnull EntityManager entityManager) {
- return new JpaTemplateImpl(entityManager).toORM();
- }
-
- /**
- * Returns an {@link ORMTemplate} for use with JPA.
- *
- * This method creates an ORM repository template using the provided {@link EntityManager}.
- * It allows you to perform database operations using JPA in a type-safe manner.
- *
- *
Example usage:
- *
{@code
- * EntityManager entityManager = ...;
- * ORMTemplate orm = Templates.ORM(entityManager);
- * List otherTables = orm.query(RAW."""
- * SELECT \{MyTable.class}
- * FROM \{MyTable.class}
- * WHERE \{MyTable_.name} = \{"ABC"}""")
- * .getResultList(MyTable.class);
- * }
- *
- * @param entityManager the {@link EntityManager} to use for database operations; must not be {@code null}.
- * @return an {@link ORMTemplate} configured for use with JPA.
- */
- static ORMTemplate of(@Nonnull EntityManager entityManager, @Nonnull UnaryOperator decorator) {
- var template = new JpaTemplateImpl(entityManager);
- var decorated = decorator.apply(template);
- if (!(decorated instanceof JpaTemplateImpl)) {
- throw new PersistenceException("Decorator must return the same template type.");
- }
- return ((JpaTemplateImpl) decorated).toORM();
- }
-
/**
* Returns an {@link ORMTemplate} for use with JDBC.
*
@@ -317,36 +262,6 @@ static ORMTemplate of(@Nonnull Connection connection,
return ((PreparedStatementTemplateImpl) decorated).toORM();
}
- /**
- * Returns an {@link ORMTemplate} for use with JPA, configured with the provided {@link StormConfig}.
- *
- * @param entityManager the {@link EntityManager} to use for database operations; must not be {@code null}.
- * @param config the Storm configuration to apply; must not be {@code null}.
- * @return an {@link ORMTemplate} configured for use with JPA.
- */
- static ORMTemplate of(@Nonnull EntityManager entityManager, @Nonnull StormConfig config) {
- return new JpaTemplateImpl(entityManager, config).toORM();
- }
-
- /**
- * Returns an {@link ORMTemplate} for use with JPA, configured with the provided {@link StormConfig} and a custom
- * template decorator.
- *
- * @param entityManager the {@link EntityManager} to use for database operations; must not be {@code null}.
- * @param config the Storm configuration to apply; must not be {@code null}.
- * @param decorator a function that transforms the {@link TemplateDecorator} to customize template processing.
- * @return an {@link ORMTemplate} configured for use with JPA.
- */
- static ORMTemplate of(@Nonnull EntityManager entityManager, @Nonnull StormConfig config,
- @Nonnull UnaryOperator decorator) {
- var template = new JpaTemplateImpl(entityManager, config);
- var decorated = decorator.apply(template);
- if (!(decorated instanceof JpaTemplateImpl)) {
- throw new PersistenceException("Decorator must return the same template type.");
- }
- return ((JpaTemplateImpl) decorated).toORM();
- }
-
/**
* Returns an {@link ORMTemplate} for use with JDBC, configured with the provided {@link StormConfig}.
*
diff --git a/storm-core/src/main/java/st/orm/core/template/Templates.java b/storm-core/src/main/java/st/orm/core/template/Templates.java
index 4712f5e54..54227dbdb 100644
--- a/storm-core/src/main/java/st/orm/core/template/Templates.java
+++ b/storm-core/src/main/java/st/orm/core/template/Templates.java
@@ -22,7 +22,6 @@
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
-import jakarta.persistence.EntityManager;
import java.sql.Connection;
import java.util.Arrays;
import java.util.Calendar;
@@ -71,8 +70,8 @@
* tables, aliases, and more.
*
* Additionally, the {@code Templates} interface provides methods to create {@link ORMTemplate}
- * instances for use with different data sources like JPA's {@link EntityManager}, JDBC's {@link DataSource}, or
- * {@link Connection}.
+ * instances for use with different data sources like JDBC's {@link DataSource} or {@link Connection}, or
+ * JPA's {@code EntityManager} (see {@link JpaTemplate}).
*
*
Using Templates
*
diff --git a/storm-core/src/test/java/st/orm/core/EntityRepositoryIntegrationTest.java b/storm-core/src/test/java/st/orm/core/EntityRepositoryIntegrationTest.java
index 92c711f4d..40c4374ee 100644
--- a/storm-core/src/test/java/st/orm/core/EntityRepositoryIntegrationTest.java
+++ b/storm-core/src/test/java/st/orm/core/EntityRepositoryIntegrationTest.java
@@ -721,7 +721,7 @@ public void testExistsByRef() {
public void testSelectAll() {
var orm = ORMTemplate.of(dataSource);
var cities = orm.entity(City.class);
- try (var stream = cities.selectAll()) {
+ try (var stream = cities.select().getResultStream()) {
long count = stream.count();
assertEquals(cities.count(), count);
}
diff --git a/storm-core/src/test/java/st/orm/core/JpaIntegrationTest.java b/storm-core/src/test/java/st/orm/core/JpaIntegrationTest.java
index 033eb1a82..934438a41 100644
--- a/storm-core/src/test/java/st/orm/core/JpaIntegrationTest.java
+++ b/storm-core/src/test/java/st/orm/core/JpaIntegrationTest.java
@@ -243,34 +243,34 @@ void jpaTemplate_toORM() {
@Test
void ormTemplate_of_entityManager_basic() {
- ORMTemplate orm = ORMTemplate.of(entityManager);
+ ORMTemplate orm = JpaTemplate.ORM(entityManager);
assertNotNull(orm);
}
@Test
void ormTemplate_of_entityManager_withConfig() {
StormConfig config = StormConfig.of(Map.of());
- ORMTemplate orm = ORMTemplate.of(entityManager, config);
+ ORMTemplate orm = JpaTemplate.ORM(entityManager, config);
assertNotNull(orm);
assertEquals(config, orm.config());
}
@Test
void ormTemplate_of_entityManager_withDecorator() {
- ORMTemplate orm = ORMTemplate.of(entityManager, t -> t);
+ ORMTemplate orm = JpaTemplate.ORM(entityManager, t -> t);
assertNotNull(orm);
}
@Test
void ormTemplate_of_entityManager_withDecorator_customTableNameResolver() {
- ORMTemplate orm = ORMTemplate.of(entityManager, t -> t.withTableNameResolver(null));
+ ORMTemplate orm = JpaTemplate.ORM(entityManager, t -> t.withTableNameResolver(null));
assertNotNull(orm);
}
@Test
void ormTemplate_of_entityManager_withConfigAndDecorator() {
StormConfig config = StormConfig.of(Map.of());
- ORMTemplate orm = ORMTemplate.of(entityManager, config, t -> t);
+ ORMTemplate orm = JpaTemplate.ORM(entityManager, config, t -> t);
assertNotNull(orm);
assertEquals(config, orm.config());
}
@@ -279,14 +279,14 @@ void ormTemplate_of_entityManager_withConfigAndDecorator() {
void ormTemplate_of_entityManager_decoratorMustReturnSameType() {
// Decorator that returns a non-JpaTemplateImpl should throw.
assertThrows(st.orm.PersistenceException.class, () ->
- ORMTemplate.of(entityManager, t -> new TemplateDecoratorStub()));
+ JpaTemplate.ORM(entityManager, t -> new TemplateDecoratorStub()));
}
@Test
void ormTemplate_of_entityManager_configAndDecorator_decoratorMustReturnSameType() {
StormConfig config = StormConfig.of(Map.of());
assertThrows(st.orm.PersistenceException.class, () ->
- ORMTemplate.of(entityManager, config, t -> new TemplateDecoratorStub()));
+ JpaTemplate.ORM(entityManager, config, t -> new TemplateDecoratorStub()));
}
@Test
diff --git a/storm-core/src/test/java/st/orm/core/RepositoryPreparedStatementIntegrationTest.java b/storm-core/src/test/java/st/orm/core/RepositoryPreparedStatementIntegrationTest.java
index 540d4d53c..e841f4653 100644
--- a/storm-core/src/test/java/st/orm/core/RepositoryPreparedStatementIntegrationTest.java
+++ b/storm-core/src/test/java/st/orm/core/RepositoryPreparedStatementIntegrationTest.java
@@ -358,10 +358,10 @@ public void testInsertReturningIdsCompoundPk() {
@Test
public void testUpdateList() {
var repository = ORMTemplate.of(dataSource).entity(Vet.class);
- try (var stream1 = repository.selectAll()) {
+ try (var stream1 = repository.select().getResultStream()) {
var list1 = stream1.toList();
repository.update(list1);
- try (var stream2 = repository.selectAll()) {
+ try (var stream2 = repository.select().getResultStream()) {
var list2 = stream2.toList();
assertEquals(list1, list2);
}
@@ -675,7 +675,7 @@ void testInsertNullablePetRefWithNonnull() {
@Test
public void testSelectNullOwnerRef() {
- try (var stream = ORMTemplate.of(dataSource).entity(PetWithNullableOwnerRef.class).selectAll()) {
+ try (var stream = ORMTemplate.of(dataSource).entity(PetWithNullableOwnerRef.class).select().getResultStream()) {
var pet = stream.filter(p -> p.name().equals("Sly")).findFirst().orElseThrow();
assertNull(pet.owner());
}
@@ -1240,7 +1240,7 @@ public void deleteAll() {
@Test
public void deleteBatch() {
var repo = ORMTemplate.of(dataSource).entity(Visit.class);
- try (var stream = repo.selectAll()) {
+ try (var stream = repo.select().getResultStream()) {
repo.delete(stream);
}
assertEquals(0, repo.count());
@@ -1249,7 +1249,7 @@ public void deleteBatch() {
@Test
public void deleteRefBatch() {
var repo = ORMTemplate.of(dataSource).entity(Visit.class);
- try (var stream = repo.selectAll().map(Ref::of)) {
+ try (var stream = repo.select().getResultStream().map(Ref::of)) {
repo.deleteByRef(stream);
}
assertEquals(0, repo.count());
diff --git a/storm-foundation/src/main/java/module-info.java b/storm-foundation/src/main/java/module-info.java
index d30053ebd..c180e487c 100644
--- a/storm-foundation/src/main/java/module-info.java
+++ b/storm-foundation/src/main/java/module-info.java
@@ -1,6 +1,6 @@
module storm.foundation {
exports st.orm;
exports st.orm.mapping;
- requires jakarta.persistence;
+ requires static jakarta.persistence;
requires jakarta.annotation;
}
diff --git a/storm-java21/src/main/java/module-info.java b/storm-java21/src/main/java/module-info.java
index 491b9debf..0742bbd6e 100644
--- a/storm-java21/src/main/java/module-info.java
+++ b/storm-java21/src/main/java/module-info.java
@@ -2,7 +2,7 @@
exports st.orm.repository;
exports st.orm.template;
requires java.sql;
- requires jakarta.persistence;
+ requires static jakarta.persistence;
requires jakarta.annotation;
requires java.compiler;
requires storm.foundation;
diff --git a/storm-java21/src/main/java/st/orm/repository/EntityRepository.java b/storm-java21/src/main/java/st/orm/repository/EntityRepository.java
index 2224990ed..ca22c1b11 100644
--- a/storm-java21/src/main/java/st/orm/repository/EntityRepository.java
+++ b/storm-java21/src/main/java/st/orm/repository/EntityRepository.java
@@ -943,48 +943,6 @@ default Window scroll(@Nonnull Scrollable scrollable) {
// processed. The BatchCallback approach prevents the caller from accidentally misusing the API.
//
- /**
- * Returns a stream of all entities of the type supported by this repository. Each element in the stream represents
- * an entity in the database, encapsulating all relevant data as mapped by the entity model.
- *
- * The resulting stream is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the stream. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the stream holds resources open
- * while in use, it must be closed after usage to prevent resource leaks. As the stream is {@code AutoCloseable}, it
- * is recommended to use it within a {@code try-with-resources} block.
- *
- * @return a stream of all entities of the type supported by this repository.
- * @throws PersistenceException if the selection operation fails due to underlying database issues, such as
- * connectivity.
- */
- Stream selectAll();
-
- /**
- * Returns a stream of refs to all entities of the type supported by this repository. Each element in the stream
- * represents a lightweight reference to an entity in the database, containing only the primary key.
- *
- * This method is useful when you need to retrieve all entity identifiers without loading the full entity data.
- * The complete entity can be fetched on demand by calling {@link Ref#fetch()} on any of the returned refs.
- *
- * The resulting stream is lazily loaded, meaning that the refs are only retrieved from the database as they
- * are consumed by the stream. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the stream holds resources open
- * while in use, it must be closed after usage to prevent resource leaks. As the stream is {@code AutoCloseable}, it
- * is recommended to use it within a {@code try-with-resources} block.
- *
- * @return a stream of refs to all entities of the type supported by this repository.
- * @throws PersistenceException if the selection operation fails due to underlying database issues, such as
- * connectivity.
- * @since 1.3
- */
- Stream[> selectAllRef();
-
/**
* Retrieves a stream of entities based on their primary keys.
*
diff --git a/storm-java21/src/main/java/st/orm/repository/ProjectionRepository.java b/storm-java21/src/main/java/st/orm/repository/ProjectionRepository.java
index b755b0d05..28d36f58f 100644
--- a/storm-java21/src/main/java/st/orm/repository/ProjectionRepository.java
+++ b/storm-java21/src/main/java/st/orm/repository/ProjectionRepository.java
@@ -424,25 +424,6 @@ default Window] scroll(@Nonnull Scrollable
scrollable) {
// processed. The BatchCallback approach prevents the caller from accidentally misusing the API.
//
- /**
- * Returns a stream of all projections of the type supported by this repository. Each element in the stream represents
- * a projection in the database, encapsulating all relevant data as mapped by the projection model.
- *
- *
The resulting stream is lazily loaded, meaning that the projections are only retrieved from the database as they
- * are consumed by the stream. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of projections.
- *
- * Note: Calling this method does trigger the execution of the underlying query, so it should
- * only be invoked when the query is intended to run. Since the stream holds resources open while in use, it must be
- * closed after usage to prevent resource leaks. As the stream is {@code AutoCloseable}, it is recommended to use it
- * within a {@code try-with-resources} block.
- *
- * @return a stream of all projections of the type supported by this repository.
- * @throws PersistenceException if the selection operation fails due to underlying database issues, such as
- * connectivity.
- */
- Stream selectAll();
-
/**
* Retrieves a stream of projections based on their primary keys.
*
diff --git a/storm-java21/src/main/java/st/orm/repository/impl/EntityRepositoryImpl.java b/storm-java21/src/main/java/st/orm/repository/impl/EntityRepositoryImpl.java
index 56e2f1440..decbfc705 100644
--- a/storm-java21/src/main/java/st/orm/repository/impl/EntityRepositoryImpl.java
+++ b/storm-java21/src/main/java/st/orm/repository/impl/EntityRepositoryImpl.java
@@ -319,16 +319,6 @@ public void deleteByRef(@Nonnull Iterable[> refs) {
core.deleteByRef(refs);
}
- @Override
- public Stream selectAll() {
- return core.selectAll();
- }
-
- @Override
- public Stream][> selectAllRef() {
- return selectRef().getResultStream();
- }
-
@Override
public Stream selectById(@Nonnull Stream ids) {
return core.selectById(ids);
diff --git a/storm-java21/src/main/java/st/orm/repository/impl/ProjectionRepositoryImpl.java b/storm-java21/src/main/java/st/orm/repository/impl/ProjectionRepositoryImpl.java
index e78249a96..50e8eecf6 100644
--- a/storm-java21/src/main/java/st/orm/repository/impl/ProjectionRepositoryImpl.java
+++ b/storm-java21/src/main/java/st/orm/repository/impl/ProjectionRepositoryImpl.java
@@ -196,11 +196,6 @@ public List] findAllByRef(@Nonnull Iterable[> refs) {
return core.findAllByRef(refs);
}
- @Override
- public Stream] selectAll() {
- return core.selectAll();
- }
-
@Override
public Stream
selectById(@Nonnull Stream ids) {
return core.selectById(ids);
diff --git a/storm-java21/src/main/java/st/orm/template/ORMTemplate.java b/storm-java21/src/main/java/st/orm/template/ORMTemplate.java
index 8aba94543..92572bf5d 100644
--- a/storm-java21/src/main/java/st/orm/template/ORMTemplate.java
+++ b/storm-java21/src/main/java/st/orm/template/ORMTemplate.java
@@ -16,7 +16,6 @@
package st.orm.template;
import jakarta.annotation.Nonnull;
-import jakarta.persistence.EntityManager;
import java.sql.Connection;
import java.util.List;
import java.util.function.UnaryOperator;
@@ -38,9 +37,9 @@
* {@link RepositoryLookup} (for obtaining type-safe {@link EntityRepository} and {@link ProjectionRepository}
* instances). It is the central interface from which all database operations originate.
*
- * Instances are created using the static factory methods {@link #of(jakarta.persistence.EntityManager)},
- * {@link #of(javax.sql.DataSource)}, or {@link #of(java.sql.Connection)}, or via the convenience methods
- * in the {@link Templates} class.
+ * Instances are created using the static factory methods {@link #of(javax.sql.DataSource)} or
+ * {@link #of(java.sql.Connection)}, or via the convenience methods in the {@link Templates} class.
+ * For JPA-based usage, see {@code JpaTemplate}.
*
* Example
* {@code
@@ -98,7 +97,7 @@ public interface ORMTemplate extends QueryTemplate, RepositoryLookup {
* confirmation message and returns an empty list.
*
* This method requires a DataSource-backed template. Templates created from a raw
- * {@link Connection} or {@link EntityManager} do not support schema validation.
+ * {@link Connection} or {@code EntityManager} do not support schema validation.
*
* @return the list of validation error messages (empty on success).
* @throws st.orm.PersistenceException if the template does not support schema validation.
@@ -113,7 +112,7 @@ public interface ORMTemplate extends QueryTemplate, RepositoryLookup {
* confirmation message and returns an empty list.
*
* This method requires a DataSource-backed template. Templates created from a raw
- * {@link Connection} or {@link EntityManager} do not support schema validation.
+ * {@link Connection} or {@code EntityManager} do not support schema validation.
*
* @param types the entity and projection types to validate.
* @return the list of validation error messages (empty on success).
@@ -126,7 +125,7 @@ public interface ORMTemplate extends QueryTemplate, RepositoryLookup {
* Validates all discovered types and throws if any errors are found.
*
* This method requires a DataSource-backed template. Templates created from a raw
- * {@link Connection} or {@link EntityManager} do not support schema validation.
+ * {@link Connection} or {@code EntityManager} do not support schema validation.
*
* @throws st.orm.PersistenceException if validation fails or the template does not support schema validation.
* @since 1.9
@@ -137,7 +136,7 @@ public interface ORMTemplate extends QueryTemplate, RepositoryLookup {
* Validates the specified types and throws if any errors are found.
*
* This method requires a DataSource-backed template. Templates created from a raw
- * {@link Connection} or {@link EntityManager} do not support schema validation.
+ * {@link Connection} or {@code EntityManager} do not support schema validation.
*
* @param types the entity and projection types to validate.
* @throws st.orm.PersistenceException if validation fails or the template does not support schema validation.
@@ -145,30 +144,6 @@ public interface ORMTemplate extends QueryTemplate, RepositoryLookup {
*/
void validateSchemaOrThrow(@Nonnull Iterable> types);
- /**
- * Returns an {@link ORMTemplate} for use with JPA.
- *
- * This method creates an ORM repository template using the provided {@link EntityManager}.
- * It allows you to perform database operations using JPA in a type-safe manner.
- *
- *
Example usage:
- *
{@code
- * EntityManager entityManager = ...;
- * ORMTemplate orm = ORMTemplate.of(entityManager);
- * List otherTables = orm.query(RAW."""
- * SELECT \{MyTable.class}
- * FROM \{MyTable.class}
- * WHERE \{MyTable_.name} = \{"ABC"}""")
- * .getResultList(MyTable.class);
- * }
- *
- * @param entityManager the {@link EntityManager} to use for database operations; must not be {@code null}.
- * @return an {@link ORMTemplate} configured for use with JPA.
- */
- static ORMTemplate of(@Nonnull EntityManager entityManager) {
- return new ORMTemplateImpl(st.orm.core.template.ORMTemplate.of(entityManager));
- }
-
/**
* Returns an {@link ORMTemplate} for use with JDBC.
*
@@ -220,21 +195,6 @@ static ORMTemplate of(@Nonnull Connection connection) {
return new ORMTemplateImpl(st.orm.core.template.ORMTemplate.of(connection));
}
- /**
- * Returns an {@link ORMTemplate} for use with JPA, with a custom template decorator.
- *
- * This method creates an ORM repository template using the provided {@link EntityManager} and applies
- * the specified decorator to customize template processing behavior.
- *
- * @param entityManager the {@link EntityManager} to use for database operations; must not be {@code null}.
- * @param decorator a function that transforms the {@link TemplateDecorator} to customize template processing.
- * @return an {@link ORMTemplate} configured for use with JPA.
- */
- static ORMTemplate of(@Nonnull EntityManager entityManager, @Nonnull UnaryOperator decorator) {
- return new ORMTemplateImpl(st.orm.core.template.ORMTemplate.of(entityManager, decorator));
-
- }
-
/**
* Returns an {@link ORMTemplate} for use with JDBC, with a custom template decorator.
*
@@ -265,31 +225,6 @@ static ORMTemplate of(@Nonnull Connection connection, @Nonnull UnaryOperator decorator) {
- return new ORMTemplateImpl(st.orm.core.template.ORMTemplate.of(entityManager, config, decorator));
- }
-
/**
* Returns an {@link ORMTemplate} for use with JDBC, configured with the provided {@link StormConfig}.
*
diff --git a/storm-java21/src/main/java/st/orm/template/Templates.java b/storm-java21/src/main/java/st/orm/template/Templates.java
index a940e57a9..ec0e0a10b 100644
--- a/storm-java21/src/main/java/st/orm/template/Templates.java
+++ b/storm-java21/src/main/java/st/orm/template/Templates.java
@@ -23,7 +23,6 @@
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
-import jakarta.persistence.EntityManager;
import java.sql.Connection;
import java.util.Arrays;
import java.util.Calendar;
@@ -72,8 +71,8 @@
* tables, aliases, and more.
*
* Additionally, the {@code Templates} interface provides methods to create {@link ORMTemplate}
- * instances for use with different data sources like JPA's {@link EntityManager}, JDBC's {@link DataSource}, or
- * {@link Connection}.
+ * instances for use with different data sources like JDBC's {@link DataSource} or {@link Connection}, or
+ * JPA's {@code EntityManager} (see {@code JpaTemplate}).
*
*
Using Templates
*
diff --git a/storm-java21/src/test/java/st/orm/template/ORMTemplateTest.java b/storm-java21/src/test/java/st/orm/template/ORMTemplateTest.java
index 408f3e57f..1ace0b056 100644
--- a/storm-java21/src/test/java/st/orm/template/ORMTemplateTest.java
+++ b/storm-java21/src/test/java/st/orm/template/ORMTemplateTest.java
@@ -307,7 +307,7 @@ public void testEntityFindAllRef() {
@Test
public void testEntitySelectAll() {
EntityRepository cities = orm.entity(City.class);
- try (Stream stream = cities.selectAll()) {
+ try (Stream stream = cities.select().getResultStream()) {
assertEquals(6, stream.count());
}
}
@@ -315,7 +315,7 @@ public void testEntitySelectAll() {
@Test
public void testEntitySelectAllRef() {
EntityRepository cities = orm.entity(City.class);
- try (Stream[> stream = cities.selectAllRef()) {
+ try (Stream][> stream = cities.selectRef().getResultStream()) {
assertEquals(6, stream.count());
}
}
@@ -658,7 +658,7 @@ public void testProjectionFindAllByRef() {
@Test
public void testProjectionSelectAll() {
ProjectionRepository views = orm.projection(OwnerView.class);
- try (Stream stream = views.selectAll()) {
+ try (Stream stream = views.select().getResultStream()) {
assertEquals(10, stream.count());
}
}
diff --git a/storm-kotlin/src/main/java/module-info.java b/storm-kotlin/src/main/java/module-info.java
index 7e38bd426..2124f92d2 100644
--- a/storm-kotlin/src/main/java/module-info.java
+++ b/storm-kotlin/src/main/java/module-info.java
@@ -5,7 +5,7 @@
exports st.orm.repository;
exports st.orm.template.impl to kotlin.reflect;
requires java.sql;
- requires jakarta.persistence;
+ requires static jakarta.persistence;
requires jakarta.annotation;
requires kotlin.reflect;
requires kotlin.stdlib;
diff --git a/storm-kotlin/src/main/kotlin/st/orm/repository/EntityRepository.kt b/storm-kotlin/src/main/kotlin/st/orm/repository/EntityRepository.kt
index dc3282a84..e0e0837ba 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/repository/EntityRepository.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/repository/EntityRepository.kt
@@ -224,6 +224,28 @@ interface EntityRepository : Repository where E : Entity {
*/
fun select(): QueryBuilder
+ /**
+ * Constructs a SELECT query using a block-based DSL.
+ *
+ * A [PredicateBuilder] returned as the block's last expression is automatically applied as a WHERE clause,
+ * so `select { path eq value }` is equivalent to `select { where(path eq value) }`.
+ *
+ * ```kotlin
+ * interface UserRepository : EntityRepository {
+ * fun findActive(): List = select {
+ * where(User_.active eq true)
+ * orderBy(User_.name)
+ * }.resultList
+ * }
+ * ```
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun select(block: SqlScope.() -> Any?): QueryBuilder {
+ val scope = SqlScope(select())
+ scope.applyResult(scope.block())
+ return scope.builder
+ }
+
/**
* Creates a new query builder for the entity type managed by this repository.
*
@@ -296,6 +318,29 @@ interface EntityRepository : Repository where E : Entity {
*/
fun delete(): QueryBuilder
+ /**
+ * Constructs and executes a DELETE statement using a block-based DSL.
+ *
+ * A [PredicateBuilder] returned as the block's last expression is automatically applied as a WHERE clause,
+ * so `delete { path eq value }` is equivalent to `delete { where(path eq value) }`.
+ *
+ * ```kotlin
+ * interface UserRepository : EntityRepository {
+ * fun deleteInactive(): Int = delete {
+ * where(User_.active eq false)
+ * }
+ * }
+ * ```
+ *
+ * @return the number of rows deleted.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun delete(block: SqlScope.() -> Any?): Int {
+ val scope = SqlScope(delete() as QueryBuilder)
+ scope.applyResult(scope.block())
+ return scope.builder.executeUpdate()
+ }
+
// Base methods.
/**
@@ -901,25 +946,6 @@ interface EntityRepository : Repository where E : Entity {
*/
fun deleteByRef(refs: Iterable][>)
- /**
- * Returns a flow of all entities of the type supported by this repository. Each element in the flow represents
- * an entity in the database, encapsulating all relevant data as mapped by the entity model.
- *
- * The resulting flow is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the flow. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * **Note:** Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the flow holds resources open
- * while in use, it must be closed after usage to prevent resource leaks. As the flow is `AutoCloseable`, it
- * is recommended to use it within a `try-with-resources` block.
- *
- * @return a flow of all entities of the type supported by this repository.
- * @throws st.orm.PersistenceException if the selection operation fails due to underlying database issues, such as
- * connectivity.
- */
- fun selectAll(): Flow
-
/**
* Retrieves a flow of entities based on their primary keys.
*
@@ -1468,21 +1494,6 @@ interface EntityRepository : Repository where E : Entity {
*/
fun findAllRef(): List][> = selectRef().resultList
- /**
- * Retrieves all entities of type [E] from the repository.
- *
- * The resulting sequence is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the sequence. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the sequence holds resources open
- * while in use, it must be closed after usage to prevent resource leaks.
- *
- * @return a sequence containing all entities.
- */
- fun selectAllRef(): Flow][> = selectRef().resultFlow
-
/**
* Retrieves an optional entity of type [E] based on a single field and its value.
* Returns null if no matching entity is found.
@@ -1794,18 +1805,6 @@ interface EntityRepository : Repository where E : Entity {
*/
fun findAll(predicate: PredicateBuilder): List = select().where(predicate).resultList
- /**
- * Retrieves entities of type [E] matching the specified predicate lambda.
- *
- * Example:
- * ```kotlin
- * val entities = repository.findAll { Entity_.active eq true }
- * ```
- *
- * @return a list of matching entities.
- */
- fun findAll(predicate: () -> PredicateBuilder): List = findAll(predicate())
-
/**
* Retrieves entities of type [E] matching the specified predicate.
*
@@ -1813,18 +1812,6 @@ interface EntityRepository : Repository where E : Entity {
*/
fun findAllRef(predicate: PredicateBuilder): List][> = selectRef().where(predicate).resultList
- /**
- * Retrieves entity references of type [E] matching the specified predicate lambda.
- *
- * Example:
- * ```kotlin
- * val refs = repository.findAllRef { Entity_.active eq true }
- * ```
- *
- * @return a list of matching entity references.
- */
- fun findAllRef(predicate: () -> PredicateBuilder): List][> = findAllRef(predicate())
-
/**
* Retrieves an optional entity of type [E] matching the specified predicate.
* Returns null if no matching entity is found.
@@ -1835,19 +1822,6 @@ interface EntityRepository : Repository where E : Entity {
predicate: PredicateBuilder,
): E? = select().where(predicate).optionalResult
- /**
- * Retrieves an optional entity of type [E] matching the specified predicate lambda.
- * Returns null if no matching entity is found.
- *
- * Example:
- * ```kotlin
- * val entity = repository.find { Entity_.name eq "Storm" }
- * ```
- *
- * @return an optional entity, or null if none found.
- */
- fun find(predicate: () -> PredicateBuilder): E? = find(predicate())
-
/**
* Retrieves an optional entity of type [E] matching the specified predicate.
* Returns a ref with a null value if no matching entity is found.
@@ -1858,19 +1832,6 @@ interface EntityRepository : Repository where E : Entity {
predicate: PredicateBuilder,
): Ref? = selectRef().where(predicate).optionalResult
- /**
- * Retrieves an optional entity reference of type [E] matching the specified predicate lambda.
- * Returns null if no matching entity is found.
- *
- * Example:
- * ```kotlin
- * val ref = repository.findRef { Entity_.name eq "Storm" }
- * ```
- *
- * @return an optional entity reference, or null if none found.
- */
- fun findRef(predicate: () -> PredicateBuilder): Ref? = findRef(predicate())
-
/**
* Retrieves a single entity of type [E] matching the specified predicate.
* Throws an exception if no entity or more than one entity is found.
@@ -1883,21 +1844,6 @@ interface EntityRepository : Repository where E : Entity {
predicate: PredicateBuilder,
): E = select().where(predicate).singleResult
- /**
- * Retrieves a single entity of type [E] matching the specified predicate lambda.
- * Throws an exception if no entity or more than one entity is found.
- *
- * Example:
- * ```kotlin
- * val entity = repository.get { Entity_.name eq "Storm" }
- * ```
- *
- * @return the matching entity.
- * @throws st.orm.NoResultException if there is no result.
- * @throws st.orm.NonUniqueResultException if more than one result.
- */
- fun get(predicate: () -> PredicateBuilder): E = get(predicate())
-
/**
* Retrieves a single entity of type [E] matching the specified predicate.
* Throws an exception if no entity or more than one entity is found.
@@ -1910,85 +1856,6 @@ interface EntityRepository : Repository where E : Entity {
predicate: PredicateBuilder,
): Ref = selectRef().where(predicate).singleResult
- /**
- * Retrieves a single entity reference of type [E] matching the specified predicate lambda.
- * Throws an exception if no entity or more than one entity is found.
- *
- * Example:
- * ```kotlin
- * val ref = repository.getRef { Entity_.name eq "Storm" }
- * ```
- *
- * @return the matching entity reference.
- * @throws st.orm.NoResultException if there is no result.
- * @throws st.orm.NonUniqueResultException if more than one result.
- */
- fun getRef(predicate: () -> PredicateBuilder): Ref = getRef(predicate())
-
- /**
- * Retrieves entities of type [E] matching the specified predicate.
- *
- * The resulting sequence is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the sequence. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the sequence holds resources open
- * while in use, it must be closed after usage to prevent resource leaks.
- *
- * @return a sequence of matching entities.
- */
- fun select(
- predicate: PredicateBuilder,
- ): Flow = select().where(predicate).resultFlow
-
- /**
- * Retrieves entities of type [E] matching the specified predicate lambda as a flow.
- *
- * The resulting flow is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the flow.
- *
- * Example:
- * ```kotlin
- * val entities = repository.select { Entity_.active eq true }
- * ```
- *
- * @return a flow of matching entities.
- */
- fun select(predicate: () -> PredicateBuilder): Flow = select(predicate())
-
- /**
- * Retrieves entities of type [E] matching the specified predicate.
- *
- * The resulting sequence is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the sequence. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the sequence holds resources open
- * while in use, it must be closed after usage to prevent resource leaks.
- *
- * @return a sequence of matching entities.
- */
- fun selectRef(
- predicate: PredicateBuilder,
- ): Flow][> = selectRef().where(predicate).resultFlow
-
- /**
- * Retrieves entity references of type [E] matching the specified predicate lambda as a flow.
- *
- * The resulting flow is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the flow.
- *
- * Example:
- * ```kotlin
- * val refs = repository.selectRef { Entity_.active eq true }
- * ```
- *
- * @return a flow of matching entity references.
- */
- fun selectRef(predicate: () -> PredicateBuilder): Flow][> = selectRef(predicate())
-
/**
* Counts entities of type [E] matching the specified field and value.
*
@@ -2023,18 +1890,6 @@ interface EntityRepository : Repository where E : Entity {
predicate: PredicateBuilder,
): Long = selectCount().where(predicate).singleResult
- /**
- * Counts entities of type [E] matching the specified predicate lambda.
- *
- * Example:
- * ```kotlin
- * val count = repository.count { Entity_.active eq true }
- * ```
- *
- * @return the count of matching entities.
- */
- fun count(predicate: () -> PredicateBuilder): Long = count(predicate())
-
/**
* Checks if entities of type [E] matching the specified field and value exists.
*
@@ -2069,18 +1924,6 @@ interface EntityRepository : Repository where E : Entity {
predicate: PredicateBuilder,
): Boolean = selectCount().where(predicate).singleResult > 0
- /**
- * Checks if entities of type [E] matching the specified predicate lambda exist.
- *
- * Example:
- * ```kotlin
- * val hasActive = repository.exists { Entity_.active eq true }
- * ```
- *
- * @return true if any matching entities exist, false otherwise.
- */
- fun exists(predicate: () -> PredicateBuilder): Boolean = exists(predicate())
-
/**
* Deletes entities of type [E] matching the specified field and value.
*
@@ -2129,26 +1972,6 @@ interface EntityRepository : Repository where E : Entity {
values: Iterable][>,
): Int = delete().whereRef(field, values).executeUpdate()
- /**
- * Deletes entities of type [E] matching the specified predicate.
- *
- * @param predicate Lambda to build the WHERE clause.
- * @return the number of entities deleted.
- */
- fun delete(predicate: PredicateBuilder): Int = delete().where(predicate).executeUpdate()
-
- /**
- * Deletes entities of type [E] matching the specified predicate lambda.
- *
- * Example:
- * ```kotlin
- * val count = repository.delete { Entity_.active eq false }
- * ```
- *
- * @return the number of entities deleted.
- */
- fun delete(predicate: () -> PredicateBuilder): Int = delete(predicate())
-
/**
* Returns a page of entities using offset-based pagination.
*
diff --git a/storm-kotlin/src/main/kotlin/st/orm/repository/ProjectionRepository.kt b/storm-kotlin/src/main/kotlin/st/orm/repository/ProjectionRepository.kt
index 9274b3722..2597b40d3 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/repository/ProjectionRepository.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/repository/ProjectionRepository.kt
@@ -68,6 +68,28 @@ interface ProjectionRepository] : Repository where P : Projection
+ /**
+ * Constructs a SELECT query using a block-based DSL.
+ *
+ * A [PredicateBuilder] returned as the block's last expression is automatically applied as a WHERE clause,
+ * so `select { path eq value }` is equivalent to `select { where(path eq value) }`.
+ *
+ * ```kotlin
+ * interface OwnerViewRepository : ProjectionRepository {
+ * fun findByCity(city: City): List = select {
+ * where(OwnerView_.city eq city)
+ * orderBy(OwnerView_.lastName)
+ * }.resultList
+ * }
+ * ```
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun select(block: SqlScope.() -> Any?): QueryBuilder
{
+ val scope = SqlScope(select())
+ scope.applyResult(scope.block())
+ return scope.builder
+ }
+
/**
* Creates a new query builder for the projection type managed by this repository.
*
@@ -356,27 +378,6 @@ interface ProjectionRepository
: Repository where P : Projection
-
/**
* Retrieves a stream of projections based on their primary keys.
*
@@ -574,21 +575,6 @@ interface ProjectionRepository : Repository where P : Projection> = selectRef().resultList
- /**
- * Retrieves all entities of type [P] from the repository.
- *
- * The resulting sequence is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the sequence. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the sequence holds resources open
- * while in use, it must be closed after usage to prevent resource leaks.
- *
- * @return a sequence containing all entities.
- */
- fun selectAllRef(): Flow[> = selectRef().resultFlow
-
/**
* Retrieves an optional entity of type [P] based on a single field and its value.
* Returns null if no matching entity is found.
@@ -900,18 +886,6 @@ interface ProjectionRepository] : Repository where P : Projection): List = select().where(predicate).resultList
- /**
- * Retrieves projections of type [P] matching the specified predicate lambda.
- *
- * Example:
- * ```kotlin
- * val owners = ownerViews.findAll { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return a list of matching projections.
- */
- fun findAll(predicate: () -> PredicateBuilder
): List
= findAll(predicate())
-
/**
* Retrieves entities of type [P] matching the specified predicate.
*
@@ -919,18 +893,6 @@ interface ProjectionRepository
: Repository where P : Projection): List[> = selectRef().where(predicate).resultList
- /**
- * Retrieves projection references of type [P] matching the specified predicate lambda.
- *
- * Example:
- * ```kotlin
- * val ownerRefs = ownerViews.findAllRef { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return a list of matching projection references.
- */
- fun findAllRef(predicate: () -> PredicateBuilder]): List[> = findAllRef(predicate())
-
/**
* Retrieves an optional entity of type [P] matching the specified predicate.
* Returns null if no matching entity is found.
@@ -941,19 +903,6 @@ interface ProjectionRepository] : Repository where P : Projection,
): P? = select().where(predicate).optionalResult
- /**
- * Retrieves an optional projection of type [P] matching the specified predicate lambda.
- * Returns null if no matching projection is found.
- *
- * Example:
- * ```kotlin
- * val owner = ownerViews.find { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return the matching projection, or null if none found.
- */
- fun find(predicate: () -> PredicateBuilder): P? = find(predicate())
-
/**
* Retrieves an optional entity of type [P] matching the specified predicate.
* Returns a ref with a null value if no matching entity is found.
@@ -964,19 +913,6 @@ interface ProjectionRepository
: Repository where P : Projection,
): Ref? = selectRef().where(predicate).optionalResult
- /**
- * Retrieves an optional projection reference of type [P] matching the specified predicate lambda.
- * Returns null if no matching projection is found.
- *
- * Example:
- * ```kotlin
- * val ownerRef = ownerViews.findRef { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return the matching projection reference, or null if none found.
- */
- fun findRef(predicate: () -> PredicateBuilder
): Ref
? = findRef(predicate())
-
/**
* Retrieves a single entity of type [P] matching the specified predicate.
* Throws an exception if no entity or more than one entity is found.
@@ -989,21 +925,6 @@ interface ProjectionRepository
: Repository where P : Projection,
): P = select().where(predicate).singleResult
- /**
- * Retrieves a single projection of type [P] matching the specified predicate lambda.
- * Throws an exception if no projection or more than one projection is found.
- *
- * Example:
- * ```kotlin
- * val owner = ownerViews.get { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return the matching projection.
- * @throws st.orm.NoResultException if there is no result.
- * @throws st.orm.NonUniqueResultException if more than one result.
- */
- fun get(predicate: () -> PredicateBuilder): P = get(predicate())
-
/**
* Retrieves a single entity of type [P] matching the specified predicate.
* Throws an exception if no entity or more than one entity is found.
@@ -1016,85 +937,6 @@ interface ProjectionRepository
: Repository where P : Projection,
): Ref = selectRef().where(predicate).singleResult
- /**
- * Retrieves a single projection reference of type [P] matching the specified predicate lambda.
- * Throws an exception if no projection or more than one projection is found.
- *
- * Example:
- * ```kotlin
- * val ownerRef = ownerViews.getRef { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return the matching projection reference.
- * @throws st.orm.NoResultException if there is no result.
- * @throws st.orm.NonUniqueResultException if more than one result.
- */
- fun getRef(predicate: () -> PredicateBuilder
): Ref
= getRef(predicate())
-
- /**
- * Retrieves entities of type [P] matching the specified predicate.
- *
- * The resulting sequence is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the sequence. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the sequence holds resources open
- * while in use, it must be closed after usage to prevent resource leaks.
- *
- * @return a sequence of matching entities.
- */
- fun select(
- predicate: PredicateBuilder
,
- ): Flow
= select().where(predicate).resultFlow
-
- /**
- * Retrieves projections of type [P] matching the specified predicate lambda as a flow.
- *
- * The resulting flow is lazily loaded, meaning that the projections are only retrieved from the database as they
- * are consumed by the flow.
- *
- * Example:
- * ```kotlin
- * val owners = ownerViews.select { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return a flow of matching projections.
- */
- fun select(predicate: () -> PredicateBuilder
): Flow
= select(predicate())
-
- /**
- * Retrieves entities of type [P] matching the specified predicate.
- *
- * The resulting sequence is lazily loaded, meaning that the entities are only retrieved from the database as they
- * are consumed by the sequence. This approach is efficient and minimizes the memory footprint, especially when
- * dealing with large volumes of entities.
- *
- * Note: Calling this method does trigger the execution of the underlying
- * query, so it should only be invoked when the query is intended to run. Since the sequence holds resources open
- * while in use, it must be closed after usage to prevent resource leaks.
- *
- * @return a sequence of matching entities.
- */
- fun selectRef(
- predicate: PredicateBuilder
,
- ): Flow[> = selectRef().where(predicate).resultFlow
-
- /**
- * Retrieves projection references of type [P] matching the specified predicate lambda as a flow.
- *
- * The resulting flow is lazily loaded, meaning that the projections are only retrieved from the database as they
- * are consumed by the flow.
- *
- * Example:
- * ```kotlin
- * val ownerRefs = ownerViews.selectRef { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return a flow of matching projection references.
- */
- fun selectRef(predicate: () -> PredicateBuilder]): Flow[> = selectRef(predicate())
-
/**
* Counts entities of type [P] matching the specified field and value.
*
@@ -1129,18 +971,6 @@ interface ProjectionRepository] : Repository where P : Projection,
): Long = selectCount().where(predicate).singleResult
- /**
- * Counts projections of type [P] matching the specified predicate lambda.
- *
- * Example:
- * ```kotlin
- * val count = ownerViews.count { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return the count of matching projections.
- */
- fun count(predicate: () -> PredicateBuilder): Long = count(predicate())
-
/**
* Checks if entities of type [P] matching the specified field and value exists.
*
@@ -1175,18 +1005,6 @@ interface ProjectionRepository
: Repository where P : Projection,
): Boolean = selectCount().where(predicate).singleResult > 0
- /**
- * Checks if projections of type [P] matching the specified predicate lambda exist.
- *
- * Example:
- * ```kotlin
- * val hasDavis = ownerViews.exists { OwnerView_.lastName eq "Davis" }
- * ```
- *
- * @return true if any matching projections exist, false otherwise.
- */
- fun exists(predicate: () -> PredicateBuilder): Boolean = exists(predicate())
-
/**
* Returns a page of projections using offset-based pagination.
*
diff --git a/storm-kotlin/src/main/kotlin/st/orm/repository/RepositoryLookup.kt b/storm-kotlin/src/main/kotlin/st/orm/repository/RepositoryLookup.kt
index 26673c861..f012806eb 100644
--- a/storm-kotlin/src/main/kotlin/st/orm/repository/RepositoryLookup.kt
+++ b/storm-kotlin/src/main/kotlin/st/orm/repository/RepositoryLookup.kt
@@ -130,20 +130,6 @@ inline fun RepositoryLookup.findAll(): List = if (T::class
(projection(T::class as KClass>) as ProjectionRepository, *>).findAll() as List
}
-/**
- * Retrieves all records of type [T] from the repository.
- *
- * [T] must be either an Entity or Projection type.
- *
- * @return stream containing all records.
- */
-@Suppress("UNCHECKED_CAST")
-inline fun RepositoryLookup.selectAll(): Flow = if (T::class.isSubclassOf(Entity::class)) {
- (entity(T::class as KClass>) as EntityRepository, *>).selectAll() as Flow
-} else {
- (projection(T::class as KClass>) as ProjectionRepository, *>).selectAll() as Flow
-}
-
/**
* Retrieves all records of type [T] from the repository.
*
@@ -158,20 +144,6 @@ inline fun RepositoryLookup.findAllRef(): List[> = if (
(projection(T::class as KClass>) as ProjectionRepository, *>).selectRef().resultList as List][>
}
-/**
- * Retrieves all records of type [T] from the repository.
- *
- * [T] must be either an Entity or Projection type.
- *
- * @return stream containing all records.
- */
-@Suppress("UNCHECKED_CAST")
-inline fun RepositoryLookup.selectAllRef(): Flow][> = if (T::class.isSubclassOf(Entity::class)) {
- (entity(T::class as KClass>) as EntityRepository, *>).selectRef().resultFlow as Flow][>
-} else {
- (projection(T::class as KClass>) as ProjectionRepository, *>).selectRef().resultFlow as Flow][>
-}
-
/**
* Retrieves an optional record of type [T] based on a single field and its value.
* Returns null if no matching record is found.
@@ -578,14 +550,6 @@ inline fun RepositoryLookup.findAll(predicate: PredicateBuild
(projection(T::class as KClass>) as ProjectionRepository, *>).select().where(predicate as PredicateBuilder, *, *>).resultList as List
}
-/**
- * Retrieves all records of type [T] matching the specified predicate, using trailing lambda syntax.
- *
- * @param predicate Lambda providing the predicate to build the WHERE clause.
- * @return list of matching records.
- */
-inline fun RepositoryLookup.findAll(predicate: () -> PredicateBuilder): List = findAll(predicate())
-
/**
* Creates a query builder to select records of type [T].
*
@@ -600,14 +564,6 @@ inline fun RepositoryLookup.findAllRef(predicate: PredicateBu
(projection(T::class as KClass>) as ProjectionRepository, *>).selectRef().where(predicate as PredicateBuilder, *, *>).resultList as List][>
}
-/**
- * Retrieves all record references of type [T] matching the specified predicate, using trailing lambda syntax.
- *
- * @param predicate Lambda providing the predicate to build the WHERE clause.
- * @return list of matching record references.
- */
-inline fun RepositoryLookup.findAllRef(predicate: () -> PredicateBuilder): List][> = findAllRef(predicate())
-
/**
* Creates a query builder to select records of type [T].
*
@@ -622,14 +578,6 @@ inline fun RepositoryLookup.find(predicate: PredicateBuilder<
(projection(T::class as KClass>) as ProjectionRepository, *>).select().where(predicate as PredicateBuilder, *, *>).optionalResult as T?
}
-/**
- * Finds a single record of type [T] matching the specified predicate, using trailing lambda syntax.
- *
- * @param predicate Lambda providing the predicate to build the WHERE clause.
- * @return the matching record, or null if none found.
- */
-inline fun RepositoryLookup.find(predicate: () -> PredicateBuilder): T? = find(predicate())
-
/**
* Creates a query builder to select records of type [T].
*
@@ -644,14 +592,6 @@ inline fun RepositoryLookup.findRef(predicate: PredicateBuild
(projection(T::class as KClass>) as ProjectionRepository, *>).selectRef().where(predicate as PredicateBuilder, *, *>).optionalResult as Ref?
}
-/**
- * Finds a single record reference of type [T] matching the specified predicate, using trailing lambda syntax.
- *
- * @param predicate Lambda providing the predicate to build the WHERE clause.
- * @return the matching record reference, or null if none found.
- */
-inline fun RepositoryLookup.findRef(predicate: () -> PredicateBuilder): Ref? = findRef(predicate())
-
/**
* Creates a query builder to select records of type [T].
*
@@ -666,16 +606,6 @@ inline fun RepositoryLookup.get(predicate: PredicateBuilder>) as ProjectionRepository, *>).select().where(predicate as PredicateBuilder, *, *>).singleResult as T
}
-/**
- * Retrieves exactly one record of type [T] matching the specified predicate, using trailing lambda syntax.
- *
- * @param predicate Lambda providing the predicate to build the WHERE clause.
- * @return the matching record.
- * @throws st.orm.NoResultException if there is no result.
- * @throws st.orm.NonUniqueResultException if more than one result.
- */
-inline fun RepositoryLookup.get(predicate: () -> PredicateBuilder): T = get(predicate())
-
/**
* Creates a query builder to select records of type [T].
*
@@ -690,16 +620,6 @@ inline fun RepositoryLookup.getRef(predicate: PredicateBuilde
(projection(T::class as KClass>) as ProjectionRepository, *>).selectRef().where(predicate as PredicateBuilder, *, *>).singleResult as Ref
}
-/**
- * Retrieves exactly one record reference of type [T] matching the specified predicate, using trailing lambda syntax.
- *
- * @param predicate Lambda providing the predicate to build the WHERE clause.
- * @return the matching record reference.
- * @throws st.orm.NoResultException if there is no result.
- * @throws st.orm.NonUniqueResultException if more than one result.
- */
-inline fun RepositoryLookup.getRef(predicate: () -> PredicateBuilder): Ref = getRef(predicate())
-
/**
* Creates a query builder to select records of type [T].
*
@@ -708,55 +628,27 @@ inline fun RepositoryLookup.getRef(predicate: () -> Predicate
* @return A [QueryBuilder] for selecting records of type [T].
*/
@Suppress("UNCHECKED_CAST")
-inline fun RepositoryLookup.select(predicate: PredicateBuilder): Flow = if (T::class.isSubclassOf(Entity::class)) {
- (entity(T::class as KClass>) as EntityRepository, *>).select().where(predicate as PredicateBuilder, *, *>).resultFlow as Flow
-} else {
- (projection(T::class as KClass>) as ProjectionRepository, *>).select().where(predicate as PredicateBuilder, *, *>).resultFlow as Flow
-}
-
-/**
- * Selects records of type [T] matching the specified predicate as a [Flow], using trailing lambda syntax.
- *
- * @param predicate Lambda providing the predicate to build the WHERE clause.
- * @return flow of matching records.
- */
-inline fun RepositoryLookup.select(predicate: () -> PredicateBuilder): Flow = select(predicate())
-
-/**
- * Creates a query builder to select records of type [T].
- *
- * [T] must be either an Entity or Projection type.
- *
- * @return A [QueryBuilder] for selecting records of type [T].
- */
-@Suppress("UNCHECKED_CAST")
-inline fun RepositoryLookup.selectRef(predicate: PredicateBuilder): Flow][> = if (T::class.isSubclassOf(Entity::class)) {
- (entity(T::class as KClass>) as EntityRepository, *>).selectRef().where(predicate as PredicateBuilder, *, *>).resultFlow as Flow][>
+inline fun RepositoryLookup.select(): QueryBuilder = if (T::class.isSubclassOf(Entity::class)) {
+ (entity(T::class as KClass>) as EntityRepository, *>).select() as QueryBuilder
} else {
- (projection(T::class as KClass>) as ProjectionRepository, *>).selectRef().where(predicate as PredicateBuilder, *, *>).resultFlow as Flow][>
+ (projection(T::class as KClass>) as ProjectionRepository, *>).select() as QueryBuilder
}
/**
- * Selects record references of type [T] matching the specified predicate as a [Flow], using trailing lambda syntax.
+ * Creates a query builder to select records of type [T] matching the specified predicate.
*
- * @param predicate Lambda providing the predicate to build the WHERE clause.
- * @return flow of matching record references.
- */
-inline fun RepositoryLookup.selectRef(predicate: () -> PredicateBuilder): Flow][> = selectRef(predicate())
-
-/**
- * Creates a query builder to select records of type [T].
- *
- * [T] must be either an Entity or Projection type.
+ * The entity type is inferred from the predicate:
+ * ```kotlin
+ * orm.select { User_.city eq city }.resultList
+ * orm.select { User_.city eq city }.page(0, 20)
+ * ```
*
- * @return A [QueryBuilder] for selecting records of type [T].
+ * @return A [QueryBuilder] with the predicate applied.
*/
@Suppress("UNCHECKED_CAST")
-inline fun RepositoryLookup.select(): QueryBuilder = if (T::class.isSubclassOf(Entity::class)) {
- (entity(T::class as KClass>) as EntityRepository, *>).select() as QueryBuilder
-} else {
- (projection(T::class as KClass>) as ProjectionRepository, *>).select() as QueryBuilder
-}
+inline fun RepositoryLookup.select(
+ predicate: PredicateBuilder,
+): QueryBuilder = select().where(predicate)
/**
* Creates a query builder to select references of entity records of type [T].
@@ -823,14 +715,6 @@ inline fun RepositoryLookup.count(predicate: PredicateBuilder
(projection(T::class as KClass>) as ProjectionRepository, *>).selectCount().where(predicate as PredicateBuilder, *, *>).singleResult
}
-/**
- * Counts entities of type [T] matching the specified predicate, using trailing lambda syntax.
- *
- * @param predicate Lambda providing the predicate to build the WHERE clause.
- * @return the count of matching entities.
- */
-inline fun ]