sirius-framework-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +160 -0
  3. package/dist/grammars/tree-sitter-java.wasm +0 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +247 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/java-parser.d.ts +25 -0
  8. package/dist/java-parser.js +281 -0
  9. package/dist/java-parser.js.map +1 -0
  10. package/dist/prompts/index.d.ts +9 -0
  11. package/dist/prompts/index.js +15 -0
  12. package/dist/prompts/index.js.map +1 -0
  13. package/dist/prompts/workflows.d.ts +27 -0
  14. package/dist/prompts/workflows.js +317 -0
  15. package/dist/prompts/workflows.js.map +1 -0
  16. package/dist/resources/biz/analytics.md +157 -0
  17. package/dist/resources/biz/biz-controller.md +151 -0
  18. package/dist/resources/biz/codelists.md +154 -0
  19. package/dist/resources/biz/entity-triple.md +142 -0
  20. package/dist/resources/biz/importer.md +153 -0
  21. package/dist/resources/biz/isenguard.md +156 -0
  22. package/dist/resources/biz/jobs.md +145 -0
  23. package/dist/resources/biz/processes.md +155 -0
  24. package/dist/resources/biz/storage.md +149 -0
  25. package/dist/resources/biz/tenants.md +159 -0
  26. package/dist/resources/biz/testing.md +127 -0
  27. package/dist/resources/db/composites.md +145 -0
  28. package/dist/resources/db/entities.md +156 -0
  29. package/dist/resources/db/mixing.md +176 -0
  30. package/dist/resources/db/queries.md +178 -0
  31. package/dist/resources/db/refs.md +135 -0
  32. package/dist/resources/index.d.ts +27 -0
  33. package/dist/resources/index.js +68 -0
  34. package/dist/resources/index.js.map +1 -0
  35. package/dist/resources/kernel/async.md +189 -0
  36. package/dist/resources/kernel/commons.md +203 -0
  37. package/dist/resources/kernel/config.md +155 -0
  38. package/dist/resources/kernel/di.md +138 -0
  39. package/dist/resources/kernel/lifecycle.md +146 -0
  40. package/dist/resources/loader.d.ts +9 -0
  41. package/dist/resources/loader.js +17 -0
  42. package/dist/resources/loader.js.map +1 -0
  43. package/dist/resources/web/controllers.md +151 -0
  44. package/dist/resources/web/services.md +136 -0
  45. package/dist/resources/web/templates.md +162 -0
  46. package/dist/tools/index.d.ts +4 -0
  47. package/dist/tools/index.js +3 -0
  48. package/dist/tools/index.js.map +1 -0
  49. package/dist/tools/introspection.d.ts +55 -0
  50. package/dist/tools/introspection.js +233 -0
  51. package/dist/tools/introspection.js.map +1 -0
  52. package/dist/tools/scaffold.d.ts +64 -0
  53. package/dist/tools/scaffold.js +505 -0
  54. package/dist/tools/scaffold.js.map +1 -0
  55. package/dist/workspace.d.ts +37 -0
  56. package/dist/workspace.js +185 -0
  57. package/dist/workspace.js.map +1 -0
  58. package/package.json +41 -0
