skills/.system/entityassist/SKILL.md
CRTP-first reactive persistence toolkit for GuicedEE services. Provides fluent entity and query-builder DSL on top of Vert.x 5, Hibernate Reactive 7, and Mutiny with PostgreSQL support. Features type-safe queries, reactive CRUD with Uni, dot-notation path filters, pagination, aggregates, joins, bulk operations, and stateless sessions. Use when working with reactive persistence, Hibernate Reactive, building entities and repositories, writing queries, or implementing non-blocking database operations in GuicedEE applications.
npx skillsauth add guicedee/ai-rules entityassistInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
CRTP-first reactive persistence toolkit for GuicedEE services with Hibernate Reactive 7 and Mutiny.
BaseEntity<J, Q, I> with self-referencing fluent setterswhere(), or(), orderBy(), groupBy(), join()Uni<T>where("roles.name", Equals, "ADMIN")setFirstResults() / setMaxResults()selectMin(), selectMax(), selectSum(), selectAverage(), selectCount()validateEntity() returns constraint violations@Entity
@Accessors(chain = true)
@Table(name = "entity_class")
public class EntityClass
extends BaseEntity<EntityClass, EntityClass.EntityClassQueryBuilder, String> {
@Id
@Column(name = "id", nullable = false)
@Getter @Setter
private String id;
@Column(name = "name")
@Getter @Setter
private String name;
@Override
public String getId() { return id; }
@Override
public EntityClass setId(String id) {
this.id = id;
return this;
}
public static class EntityClassQueryBuilder
extends QueryBuilder<EntityClassQueryBuilder, EntityClass, String> {
@Override
public boolean isIdGenerated() {
return false;
}
}
}
@Entity
@Accessors(chain = true)
@Table(name = "entity_class_two")
public class EntityClassTwo
extends BaseEntity<EntityClassTwo, EntityClassTwo.EntityClassTwoQueryBuilder, String> {
@Id
@Getter @Setter
private String id;
@Column(name = "value")
@Getter @Setter
private Integer value;
@ManyToOne
@JoinColumn(name = "entity_class_id")
@Getter @Setter
private EntityClass entityClass;
@Override
public String getId() { return id; }
@Override
public EntityClassTwo setId(String id) {
this.id = id;
return this;
}
public static class EntityClassTwoQueryBuilder
extends QueryBuilder<EntityClassTwoQueryBuilder, EntityClassTwo, String> {
@Override
public boolean isIdGenerated() {
return false;
}
}
}
IRootEntity IQueryBuilderRoot
└─ IDefaultEntity └─ IDefaultQueryBuilder
└─ IBaseEntity └─ IQueryBuilder
↑ ↑
RootEntity<J,Q,I> QueryBuilderRoot<J,E,I>
└─ DefaultEntity<J,Q,I> └─ DefaultQueryBuilder<J,E,I>
└─ BaseEntity<J,Q,I> └─ QueryBuilder<J,E,I>
↑ ↑
Your Entity Your QueryBuilder
Every entity binds to its query builder via CRTP generics.
sessionFactory.withSession(session ->
session.withTransaction(tx ->
entity.builder(session)
.persist(entity)
)
).await().indefinitely();
sessionFactory.withSession(session ->
new EntityClass()
.builder(session)
.find("test1")
.get() // Uni<EntityClass>
).await().indefinitely();
sessionFactory.withSession(session -> {
var qb = new EntityClass().builder(session);
return qb
.where(qb.getAttribute("name"), Operand.Like, "A%")
.or(qb.getAttribute("name"), Operand.Equals, "Bob")
.orderBy(qb.getAttribute("name"), OrderByType.ASC)
.setMaxResults(50)
.getAll(); // Uni<List<EntityClass>>
});
Traverse relationships without explicit joins:
sessionFactory.withSession(session -> {
var qb = new EntityClassTwo().builder(session);
return qb
.where("entityClass.name", Operand.Equals, "Parent Entity")
.where("value", Operand.GreaterThan, 10)
.getAll();
});
sessionFactory.withSession(session -> {
var qb = new EntityClass().builder(session);
return qb
.where(qb.getAttribute("name"), Operand.Like, "A%")
.orderBy(qb.getAttribute("name"), OrderByType.ASC)
.setFirstResults(0)
.setMaxResults(20)
.getAll();
});
sessionFactory.withSession(session -> {
var qb = new EntityClass().builder(session);
return qb
.where(qb.getAttribute("name"), Operand.Like, "A%")
.getCount(); // Uni<Long>
});
sessionFactory.withSession(session -> {
var qb = new EntityClassTwo().builder(session);
return qb
.selectMax(qb.getAttribute("value"))
.get(Integer.class); // Uni<Integer>
});
Available aggregates:
selectMin()selectMax()selectSum()selectSumAsDouble()selectSumAsLong()selectAverage()selectCount()selectCountDistinct()selectColumn()sessionFactory.withSession(session -> {
var parent = new EntityClass().builder(session);
var child = new EntityClassTwo().builder(session);
return child
.join(child.getAttribute("entityClass"), parent, JoinType.INNER)
.where(parent.getAttribute("name"), Operand.Equals, "Parent Entity")
.getAll();
});
sessionFactory.withSession(session ->
session.withTransaction(tx -> {
var qb = new EntityClass().builder(session);
return qb
.where(qb.getAttribute("name"), Operand.Equals, "obsolete")
.delete(); // Uni<Integer> — rows affected
})
);
Safety guard: Bulk delete() requires at least one filter. Use truncate() to remove all rows.
sessionFactory.withSession(session ->
session.withTransaction(tx ->
entity.builder(session)
.delete(entity) // Uni<EntityClass>
)
);
entity.setName("Updated Name");
sessionFactory.withSession(session ->
session.withTransaction(tx ->
entity.builder(session)
.update() // Uni<EntityClass>
)
);
For high-throughput bulk operations:
sessionFactory.withStatelessSession(session ->
entity.builder(session) // uses Mutiny.StatelessSession
.persist(entity)
);
sessionFactory.withSession(session ->
session.withTransaction(tx ->
new EntityClass().builder(session)
.persist(new EntityClass().setId("b1").setName("Bob"))
.chain(() ->
new EntityClass().builder(session)
.find("b1")
.get()
)
.invoke(found -> log.info("Created and retrieved: {}", found.getName()))
)
);
Create a DatabaseModule subclass annotated with @EntityManager:
@EntityManager(value = "entityAssistReactive", defaultEm = true)
public class EntityAssistReactiveDBModule
extends DatabaseModule<EntityAssistReactiveDBModule>
implements IGuiceModule<EntityAssistReactiveDBModule> {
@Override
protected String getPersistenceUnitName() {
return "entityAssistReactive";
}
@Override
protected ConnectionBaseInfo getConnectionBaseInfo(
PersistenceUnitDescriptor unit, Properties filteredProperties) {
PostgresConnectionBaseInfo connectionInfo = new PostgresConnectionBaseInfo();
connectionInfo.setServerName("localhost");
connectionInfo.setPort("5432");
connectionInfo.setDatabaseName("mydb");
connectionInfo.setUsername(System.getenv("DB_USER"));
connectionInfo.setPassword(System.getenv("DB_PASSWORD"));
connectionInfo.setDefaultConnection(true);
connectionInfo.setReactive(true);
return connectionInfo;
}
@Override
protected String getJndiMapping() {
return "jdbc:entityAssistReactive";
}
}
module my.app {
requires com.entityassist;
requires com.guicedee.persistence;
opens my.app.entities to org.hibernate.orm.core, com.google.guice, com.entityassist;
provides com.guicedee.client.services.lifecycle.IGuiceModule
with my.app.MyDatabaseModule;
}
| Variable | Purpose | Default |
|---|---|---|
| DB_HOST | Database hostname | localhost |
| DB_PORT | Database port | 5432 |
| DB_NAME | Database name | — |
| DB_USER | Database username | — |
| DB_PASSWORD | Database password | — |
| ENVIRONMENT | Runtime environment | dev |
See references/operands.md for complete list.
Common operands:
Equals, NotEqualsLike, NotLikeLessThan, LessThanEqualToGreaterThan, GreaterThanEqualToNull, NotNullInList, NotInListEntities:
RootEntity<J,Q,I> — Root CRTP entity with builder(), persist(), update()DefaultEntity<J,Q,I> — Intermediate extension pointBaseEntity<J,Q,I> — Primary superclass for user entitiesQuery Builders:
QueryBuilderRoot<J,E,I> — Root builder with session managementDefaultQueryBuilder<J,E,I> — Fluent DSL methodsQueryBuilder<J,E,I> — Primary superclass for user buildersExpressions:
WhereExpression — Single where predicateGroupedExpression — AND/OR predicate groupingJoinExpression — Join definitionSelectExpression — Column selection with aggregatesOrderByExpression — Column + directionGroupByExpression — Column groupingRich status model with ranged queries:
public enum ActiveFlag {
Unknown,
Deleted,
Active,
Permanent
}
Helpers:
getActiveRange() — Active to PermanentgetVisibleRangeAndUp() — Active and aboveBuilt-in JPA attribute converters:
LocalDateAttributeConverter — LocalDate ↔ java.sql.DateLocalDateTimeAttributeConverter — LocalDateTime ↔ java.sql.TimestampLocalDateTimestampAttributeConverter — LocalDate ↔ java.sql.Timestamp@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class EntityAssistReactiveTest {
private Mutiny.SessionFactory sessionFactory;
@BeforeAll
public void setup() {
IGuiceContext.instance();
JtaPersistService ps = (JtaPersistService) IGuiceContext.get(
Key.get(PersistService.class, Names.named("entityAssistReactive")));
ps.start();
sessionFactory = IGuiceContext.get(
Key.get(Mutiny.SessionFactory.class, Names.named("entityAssistReactive")));
}
@Test
void roundTrip() {
EntityClass entity = new EntityClass()
.setId("test1")
.setName("Test Entity");
sessionFactory.withSession(session ->
session.withTransaction(tx ->
entity.builder(session).persist(entity)
).chain(() ->
new EntityClass().builder(session)
.find("test1").get()
).invoke(found -> {
assertNotNull(found);
assertEquals("test1", found.getId());
})
).await().indefinitely();
}
}
setFirstResults() / setMaxResults() for paginationUni callsdelete() requires filters — use truncate() for all rowsvalidateEntity()module com.entityassist {
requires transitive com.guicedee.persistence;
requires transitive jakarta.persistence;
requires transitive org.hibernate.reactive;
requires transitive io.smallrye.mutiny;
exports com.entityassist.entities;
exports com.entityassist.querybuilder;
exports com.entityassist.enumerations;
opens com.entityassist.entities to org.hibernate.orm.core, com.google.guice;
}
<dependency>
<groupId>com.entityassist</groupId>
<artifactId>entity-assist-reactive</artifactId>
</dependency>
com.entityassist
├── com.guicedee.persistence
├── com.guicedee.client
├── jakarta.persistence
├── org.hibernate.reactive
├── org.hibernate.orm.core
├── io.smallrye.mutiny
├── io.vertx.sql.client.pg
└── jakarta.xml.bind
com.entityassistdevelopment
Install Codex skills into $CODEX_HOME/skills from a curated list or a GitHub repo path. Use when a user asks to list installable skills, install a curated skill, or install a skill from another repo (including private repos).
tools
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Codex's capabilities with specialized knowledge, workflows, or tool integrations.
development
WebAwesome icon integration for JWebMP — modern, open-source icon library. Provides 1,500+ icons with solid/regular styles, sizing, rotation, animation, and CSS utilities. Drop-in FontAwesome alternative with fresh designs. Use when working with WebAwesome icons, modern icon designs, or as FontAwesome alternative in JWebMP applications.
development
WebAwesome Pro integration for JWebMP with premium icons and features. Extends jwebmp-webawesome with additional styles, premium icons, and advanced features. Use when working with WebAwesome Pro icons or premium WebAwesome features in JWebMP applications.