@cratis/pi 0.0.1

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 (125) hide show
  1. package/README.md +43 -0
  2. package/package.json +24 -0
  3. package/skills/cratis-application-react-specifications/LICENSE +2 -0
  4. package/skills/cratis-application-react-specifications/SKILL.md +192 -0
  5. package/skills/cratis-application-slice-specifications/LICENSE +2 -0
  6. package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
  7. package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
  8. package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
  9. package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
  10. package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
  11. package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
  12. package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
  13. package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
  14. package/skills/cratis-arc-command/LICENSE +2 -0
  15. package/skills/cratis-arc-command/SKILL.md +286 -0
  16. package/skills/cratis-arc-command/references/command-result.md +111 -0
  17. package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
  18. package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
  19. package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
  20. package/skills/cratis-arc-command-execution/LICENSE +2 -0
  21. package/skills/cratis-arc-command-execution/SKILL.md +148 -0
  22. package/skills/cratis-arc-command-validation/LICENSE +2 -0
  23. package/skills/cratis-arc-command-validation/SKILL.md +230 -0
  24. package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
  25. package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
  26. package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
  27. package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
  28. package/skills/cratis-arc-query-paging/LICENSE +2 -0
  29. package/skills/cratis-arc-query-paging/SKILL.md +173 -0
  30. package/skills/cratis-arc-react-page/LICENSE +2 -0
  31. package/skills/cratis-arc-react-page/SKILL.md +291 -0
  32. package/skills/cratis-arc-react-page/references/data-page.md +125 -0
  33. package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
  34. package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
  35. package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
  36. package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
  37. package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
  38. package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
  39. package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
  40. package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
  41. package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
  42. package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
  43. package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
  44. package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
  45. package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
  46. package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
  47. package/skills/cratis-chronicle-compliance/LICENSE +2 -0
  48. package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
  49. package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
  50. package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
  51. package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
  52. package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
  53. package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
  54. package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
  55. package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
  56. package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
  57. package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
  58. package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
  59. package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
  60. package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
  61. package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
  62. package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
  63. package/skills/cratis-chronicle-projection/LICENSE +2 -0
  64. package/skills/cratis-chronicle-projection/SKILL.md +162 -0
  65. package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
  66. package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
  67. package/skills/cratis-chronicle-reactor/LICENSE +2 -0
  68. package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
  69. package/skills/cratis-chronicle-read-model/LICENSE +2 -0
  70. package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
  71. package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
  72. package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
  73. package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
  74. package/skills/cratis-chronicle-reducer/LICENSE +2 -0
  75. package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
  76. package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
  77. package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
  78. package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
  79. package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
  80. package/skills/cratis-code-review/LICENSE +2 -0
  81. package/skills/cratis-code-review/SKILL.md +177 -0
  82. package/skills/cratis-components-accessibility/LICENSE +2 -0
  83. package/skills/cratis-components-accessibility/SKILL.md +176 -0
  84. package/skills/cratis-components-schema-editor/LICENSE +2 -0
  85. package/skills/cratis-components-schema-editor/SKILL.md +238 -0
  86. package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
  87. package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
  88. package/skills/cratis-components-styling/LICENSE +2 -0
  89. package/skills/cratis-components-styling/SKILL.md +221 -0
  90. package/skills/cratis-components-toolbar/LICENSE +2 -0
  91. package/skills/cratis-components-toolbar/SKILL.md +259 -0
  92. package/skills/cratis-documentation-writing/LICENSE +2 -0
  93. package/skills/cratis-documentation-writing/SKILL.md +121 -0
  94. package/skills/cratis-event-model-diagram/LICENSE +2 -0
  95. package/skills/cratis-event-model-diagram/SKILL.md +142 -0
  96. package/skills/cratis-fundamentals-concept/LICENSE +2 -0
  97. package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
  98. package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
  99. package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
  100. package/skills/cratis-governed-release-methodology/LICENSE +2 -0
  101. package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
  102. package/skills/cratis-lens-browser-extension/LICENSE +2 -0
  103. package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
  104. package/skills/cratis-performance-review/LICENSE +2 -0
  105. package/skills/cratis-performance-review/SKILL.md +139 -0
  106. package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
  107. package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
  108. package/skills/cratis-security-review/LICENSE +2 -0
  109. package/skills/cratis-security-review/SKILL.md +141 -0
  110. package/skills/cratis-specification-by-example/LICENSE +2 -0
  111. package/skills/cratis-specification-by-example/SKILL.md +188 -0
  112. package/skills/cratis-specifications-csharp/LICENSE +2 -0
  113. package/skills/cratis-specifications-csharp/SKILL.md +230 -0
  114. package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
  115. package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
  116. package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
  117. package/skills/cratis-specifications-typescript/LICENSE +2 -0
  118. package/skills/cratis-specifications-typescript/SKILL.md +202 -0
  119. package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
  120. package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
  121. package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
  122. package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
  123. package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
  124. package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
  125. package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