@@ -0,0 +1,127 @@
1
+ # Testing
2
+
3
+ Tests in sirius-biz are written in Kotlin using JUnit 5 with the `SiriusExtension`.
4
+ This extension boots the full Sirius framework (DI, databases, config) before tests
5
+ run, giving you access to real services and database connections.
6
+
7
+ ## Test Structure
8
+
9
+ - **Test files:** `src/test/kotlin/sirius/biz/**/*Test.kt` (Kotlin)
10
+ - **Test entities:** `src/test/java/sirius/biz/**/*.java` (Java, for test-only entities)
11
+ - **Test config:** `src/test/resources/test.conf`
12
+ - **Test suite:** `TestSuite.java` using `ScenarioSuite`
13
+
14
+ ## SiriusExtension and DI
15
+
16
+ Every test class must use `@ExtendWith(SiriusExtension::class)`. This boots the
17
+ Sirius framework once per test run (shared across all test classes).
18
+
19
+ Use `@Part` in a `companion object` with `@JvmStatic` to inject services.
20
+ Always wait for database readiness in `@BeforeAll`:
21
+
22
+ ```kotlin
23
+ @BeforeAll
24
+ @JvmStatic
25
+ fun setup() {
26
+ oma.readyFuture.await(Duration.ofSeconds(60))
27
+ }
28
+ ```
29
+
30
+ **Important:** `@Part` fields must be in the `companion object` (static), not on
31
+ the test instance. Sirius DI only injects static fields.
32
+
33
+ ## Test Naming
34
+
35
+ Use Kotlin backtick syntax for descriptive test names. This produces readable test
36
+ output while being valid Kotlin method names.
37
+
38
+ ## Complete Example
39
+
40
+ ```kotlin
41
+ @ExtendWith(SiriusExtension::class)
42
+ class TenantsTest {
43
+
44
+ companion object {
45
+ @Part
46
+ @JvmStatic
47
+ private lateinit var oma: OMA
48
+
49
+ @BeforeAll
50
+ @JvmStatic
51
+ fun setup() {
52
+ oma.readyFuture.await(Duration.ofSeconds(60))
53
+ }
54
+ }
55
+
56
+ @Test
57
+ fun `installTestTenant works`() {
58
+ TenantsHelper.installTestTenant()
59
+ assertTrue {
60
+ UserContext.get().getUser().hasPermission(UserInfo.PERMISSION_LOGGED_IN)
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Test Entities
67
+
68
+ Test-only entities live in `src/test/java/` (not `src/main/java/`). They are
69
+ written in Java (not Kotlin) because the Mixing ORM annotation processor requires
70
+ Java source files:
71
+
72
+ ```java
73
+ // src/test/java/sirius/biz/mymodule/TestProduct.java
74
+ @Framework("test")
75
+ public class TestProduct extends BizEntity {
76
+ @Autoloaded
77
+ @Length(100)
78
+ private String name;
79
+
80
+ // getters and setters
81
+ }
82
+ ```
83
+
84
+ Enable the test framework in `test.conf`:
85
+
86
+ ```hocon
87
+ sirius.frameworks {
88
+ test = true
89
+ }
90
+ ```
91
+
92
+ ## Test Configuration
93
+
94
+ `src/test/resources/test.conf` enables the frameworks needed for testing:
95
+
96
+ ```hocon
97
+ sirius.frameworks {
98
+ biz.tenants = true
99
+ biz.tenants-jdbc = true
100
+ biz.storage = true
101
+ biz.storage-blob-jdbc = true
102
+ biz.isenguard = true
103
+ biz.processes = true
104
+ biz.locks = true
105
+ }
106
+ ```
107
+
108
+ Docker services (MariaDB, MongoDB, Elasticsearch, Redis) must be running for
109
+ integration tests. Use `docker-compose up -d` in the project root.
110
+
111
+ ## Common Mistakes
112
+
113
+ 1. **`@Part` on instance fields** — DI injection only works on static fields.
114
+ Place `@Part` fields in the `companion object` with `@JvmStatic`.
115
+
116
+ 2. **Missing `@BeforeAll` database wait** — Tests that run before the schema is
117
+ ready will fail intermittently. Always await `oma.readyFuture`.
118
+
119
+ 3. **Test entities in Kotlin** — The Mixing ORM requires Java source files for
120
+ entity classes. Test entities must be `.java` files even though tests are Kotlin.
121
+
122
+ 4. **Not cleaning up test data** — Tests share the same database. Create unique
123
+ test data (e.g., with random suffixes) or clean up in `@AfterEach` to avoid
124
+ interference between tests.
125
+
126
+ 5. **Forgetting `@JvmStatic`** — Without `@JvmStatic` on companion object fields,
127
+ Sirius DI cannot find and populate the fields.
@@ -0,0 +1,145 @@
1
+ # Composites
2
+
3
+ A `Composite` is a reusable group of fields that can be embedded into any entity
4
+ or other composite. It lives in `sirius.db.mixing.Composite` and extends `Mixable`,
5
+ meaning it can itself be extended via mixins.
6
+
7
+ ## How Composites Work
8
+
9
+ When a composite is declared as a field in an entity, all of the composite's fields
10
+ become properties of the entity. The field name of the composite is **prepended**
11
+ to each property name, separated by `_`:
12
+
13
+ ```java
14
+ public class Customer extends SQLTenantAware {
15
+ private final PersonData person = new PersonData();
16
+ // Creates columns: person_title, person_firstname, person_lastname, etc.
17
+ }
18
+ ```
19
+
20
+ This prefixing means the same composite class can appear multiple times in one entity
21
+ under different field names without column conflicts:
22
+
23
+ ```java
24
+ public class Order extends SQLTenantAware {
25
+ private final AddressData billingAddress = new AddressData();
26
+ private final AddressData shippingAddress = new AddressData();
27
+ // billingAddress_street, billingAddress_city, ...
28
+ // shippingAddress_street, shippingAddress_city, ...
29
+ }
30
+ ```
31
+
32
+ ## Mapping Constants
33
+
34
+ Every composite field should declare a `Mapping` constant to enable type-safe
35
+ references in queries and templates:
36
+
37
+ ```java
38
+ public class PersonData extends Composite {
39
+
40
+ public static final Mapping FIRSTNAME = Mapping.named("firstname");
41
+ @Length(150)
42
+ @Trim
43
+ @Autoloaded
44
+ @NullAllowed
45
+ @AutoImport
46
+ private String firstname;
47
+
48
+ public static final Mapping LASTNAME = Mapping.named("lastname");
49
+ @Length(150)
50
+ @Trim
51
+ @Autoloaded
52
+ @NullAllowed
53
+ @AutoImport
54
+ private String lastname;
55
+
56
+ // getters/setters...
57
+ }
58
+ ```
59
+
60
+ ## Accessing Nested Fields with Mapping.inner()
61
+
62
+ When querying or referencing a composite field from the entity level, use
63
+ `Mapping.inner()` to build the prefixed path:
64
+
65
+ ```java
66
+ // In a query on the Customer entity:
67
+ oma.select(Customer.class)
68
+ .eq(Customer.PERSON.inner(PersonData.LASTNAME), "Smith")
69
+ .queryList();
70
+ ```
71
+
72
+ This resolves to the column `person_lastname` in the database.
73
+
74
+ ## Standard Annotations
75
+
76
+ These annotations can be placed on fields in composites (and entities):
77
+
78
+ - `@Length(n)` — Maximum column/field length (required for strings).
79
+ - `@Trim` — Automatically trims whitespace on load/save.
80
+ - `@NullAllowed` — Permits null values. Without this, nulls cause validation errors.
81
+ - `@Unique` — Enforces uniqueness. Supports `within` parameter for scoped uniqueness.
82
+ - `@Transient` — Excludes the field from persistence entirely.
83
+ - `@Autoloaded` — Auto-populates the field from web request parameters.
84
+ - `@AutoImport` — Auto-populates the field during data import.
85
+
86
+ ## Lifecycle Annotations
87
+
88
+ These method-level annotations on the containing entity (or composite) hook into
89
+ the persistence lifecycle:
90
+
91
+ - `@BeforeSave` — Called before the entity is written to the database. Use for
92
+ computed fields, normalization, or throwing exceptions to abort the save.
93
+ - `@OnValidate` — Called during validation. The annotated method receives a
94
+ `Consumer<String>` to collect warning messages without aborting.
95
+ - `@ValidatedBy(ValidatorClass.class)` — Delegates field validation to an external
96
+ validator class.
97
+
98
+ ```java
99
+ public class InvoiceData extends Composite {
100
+
101
+ public static final Mapping INVOICE_NUMBER = Mapping.named("invoiceNumber");
102
+ @Length(50)
103
+ @NullAllowed
104
+ private String invoiceNumber;
105
+
106
+ @BeforeSave
107
+ protected void generateInvoiceNumber() {
108
+ if (Strings.isEmpty(invoiceNumber)) {
109
+ invoiceNumber = sequences.generateId("invoices");
110
+ }
111
+ }
112
+
113
+ @OnValidate
114
+ protected void validate(Consumer<String> validationConsumer) {
115
+ if (Strings.isEmpty(invoiceNumber)) {
116
+ validationConsumer.accept("Invoice number is required.");
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ## PersonData — Standard Example
123
+
124
+ `PersonData` in `sirius.biz.model` is the canonical composite example. It stores
125
+ title, salutation, firstname, and lastname. It provides helper methods like
126
+ `getAddressableName()` and `getShortName()`, and validation helpers
127
+ (`verifySalutation()`, `validateSalutation()`) that the containing entity can
128
+ call from its own `@BeforeSave` or `@OnValidate` methods.
129
+
130
+ ## Common Mistakes
131
+
132
+ 1. **Forgetting the prefix in queries** — A field `firstname` inside a composite
133
+ field `person` becomes `person_firstname` in the database. Always use
134
+ `Mapping.inner()` to construct the correct path.
135
+
136
+ 2. **Not declaring Mapping constants** — Without `Mapping.named()` constants,
137
+ queries require raw strings, which are error-prone and not refactoring-safe.
138
+
139
+ 3. **Mutable composite instances** — Composite fields should be declared `final`
140
+ in the entity. The composite object is created once and its internal fields are
141
+ mutated, but the composite reference itself should not change.
142
+
143
+ 4. **Missing @NullAllowed** — String fields default to non-null. If a field
144
+ legitimately can be empty, annotate it with `@NullAllowed` or provide a
145
+ `@DefaultValue`.
@@ -0,0 +1,156 @@
1
+ # Entities
2
+
3
+ Sirius-db provides three entity base classes, one per supported database. All share
4
+ a common ancestor: `BaseEntity<I>` (in `sirius.db.mixing`), which extends `Mixable`
5
+ and implements `Entity`. The type parameter `I` is the ID type.
6
+
7
+ ## SQLEntity — JDBC / SQL Databases
8
+
9
+ `SQLEntity` uses `Long` IDs auto-generated by the database. It supports optimistic
10
+ locking via a `version` field that is automatically incremented on each update.
11
+
12
+ ```java
13
+ public abstract class SQLEntity extends BaseEntity<Long> {
14
+ // id: long, auto-assigned by the database
15
+ // version: int, used for optimistic locking
16
+ }
17
+ ```
18
+
19
+ An entity is considered new (not yet persisted) when `id < 0`. The sentinel value
20
+ is `SQLEntity.NON_PERSISTENT_ENTITY_ID` (-1).
21
+
22
+ **Mapper:** `OMA` (Object Mapper for rdbms Access)
23
+
24
+ ## MongoEntity — MongoDB
25
+
26
+ `MongoEntity` uses `String` IDs generated by `KeyGenerator` -- NOT MongoDB's native
27
+ ObjectId. This produces short, URL-safe string identifiers. Optimistic locking is
28
+ supported via a `version` field.
29
+
30
+ ```java
31
+ @Index(name = "id", columns = "id", columnSettings = Mango.INDEX_ASCENDING, unique = true)
32
+ public abstract class MongoEntity extends BaseEntity<String> {
33
+ // id: String, generated by KeyGenerator before insert
34
+ // version: int, used for optimistic locking
35
+ }
36
+ ```
37
+
38
+ An entity is new when `id == null`. The ID is generated in `generateId()` during
39
+ the create operation, after before-save checks have executed.
40
+
41
+ **Mapper:** `Mango`
42
+
43
+ ## ElasticEntity — Elasticsearch
44
+
45
+ `ElasticEntity` uses `String` IDs auto-generated by Elasticsearch. It uses
46
+ `primaryTerm` and `seqNo` for optimistic concurrency control instead of a simple
47
+ version counter.
48
+
49
+ ```java
50
+ public abstract class ElasticEntity extends BaseEntity<String> {
51
+ // id: String, auto-generated by Elasticsearch
52
+ // primaryTerm, seqNo: used for optimistic concurrency
53
+ }
54
+ ```
55
+
56
+ For performance, annotate a field with `@RoutedBy` to enable custom routing:
57
+
58
+ ```java
59
+ @RoutedBy(tenantRef)
60
+ public class EventLog extends ElasticEntity { ... }
61
+ ```
62
+
63
+ **Mapper:** `Elastic`
64
+
65
+ ## Hierarchy in sirius-biz
66
+
67
+ sirius-biz extends these base classes to add tracing, journaling, and tenant
68
+ awareness. The full hierarchies are:
69
+
70
+ **SQL path:**
71
+ ```
72
+ BaseEntity<Long>
73
+ +-- SQLEntity
74
+ +-- BizEntity (adds TraceData)
75
+ +-- SQLTenantAware (adds tenant reference)
76
+ ```
77
+
78
+ **MongoDB path:**
79
+ ```
80
+ BaseEntity<String>
81
+ +-- MongoEntity
82
+ +-- PrefixSearchableEntity (adds search prefix support)
83
+ +-- MongoBizEntity (adds TraceData)
84
+ +-- MongoTenantAware (adds tenant reference)
85
+ ```
86
+
87
+ When creating a tenant-aware entity, extend `SQLTenantAware` or `MongoTenantAware`.
88
+ When creating a non-tenant entity with tracing, extend `BizEntity` or `MongoBizEntity`.
89
+
90
+ ## Entity Registration
91
+
92
+ Entities are discovered automatically at startup. Use `@Framework` to gate an entity
93
+ behind a framework flag:
94
+
95
+ ```java
96
+ @Framework("biz.tenants-jdbc")
97
+ public class SQLTenant extends SQLTenantAware implements Tenant<Long> { ... }
98
+ ```
99
+
100
+ The entity will only be registered (and its table/collection created) when the
101
+ framework flag `biz.tenants-jdbc` is enabled in the config.
102
+
103
+ ## Concrete SQL Example
104
+
105
+ ```java
106
+ @Framework("myapp.products")
107
+ public class Product extends SQLTenantAware {
108
+
109
+ public static final Mapping NAME = Mapping.named("name");
110
+ @Length(255)
111
+ @Trim
112
+ @Autoloaded
113
+ private String name;
114
+
115
+ public static final Mapping PRICE = Mapping.named("price");
116
+ @Autoloaded
117
+ private int price;
118
+
119
+ // getters/setters...
120
+ }
121
+ ```
122
+
123
+ ## Concrete Mongo Example
124
+
125
+ ```java
126
+ @Framework("myapp.products")
127
+ public class MongoProduct extends MongoTenantAware {
128
+
129
+ public static final Mapping NAME = Mapping.named("name");
130
+ @Length(255)
131
+ @Trim
132
+ @Autoloaded
133
+ private String name;
134
+
135
+ // getters/setters...
136
+ }
137
+ ```
138
+
139
+ ## Common Mistakes
140
+
141
+ 1. **Using MongoDB ObjectId** — MongoEntity does NOT use ObjectId. IDs are generated
142
+ as strings by `KeyGenerator`. Do not try to parse them as ObjectId.
143
+
144
+ 2. **Setting the SQL ID manually** — The `id` field is managed by the database.
145
+ Never assign a value to `setId()` unless you are doing a low-level data migration.
146
+
147
+ 3. **Forgetting @Framework** — Without a framework annotation, the entity is always
148
+ registered. This can cause table creation in databases where it is not wanted.
149
+
150
+ 4. **Extending concrete entity classes** — Superclasses of entities should always
151
+ be `abstract`. The framework does not support merging distinct subclasses into
152
+ one table.
153
+
154
+ 5. **Ignoring optimistic locking** — If a concurrent update causes a version
155
+ mismatch, an `OptimisticLockException` is thrown. Catch and handle it
156
+ (e.g., reload and retry) rather than letting it propagate as a 500 error.
@@ -0,0 +1,176 @@
1
+ # Mixing ORM
2
+
3
+ Mixing is the core ORM (Object-Relational/Document Mapping) layer in sirius-db.
4
+ It provides a unified abstraction over SQL, MongoDB, and Elasticsearch, handling
5
+ entity discovery, schema management, property mapping, and validation.
6
+
7
+ ## Overview
8
+
9
+ The Mixing system consists of three main components:
10
+
11
+ 1. **`Mixing`** — The central registry. Discovers all entity classes at startup,
12
+ creates their descriptors, and provides lookup by class or name.
13
+ 2. **`EntityDescriptor`** — Describes a single entity type: its properties,
14
+ lifecycle handlers, relation name, and validation rules.
15
+ 3. **`Property`** — Maps a single Java field to a database column/field. Handles
16
+ type conversion, validation, and access.
17
+
18
+ ## Mixing — The Registry
19
+
20
+ `Mixing` is a singleton (`@Register`) that initializes at startup by:
21
+
22
+ 1. Scanning for all `BaseEntity` subclasses (via `EntityLoadAction`)
23
+ 2. Creating an `EntityDescriptor` for each
24
+ 3. Linking cross-references between descriptors
25
+ 4. Optionally executing schema updates
26
+
27
+ Inject it with `@Part`:
28
+
29
+ ```java
30
+ @Part
31
+ private Mixing mixing;
32
+ ```
33
+
34
+ Key methods:
35
+ - `mixing.getDescriptor(Class)` — returns the descriptor for an entity class
36
+ - `mixing.getDescriptor(String)` — returns the descriptor by type name
37
+ (upper-cased simple class name, e.g., `"PRODUCT"`)
38
+ - `mixing.findDescriptor(Class)` — returns Optional (no exception if missing)
39
+ - `mixing.getDescriptors()` — returns all known descriptors
40
+
41
+ ## EntityDescriptor
42
+
43
+ Each entity class has exactly one `EntityDescriptor`. It holds:
44
+
45
+ - **Properties** — the list of `Property` objects for each mapped field
46
+ - **Relation name** — the table/collection/index name in the database
47
+ - **Realm** — which database instance to use (via `@Realm` annotation)
48
+ - **Lifecycle handlers** — methods annotated with `@BeforeSave`, `@AfterSave`,
49
+ `@BeforeDelete`, `@AfterDelete`, `@OnValidate`
50
+ - **Version flag** — whether optimistic locking is enabled (`@Versioned`)
51
+
52
+ ```java
53
+ EntityDescriptor descriptor = mixing.getDescriptor(Product.class);
54
+ descriptor.getRelationName(); // e.g., "product"
55
+ descriptor.getRealm(); // e.g., "mixing" (default)
56
+ descriptor.getProperties(); // all Property objects
57
+ descriptor.isVersioned(); // true if @Versioned
58
+ ```
59
+
60
+ Change detection is built into the descriptor:
61
+
62
+ ```java
63
+ descriptor.isChanged(entity, property); // checks if a field was modified
64
+ entity.isChanged(Product.NAME); // convenience on the entity itself
65
+ entity.isAnyMappingChanged(); // any field changed at all
66
+ ```
67
+
68
+ ## Property Abstraction
69
+
70
+ A `Property` bridges a Java field and its database representation:
71
+
72
+ - `getName()` — the effective property name (prefixed for composites/mixins)
73
+ - `getPropertyName()` — the column/field name in the database
74
+ - `getValue(entity)` — reads the current value from the entity
75
+ - `setValue(entity, value)` — writes a value to the entity
76
+ - `getValueForDatasource(mapperClass, entity)` — converts the value for storage
77
+ - `parseValue(entity, Value)` — converts a raw value back into the Java type
78
+
79
+ Properties are created by `PropertyFactory` implementations. Each Java type
80
+ (String, int, LocalDateTime, EntityRef, Composite, etc.) has a corresponding
81
+ property factory that knows how to map it.
82
+
83
+ ## Entity Discovery and @Framework
84
+
85
+ Entities are discovered by scanning for all concrete subclasses of `BaseEntity`.
86
+ To conditionally include an entity, use `@Framework`:
87
+
88
+ ```java
89
+ @Framework("myapp.products")
90
+ public class Product extends SQLTenantAware { ... }
91
+ ```
92
+
93
+ If the framework flag `myapp.products` is not enabled in `sirius.frameworks`,
94
+ the entity class is not loaded, its table is not created, and its descriptor
95
+ does not exist in the `Mixing` registry.
96
+
97
+ ## Mapper Hierarchy
98
+
99
+ Each database backend has its own mapper that extends `BaseMapper`:
100
+
101
+ | Mapper | Entity Base | Query Type | ID Type |
102
+ |--------|-------------|------------|---------|
103
+ | `OMA` | `SQLEntity` | `SmartQuery` | `Long` |
104
+ | `Mango` | `MongoEntity` | `MongoQuery` | `String` |
105
+ | `Elastic` | `ElasticEntity` | `ElasticQuery` | `String` |
106
+
107
+ All mappers provide the same core operations: `find()`, `select()`, `update()`,
108
+ `delete()`, `tryUpdate()`, `tryDelete()`.
109
+
110
+ ## Descriptor-Based Validation
111
+
112
+ Validation runs automatically before save via the descriptor. Sources of
113
+ validation rules include:
114
+
115
+ - **@Length** — property length check
116
+ - **@NullAllowed** — null check (fields are non-null by default)
117
+ - **@Unique** — uniqueness check via database query
118
+ - **@OnValidate** — custom validation methods on the entity or composite
119
+ - **@ValidatedBy** — external validator class
120
+ - **@BeforeSave** — pre-save hooks that can throw to abort
121
+
122
+ ```java
123
+ @OnValidate
124
+ protected void validate(Consumer<String> validationConsumer) {
125
+ if (Strings.isEmpty(getName())) {
126
+ validationConsumer.accept("Name is required.");
127
+ }
128
+ }
129
+ ```
130
+
131
+ Validation messages collected by `@OnValidate` are warnings. To hard-fail,
132
+ throw an exception in a `@BeforeSave` handler instead.
133
+
134
+ ## Schema Synchronization
135
+
136
+ Mixing can automatically update database schemas at startup. The behavior is
137
+ controlled by `mixing.autoUpdateSchema` in the config:
138
+
139
+ - `"safe"` — executes non-destructive changes (add columns, create tables)
140
+ - `"all"` — executes all changes including potentially destructive ones
141
+ - `"off"` — no automatic schema changes
142
+
143
+ ## Mixins
144
+
145
+ Mixins add fields to existing entities without modifying them. A mixin targets
146
+ a specific entity type via `@Mixin`:
147
+
148
+ ```java
149
+ @Mixin(Product.class)
150
+ public class ProductExtension extends Mixable {
151
+ public static final Mapping EXTERNAL_ID = Mapping.named("externalId");
152
+ @Length(100) @NullAllowed
153
+ private String externalId;
154
+ }
155
+ ```
156
+
157
+ This adds an `externalId` column to the `Product` table. Useful for extending
158
+ framework-provided entities from application code.
159
+
160
+ ## Common Mistakes
161
+
162
+ 1. **Querying before Mixing is ready** — At startup, `Mixing.initialize()` must
163
+ complete before any database operations. In tests, await `oma.readyFuture`
164
+ or `mango.readyFuture`.
165
+
166
+ 2. **Using getDescriptor() for unregistered classes** — If the entity's framework
167
+ flag is disabled, `getDescriptor()` throws. Use `findDescriptor()` when the
168
+ entity may not exist.
169
+
170
+ 3. **Confusing property name and field name** — A composite field `person` with
171
+ a sub-field `firstname` produces property name `person_firstname`. The Java
172
+ field name is just `firstname`.
173
+
174
+ 4. **Ignoring the realm** — Entities default to the `"mixing"` realm. If your
175
+ application uses multiple databases, annotate entities with `@Realm("other")`
176
+ to direct them to the correct database.