@@ -0,0 +1,498 @@
1
+ ---
2
+ name: cratis-chronicle-client-kotlin
3
+ description: Talk to a Chronicle server from a Kotlin or Java application with the io.cratis:chronicle client - connection strings, ChronicleClient and the Spring Boot starter, @EventType classes, suspending append, reactors and reducers dispatched by first-parameter type, model-bound read models, classpath artifact discovery, and the blocking API Java uses. Use when building a JVM application that appends to or observes a Chronicle event store. Do not use for the .NET, TypeScript, or Elixir clients, and do not use for Chronicle kernel or Arc work.
4
+ license: MIT
5
+ ---
6
+
7
+ # The Chronicle client for Kotlin and Java
8
+
9
+ `io.cratis:chronicle` is a **standalone client SDK**. Your application is an
10
+ ordinary JVM application that happens to talk to a Chronicle server over gRPC.
11
+ There is no framework to inherit from and no application architecture imposed:
12
+ you construct a client, ask it for an event store, and append or observe.
13
+
14
+ ## Verified product sources
15
+
16
+ This skill is verified against this exact source:
17
+
18
+ | Artifact | Version | Verified from |
19
+ | --- | --- | --- |
20
+ | `io.cratis:chronicle` | `4.0.0` | `Chronicle.Kotlin` at tag `v4.0.0` (commit `63ff629`) |
21
+ | `io.cratis:chronicle-spring-boot-starter` | `4.0.0` | same tag, `Integrations/SpringBoot` |
22
+ | `io.cratis:chronicle-testing` | `4.0.0` | same tag, `Testing` |
23
+ | `io.cratis:chronicle-contracts` | `16.44.1` | `Source/build.gradle.kts:11` |
24
+
25
+ All three artifacts are published to Maven Central under `io.cratis`
26
+ (`Source/build.gradle.kts:54`, `Testing/build.gradle.kts:32`,
27
+ `Integrations/SpringBoot/build.gradle.kts:48`). The JVM toolchain is **17**
28
+ (`Source/build.gradle.kts:43`).
29
+
30
+ > **Do not copy a version number out of the repository's README or docs.** At
31
+ > `v4.0.0` the README still says `2.1.1` (`README.md:186`) and the documentation
32
+ > says `2.1.2` (`Documentation/get-started/index.md:24`). The version comes from
33
+ > a Gradle property injected at release time — `Source/build.gradle.kts:8` reads
34
+ > `providers.gradleProperty("version")` and defaults to `0.0.0-SNAPSHOT` — so
35
+ > the checked-in source never carries the real number. Take it from Maven
36
+ > Central.
37
+
38
+ > **The client and the kernel version independently.** Client `4.0.0` is built
39
+ > against `chronicle-contracts` `16.44.1`; the Chronicle server has since moved
40
+ > to a `17.x` line. Confirm the client/server pair you intend to run is
41
+ > supported before relying on it — do not infer compatibility from the fact that
42
+ > both are "latest".
43
+
44
+ ## Two ways in — pick one
45
+
46
+ | You are building | Use | Artifact |
47
+ | --- | --- | --- |
48
+ | A plain Kotlin/Java application, a CLI, a worker | `ChronicleClient` directly | `io.cratis:chronicle` |
49
+ | A Spring Boot application | the starter, and inject the beans | `io.cratis:chronicle-spring-boot-starter` |
50
+
51
+ The starter is a wrapper over the same client. Everything below about event
52
+ types, appending, and observers is identical either way; only the wiring differs.
53
+
54
+ ## Connecting
55
+
56
+ ### Plain Kotlin
57
+
58
+ ```kotlin
59
+ import io.cratis.chronicle.ChronicleClient
60
+ import io.cratis.chronicle.ChronicleOptions
61
+ import kotlinx.coroutines.runBlocking
62
+
63
+ fun main() = runBlocking {
64
+ val options = ChronicleOptions.development()
65
+ val client = ChronicleClient(options)
66
+ try {
67
+ val store = client.getEventStore("<EventStoreName>")
68
+ // ... use store ...
69
+ } finally {
70
+ client.dispose()
71
+ }
72
+ }
73
+ ```
74
+
75
+ `IChronicleClient` is small and complete —
76
+ `Source/src/main/kotlin/io/cratis/chronicle/IChronicleClient.kt`:
77
+
78
+ | Member | Line | Note |
79
+ | --- | --- | --- |
80
+ | `fun getEventStore(name: String, namespace: String = EventStoreNamespaceName.default.value): EventStore` | `:17-20` | **not** suspending; returns the concrete `EventStore` |
81
+ | `suspend fun getEventStores(): List<String>` | `:27` | asks the kernel |
82
+ | `fun evictEventStores()` | `:36` | drops the local cache, keeps the client |
83
+ | `fun dispose()` | `:39` | `IChronicleClient : AutoCloseable`, `close()` delegates to it (`:41`) |
84
+
85
+ **The client connects in its constructor.** `ChronicleClient.kt:13` is
86
+ `ChronicleConnection(options.connectionString).also { it.connect() }` — there is
87
+ no separate `connect()` step to call, and constructing the client is the
88
+ connecting act. Event stores are cached per `"$name/$namespace"`
89
+ (`ChronicleClient.kt:22`), so repeated `getEventStore` calls return the same
90
+ instance.
91
+
92
+ The default namespace is the literal `"Default"` —
93
+ `EventStoreNamespaceName.kt:13`.
94
+
95
+ ### Options
96
+
97
+ `ChronicleOptions` is a `data class` with `@JvmOverloads`
98
+ (`ChronicleOptions.kt:35`):
99
+
100
+ | Property | Default | Line |
101
+ | --- | --- | --- |
102
+ | `connectionString` | required | `:36` |
103
+ | `programIdentifier` | `"Unknown"` | `:37` |
104
+ | `defaultSinkTypeId` | `CHRONICLE_SINK_TYPE` env var, else `WellKnownSinkTypes.MONGODB` | `:38` |
105
+ | `autoDiscoverAndRegister` | `true` | `:39` |
106
+ | `artifacts` | `ClientArtifacts.default` (scans the classpath) | `:40` |
107
+ | `artifactActivator` | `ArtifactActivator` | `:41` |
108
+ | `openTelemetry` | `null` (uses the globally registered one) | `:42` |
109
+
110
+ Two `@JvmStatic` factories: `ChronicleOptions.fromConnectionString(String)`
111
+ (`:69`) and `ChronicleOptions.development()` (`:78`). Two instance helpers:
112
+ `withoutAutoRegistration()` (`:48`) and `withArtifactsFrom(vararg packages)`
113
+ (`:58`) — the second is worth using in any large application, because the
114
+ default scan walks the whole classpath.
115
+
116
+ ### The connection string
117
+
118
+ `ChronicleConnectionString.kt` documents the grammar at `:14-18`:
119
+
120
+ ```
121
+ chronicle://<host>[:<port>][,<host>[:<port>]...][?<options>]
122
+ chronicle://<username>:<password>@<host>[:<port>][,...][?<options>]
123
+ chronicle+srv://<host>[:<port>][?<options>]
124
+ ```
125
+
126
+ Default port is `35000` (`:39`). Recognized query keys, lowercased at parse time
127
+ (`:111-115`): `disabletls`, `skiptlsvalidation`, `apikey`, `loadbalancer`,
128
+ `srvnameserver`. `ChronicleConnectionString.parse(String)` is on the companion
129
+ (`:63`), and `ChronicleConnectionString.DEVELOPMENT` (`:51`) points at
130
+ `localhost:35000` with the `chronicle-dev-client` / `chronicle-dev-secret`
131
+ credentials (`:42-43`).
132
+
133
+ > **TLS is on, certificate validation is off, by default.** `disableTls = false`
134
+ > but `skipTlsValidation = true` — `ChronicleConnectionString.kt:27-28`, and the
135
+ > credential selection at `:180-181` installs an `InsecureTrustManager` in that
136
+ > case. That default exists because a development kernel serves a self-signed
137
+ > certificate. **A production connection string must carry
138
+ > `?skipTlsValidation=false`**, which is the only way to get chain validation
139
+ > against the platform trust store (`:174-176`).
140
+
141
+ ### Spring Boot
142
+
143
+ The starter registers exactly two auto-configurations —
144
+ `Integrations/SpringBoot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`:
145
+
146
+ - `io.cratis.chronicle.spring.ChronicleAutoConfiguration`
147
+ - `io.cratis.chronicle.spring.ChronicleWebAutoConfiguration`
148
+
149
+ Configuration binds under the prefix `cratis.chronicle` —
150
+ `ChronicleProperties.kt:42-43`. The minimum real configuration is one key, as in
151
+ the shipped sample (`Samples/Kotlin/SpringBoot/src/main/resources/application.yml`):
152
+
153
+ ```yaml
154
+ cratis:
155
+ chronicle:
156
+ event-store: <EventStoreName>
157
+ ```
158
+
159
+ Everything else has a default (`ChronicleProperties.kt:44-55`):
160
+
161
+ | Key under `cratis.chronicle` | Default | Line |
162
+ | --- | --- | --- |
163
+ | `connection-string` | the development connection string | `:44` |
164
+ | `event-store` | `"Default"` | `:45` |
165
+ | `namespace` | `"Default"` | `:46` |
166
+ | `auto-discover-and-register` | `true` | `:47` |
167
+ | `artifact-packages` | empty — falls back to Spring's auto-configuration packages | `:48` |
168
+ | `default-sink-type-id` | `null` | `:49` |
169
+ | `program-identifier` | `null` — falls back to `spring.application.name` | `:50` |
170
+ | `registration-timeout` | `PT30S` | `:51` |
171
+ | `namespace-resolution.strategy` | `FIXED` | `:65` |
172
+ | `namespace-resolution.http-header` | `"x-cratis-tenant-id"` | `:66` |
173
+ | `namespace-resolution.claim` | `"tenant_id"` | `:67` |
174
+
175
+ `NamespaceResolution.Strategy` is `FIXED`, `HTTP_HEADER`, `SUBDOMAIN`,
176
+ `AUTHENTICATION` (`ChronicleProperties.kt:70-82`).
177
+
178
+ The beans you inject are `IChronicleClient`, `IEventStore`, and the convenience
179
+ facade `Chronicle` (`ChronicleAutoConfiguration.kt:93-114`). `Chronicle` wraps
180
+ the suspending API in blocking calls and takes `Class<T>` rather than
181
+ `KClass<T>`, so Java can use it unchanged —
182
+ `Integrations/SpringBoot/src/main/kotlin/io/cratis/chronicle/spring/Chronicle.kt:47`,
183
+ `append` at `:57`, `appendMany` at `:69`.
184
+
185
+ `ChronicleWebAutoConfiguration` is **servlet-only**
186
+ (`@ConditionalOnWebApplication(SERVLET)`, `ChronicleWebAutoConfiguration.kt:29`).
187
+ A WebFlux application gets no per-request namespace, identity, causation, or
188
+ unit-of-work filter.
189
+
190
+ **Artifacts are Spring beans.** `SpringArtifactActivator` resolves a discovered
191
+ artifact from the container when it is uniquely defined and otherwise builds it
192
+ through `autowireCapableBeanFactory.createBean`
193
+ (`SpringArtifactActivator.kt:28-43`), so a reactor takes constructor
194
+ dependencies exactly like a `@Service` would.
195
+
196
+ ## Defining event types
197
+
198
+ ```kotlin
199
+ import io.cratis.chronicle.events.EventType
200
+
201
+ /** <What happened, in the past tense.> */
202
+ @EventType
203
+ data class <EventName>(
204
+ val <property>: <Type> = <default>
205
+ )
206
+ ```
207
+
208
+ `io.cratis.chronicle.events.EventType` — `events/EventType.kt:15-19`:
209
+
210
+ ```kotlin
211
+ annotation class EventType(
212
+ val id: String = "",
213
+ val generation: Int = 1,
214
+ val tombstone: Boolean = false
215
+ )
216
+ ```
217
+
218
+ **The id defaults to the class's *simple* name, not its fully qualified name** —
219
+ `events/EventTypesService.kt:67` resolves `ann.id.ifEmpty { cls.simpleName!! }`.
220
+ Two event classes with the same simple name in different packages therefore
221
+ collide on the wire. Give one an explicit `id`.
222
+
223
+ The house shape is a Kotlin `data class` with defaulted properties
224
+ (`Samples/Kotlin/SpringBoot/.../Events.kt:9-14`) or a Java `record`
225
+ (`Samples/Java/SpringBoot/.../EmployeeHired.java:9-10`). Property-level
226
+ annotations that travel with the event: `@io.cratis.chronicle.keys.Key`,
227
+ `@io.cratis.chronicle.compliance.Pii`, `@io.cratis.chronicle.Subject`,
228
+ `@io.cratis.chronicle.schemas.JsonSchemaType`.
229
+
230
+ Evolving a schema is `IEventTypeMigration<TTarget, TSource>` with `upcast` and
231
+ `downcast` — `events/migrations/IEventTypeMigration.kt:18-37`. Migrations
232
+ register in the same call as event types
233
+ (`artifacts/ArtifactRegistrations.kt:63`).
234
+
235
+ ## Appending
236
+
237
+ Everything that touches the kernel suspends. `IEventSequence.kt`:
238
+
239
+ ```kotlin
240
+ suspend fun append(eventSourceId: String, event: Any, options: AppendOptions? = null): AppendResult // :37
241
+ suspend fun appendMany(eventSourceId: String, events: List<Any>, options: AppendOptions? = null): List<AppendResult> // :47
242
+ suspend fun appendMany( // :65
243
+ events: List<EventForEventSourceId>,
244
+ concurrencyScopes: Map<String, ConcurrencyScope> = emptyMap(),
245
+ correlationId: UUID? = null
246
+ ): List<AppendResult>
247
+ ```
248
+
249
+ ```kotlin
250
+ val result = store.eventLog.append("<event-source-id>", <EventName>(<value>))
251
+ if (!result.isSuccess) {
252
+ // result.constraintViolations, result.concurrencyViolation, result.errors
253
+ }
254
+ ```
255
+
256
+ - **The event source id is a plain `String`** at every call site
257
+ (`IEventSequence.kt:37`). Typed alternatives taking `ConceptAs<String>` exist
258
+ as extension functions in `io.cratis.chronicle.concepts`
259
+ (`concepts/EventSourceIdConcepts.kt`).
260
+ - `AppendResult` carries `sequenceNumber`, `constraintViolations`, `errors`,
261
+ `isSuccess`, `concurrencyViolation` — `eventSequences/AppendResult.kt:18-33`.
262
+ It also carries `sequenceNumberValue: Long` (`:32`) purely so Java can read
263
+ the number, because `EventSequenceNumber` is a `@JvmInline value class` whose
264
+ getter Java cannot name.
265
+ - The three-argument `appendMany(List<EventForEventSourceId>, ...)` is the only
266
+ overload that commits atomically **across** event sources.
267
+ - Reach for the second overload's `EventForEventSourceId` when events in one
268
+ batch must go to different streams — the single-source overloads cannot
269
+ express that.
270
+
271
+ `AppendOptions` (`eventSequences/AppendOptions.kt:41-51`, `@JvmOverloads`)
272
+ carries `correlationId`, `concurrencyScope`, `eventSourceType`,
273
+ `eventStreamType`, `eventStreamId`, `subject`, `tags`, `occurred`, `causation`.
274
+
275
+ A unit of work spans several appends:
276
+
277
+ ```kotlin
278
+ val unitOfWork = store.unitOfWorkManager.begin()
279
+ store.eventLog.transactional.append("<id>", <EventName>(<value>))
280
+ store.eventLog.transactional.appendMany("<id>", listOf(<OtherEvent>()))
281
+ unitOfWork.commit()
282
+ ```
283
+
284
+ `transactional` appends return `Unit`, not an `AppendResult`; the results are on
285
+ the unit of work (`transactions/IUnitOfWork.kt:23-101`).
286
+
287
+ ## Observing
288
+
289
+ ### Handlers are found by their first parameter's type
290
+
291
+ This is the single most important convention in this client, and it is not the
292
+ method name. `observation/EventHandlerMethod.kt:61-74` reads a function as a
293
+ handler only when **parameter index 1 — the first real parameter — is a class
294
+ annotated with `@EventType`**. The method name is irrelevant. A handler may be
295
+ `suspend` or plain; both are invoked through `callSuspend`.
296
+
297
+ ```kotlin
298
+ import io.cratis.chronicle.events.EventContext
299
+ import io.cratis.chronicle.observation.Reactor
300
+
301
+ @Reactor
302
+ class <ReactorName>(private val <dependency>: <Dependency>) {
303
+ fun <anyMethodName>(event: <EventName>, context: EventContext): <SideEffectEvent> {
304
+ <dependency>.<doSomething>(event.<property>)
305
+ return <SideEffectEvent>(<property> = context.eventSourceId)
306
+ }
307
+ }
308
+ ```
309
+
310
+ That shape is the shipped sample verbatim
311
+ (`Samples/Kotlin/SpringBoot/.../WelcomePackageReactor.kt:16-22`).
312
+
313
+ **A returned event is appended as a side effect.** `null`, `Unit`, and any value
314
+ whose type is not annotated `@EventType` are ignored; a single event, an
315
+ `EventForEventSourceId`, or a `List` mixing both is appended, defaulting to the
316
+ triggering event source — `observation/ReactorSideEffects.kt:26-39`, `:48-59`.
317
+ This is how a reactor appends without ever touching the event log.
318
+
319
+ `EventContext` is a data class with `sequenceNumber: Long`, `eventSourceId`,
320
+ `eventType`, `occurred`, `correlationId`, `causedBy`, `eventSourceType`,
321
+ `eventStreamType`, `eventStreamId`, `eventStore`, `namespace`, `causation`,
322
+ `tags`, `hash`, `observationState` — `events/EventContext.kt:31-47`.
323
+
324
+ ### The annotations
325
+
326
+ | Annotation | Arguments | Source |
327
+ | --- | --- | --- |
328
+ | `@Reactor` | `id = ""`, `eventSequence = ""` | `observation/Reactor.kt:15` |
329
+ | `@Reducer` | `id = ""`, `eventSequence = ""`, `isActive = true` | `observation/Reducer.kt:17` |
330
+ | `@ReadModel` | `id = ""`, `displayName = ""` | `readModels/ReadModel.kt:19` |
331
+ | `@Projection` | `id = ""`, `eventSequence = ""` — optional | `projections/Projection.kt:22` |
332
+ | `@Constraint` | `id = ""` | `constraints/Constraint.kt:13` |
333
+ | `@Seeder` | none | `seeding/Seeder.kt:11` |
334
+
335
+ Handler-level: `@Replay`, `@OnceOnly`. Observer filtering: `@Tag`/`@Tags`,
336
+ `@FilterEventsByTag`/`@FilterEventsByTags`, `@EventSequence`,
337
+ `@EventSourceType`, `@EventStreamType`.
338
+
339
+ A reducer's handler may be `(event)`, `(event, state)`, or
340
+ `(event, state, context)` — the three shapes accepted by the dispatcher and by
341
+ `ReadModelScenario` (`Testing/.../ReadModelScenario.kt:146-150`). **Reducers run
342
+ client-side**: the kernel streams events and the handler is invoked in your
343
+ process (`observation/ReducersService.kt:119-140`).
344
+
345
+ ### Read models and model-bound projections
346
+
347
+ Read models are queried through `store.readModels`
348
+ (`readModels/IReadModelsService.kt`): `getInstanceByKey(readModelClass, key): T?`
349
+ (`:11`), `getInstances(readModelClass, eventCount)` (`:20`),
350
+ `getSnapshotsById` (`:29`), and `watch(readModelClass): Flow<ReadModelChangeset<T>>`
351
+ (`:37`, which is **not** suspending — it hands back a `Flow`).
352
+
353
+ A model-bound projection puts the projection on the read model with `@FromEvent`
354
+ (`projections/FromEvent.kt:17`) and `@SetFrom`
355
+ (`projections/SetFrom.kt:22`); the full family also includes `SetValue`,
356
+ `SetFromContext`, `AddFrom`, `SubtractFrom`, `Increment`, `Decrement`, `Count`,
357
+ `Join`, `RemovedWith`, `RemovedWithJoin`, `ChildrenFrom`, `ClearWith`,
358
+ `FromAll`, `FromEvery`, `Nested`, `NoAutoMap`, `NotRewindable`. The declarative
359
+ alternative is a class implementing `IProjectionFor<TReadModel>`.
360
+
361
+ ## Discovery and registration
362
+
363
+ `ClientArtifacts` scans the classpath with ClassGraph
364
+ (`artifacts/ClientArtifacts.kt:44-48`), and `ClientArtifacts.default` is a
365
+ process-wide lazy singleton (`:156`). What it looks for (`:66-98`):
366
+
367
+ | Kind | Rule |
368
+ | --- | --- |
369
+ | event types | `@EventType` |
370
+ | event type migrations | implements `IEventTypeMigration` |
371
+ | read models | `@ReadModel` |
372
+ | declarative projections | implements `IProjectionFor` |
373
+ | model-bound projections | `@FromEvent` **and** the synthetic `FromEvent$Container` |
374
+ | reactors | `@Reactor` |
375
+ | reducers | `@Reducer` |
376
+ | constraints | implements `IConstraint` |
377
+ | seeders | implements `ICanSeedEvents` |
378
+ | webhooks | implements `IWebhookDefiner` |
379
+ | captures | implements `ICapture` |
380
+
381
+ > The `FromEvent$Container` entry is not incidental: Kotlin's `@Repeatable`
382
+ > replaces repeated annotations with a synthetic container, so a class carrying
383
+ > more than one `@FromEvent` is **not** annotated with `@FromEvent` at runtime.
384
+ > A scan that looks only for the annotation silently misses every multi-event
385
+ > projection.
386
+
387
+ Registration order is fixed and matters
388
+ (`artifacts/ArtifactRegistrations.kt:59-91`): event types and migrations →
389
+ unowned read models → constraints → model-bound constraints → projections →
390
+ webhooks → reactors → reducers → captures → seeders. Reactors and reducers are
391
+ started only on the first pass (`:78-82`).
392
+
393
+ **Registration re-runs on every reconnect.** `EventStore.kt:226-241` launches a
394
+ coroutine on `Dispatchers.IO` collecting the connection lifecycle and
395
+ re-registers each time. The connection id rotates on every disconnect because
396
+ the kernel keys observer subscriptions by it
397
+ (`connection/ConnectionLifecycle.kt:24-32`), so observers must re-register — and
398
+ they do.
399
+
400
+ `store.awaitRegistration()` (`IEventStore.kt:92`) waits for the first pass.
401
+ `store.registerAll()` (`:83`) runs it by hand when
402
+ `autoDiscoverAndRegister = false`.
403
+
404
+ ## Java
405
+
406
+ Java is a first-class target here, not an afterthought: there is a compile-only
407
+ Java conformance suite under `Source/src/test/java` whose whole point is stated
408
+ in `conformance/JavaConformance.java:71-74` — *"It is never run — compiling it is
409
+ the assertion"*.
410
+
411
+ **Start Java code at `BlockingChronicleClient`**, not at the raw `ChronicleClient`
412
+ plus static bridges. Verbatim from the compile-checked fixture
413
+ (`Source/src/test/java/io/cratis/chronicle/java/JavaClientFlowUsage.java:29-34`):
414
+
415
+ ```java
416
+ var client = BlockingChronicleClient.connect(ChronicleOptions.development());
417
+ var eventStore = client.getEventStore("<EventStoreName>");
418
+
419
+ eventStore.getEventLog().append("<event-source-id>", new <EventName>("<value>"));
420
+ ```
421
+
422
+ `BlockingChronicleClient` is `AutoCloseable`, so `try (var client = ...)` works
423
+ (`java/BlockingChronicleClient.kt:35`, `connect` at `:74-76`). The blocking
424
+ surface continues through `BlockingEventStore`, `BlockingEventSequence`,
425
+ `BlockingReadModels`, `BlockingReactors`, `BlockingReducers`,
426
+ `BlockingUnitOfWork`, and `AppendOptionsBuilder`.
427
+
428
+ > The repository's own README shows the **older** low-level route —
429
+ > `new ChronicleClient(...)` plus `EventStoreJavaBridge` / `EventLogJavaBridge`
430
+ > (`README.md:225-244`). Both APIs are real, but the reference documentation and
431
+ > the compile-checked fixture both start at `BlockingChronicleClient`. Write new
432
+ > Java against that; reach for the `*JavaBridge` statics only for a corner the
433
+ > blocking client does not wrap.
434
+
435
+ In Spring Boot, Java injects the `Chronicle` bean instead — it already takes
436
+ `Class<T>` and returns plain values.
437
+
438
+ ## Connection lifecycle
439
+
440
+ - **Keepalive is two-way.** The kernel pushes a keep-alive down the `Connect`
441
+ stream and the client answers with a separate unary `connectionKeepAlive` RPC
442
+ (`connection/ConnectionManager.kt:30`). A watchdog checks every
443
+ `WATCHDOG_INTERVAL_MS = 1_000L` and treats a gap longer than
444
+ `KEEP_ALIVE_TIMEOUT_MS = 5_000L` as a lost connection (`:126-128`, `:164`,
445
+ `:171`). **Silence, not an error, is how the connection dies** — nothing throws.
446
+ - Reconnect is an infinite loop with jittered exponential backoff, base 1s,
447
+ capped at 30s, re-resolving DNS/SRV on every attempt
448
+ (`ConnectionManager.kt:75-101`, `:155-158`).
449
+ - The client identifies itself to the kernel as `"Kotlin"`
450
+ (`ConnectionManager.kt:161`).
451
+ - `dispose()` cancels the connection manager, shuts the channel down with a
452
+ 5-second `awaitTermination`, then `shutdownNow`
453
+ (`connection/ChronicleConnection.kt:116-125`).
454
+ - **Your process must stay alive** for reactors and reducers to keep receiving —
455
+ observation is a live gRPC stream, and each observer runs on its own
456
+ `CoroutineScope(Dispatchers.IO)` (`observation/ReactorsService.kt:57`,
457
+ `observation/ReducersService.kt:59`).
458
+
459
+ ## Testing
460
+
461
+ `io.cratis:chronicle-testing` runs in-process with no kernel and no Docker:
462
+ `EventScenario` (`Testing/.../EventScenario.kt:35`) and
463
+ `ReadModelScenario<TReadModel>` (`Testing/.../ReadModelScenario.kt:47`), which
464
+ folds a reducer through the same handler-shape rules as production
465
+ (`ReadModelScenario.kt:139-150`). Its scope is small — appends and reducer folds
466
+ only; there is no in-process reactor, projection, or constraint scenario
467
+ (`Testing/api/Testing.api` is 56 lines).
468
+
469
+ ## Common pitfalls
470
+
471
+ | Pitfall | Why it bites |
472
+ | --- | --- |
473
+ | Naming a reactor method after the event | The name is ignored; **the first parameter's type** is the subscription (`EventHandlerMethod.kt:61-74`) |
474
+ | Two `@EventType` classes sharing a simple name | The id defaults to the simple name, so they collide on the wire (`EventTypesService.kt:67`) |
475
+ | Shipping the default connection string to production | `skipTlsValidation` defaults to **true**; certificate validation is off (`ChronicleConnectionString.kt:28`) |
476
+ | Copying a version from the README or docs | Both are stale at `v4.0.0`; the real version comes from the release, not the source |
477
+ | Expecting `getEventStore` to suspend | It does not — the client already connected in its constructor (`ChronicleClient.kt:13`) |
478
+ | Expecting `awaitRegistration()` to mean "registered" | It completes in a `finally`, so it also returns after a failed pass (`ArtifactRegistrations.kt:47-57`) |
479
+ | Expecting a registration failure to throw | Failures are printed to `System.err`, not raised (`EventStore.kt:236`) |
480
+ | Expecting read model reactors to be discovered | `IReadModelReactor` is absent from the scan and from `IEventStore`; construct `ReadModelReactors(...)` yourself |
481
+ | Leaving the default classpath scan on in a large app | Use `withArtifactsFrom(...)` or `artifact-packages` to narrow it (`ChronicleOptions.kt:58`) |
482
+ | Expecting WebFlux support from the starter | The web auto-configuration is `SERVLET`-only (`ChronicleWebAutoConfiguration.kt:29`) |
483
+ | Exiting `main` after an append | Reactors and reducers stop with the process; the stream is live |
484
+
485
+ ## Verify
486
+
487
+ - The dependency resolves from Maven Central and the version is the one you
488
+ intended — not a number copied from a README.
489
+ - A connect against the target kernel succeeds, and the client/server version
490
+ pair is one you confirmed rather than assumed.
491
+ - A production connection string sets `skipTlsValidation=false`.
492
+ - Every `@EventType` class has a unique simple name, or an explicit `id`.
493
+ - Every reactor and reducer handler's **first parameter** is an `@EventType`
494
+ class.
495
+ - `awaitRegistration()` is followed by a check that registration actually
496
+ succeeded, not treated as proof on its own.
497
+ - The build is clean and the specifications pass against the verified artifact
498
+ version.
@@ -0,0 +1,2 @@
1
+ Copyright (c) Cratis. All rights reserved.
2
+ Licensed under the MIT license. See LICENSE file in the project root for full license information.