@cratis/pi 2.27.0 → 2.28.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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cratis-chronicle-client-elixir
|
|
3
|
-
description: Talk to a Chronicle server from an Elixir application with the cratis_chronicle Hex package - putting Chronicle.Client in a supervision tree, connection strings, use Chronicle.Events.EventType structs, Chronicle.append returning ok or error tuples, reactors with the @handles attribute and a handle/2 callback, model-bound read models, and the connection lifecycle phases and keepalive. Use when an OTP application appends to or observes a Chronicle event store. Do not use for the .NET, TypeScript, or Kotlin clients.
|
|
3
|
+
description: Talk to a Chronicle server from an Elixir application with the cratis_chronicle Hex package - putting Chronicle.Client in a supervision tree, connection strings, use Chronicle.Events.EventType structs, Chronicle.append returning ok or error tuples, reactors with the @handles attribute and a handle/2 callback, model-bound and declarative read models/projections, variant_of/enters_on/GlobalHandler for an entity with mutually exclusive lifecycle shapes, and the connection lifecycle phases and keepalive. Use when an OTP application appends to or observes a Chronicle event store. Do not use for the .NET, TypeScript, or Kotlin clients.
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -100,7 +100,7 @@ All configuration is **child-spec keyword options**. There is no
|
|
|
100
100
|
| `:namespace` | `"Default"` — capital D | `:160` |
|
|
101
101
|
| `:discover` | `true` | `:161` |
|
|
102
102
|
| `:otp_app` | none; falls back to scanning loaded modules | `:168` |
|
|
103
|
-
| `:event_types`, `:migrations`, `:reactors`, `:reducers`, `:read_models`, `:projections`, `:seeders`, `:webhooks`, `:event_store_subscriptions` | `[]`, merged with discovered | `:186-197` |
|
|
103
|
+
| `:event_types`, `:migrations`, `:reactors`, `:reducers`, `:read_models`, `:projections`, `:seeders`, `:webhooks`, `:event_store_subscriptions`, `:global_handlers` | `[]`, merged with discovered | `:186-197` |
|
|
104
104
|
| `:skip_tls_validation`, `:load_balancer`, `:grpc_options`, `:retry_attempts`, `:reconnect_base_delay`, `:reconnect_max_delay` | forwarded to the connection | — |
|
|
105
105
|
|
|
106
106
|
> The moduledoc at `client.ex:81` says the namespace defaults to `"default"`.
|
|
@@ -311,6 +311,78 @@ A standalone projection is `use Chronicle.Projections.Projection, model: Mod`
|
|
|
311
311
|
`reduce(event, model_or_nil, context) :: struct()`. **Reducers run in your
|
|
312
312
|
process**, so the reduction is Elixir code you own.
|
|
313
313
|
|
|
314
|
+
#### Variants — mutually exclusive read models for one entity's lifecycle
|
|
315
|
+
|
|
316
|
+
> Requires `cratis_chronicle` `3.4.0` or later — newer than this skill's
|
|
317
|
+
> `3.1.0` baseline (`lib/chronicle/projections/variant_reclassifier.ex` and
|
|
318
|
+
> siblings). Reverify before claiming support; take the version from hex.pm.
|
|
319
|
+
|
|
320
|
+
Some entities do not have one shape for their whole lifetime — a work item is a
|
|
321
|
+
backlog entry until a pull request exists for it, then it is a pull request
|
|
322
|
+
until it merges. `variant_of/2` and `enters_on/1,2` are macros imported by
|
|
323
|
+
**both** `use Chronicle.ReadModels.ReadModel` and
|
|
324
|
+
`use Chronicle.Projections.Projection`, so the model-bound and declarative
|
|
325
|
+
paths declare a variant identically:
|
|
326
|
+
|
|
327
|
+
```elixir
|
|
328
|
+
defmodule MyApp.ReadModels.WorkItem do
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
defmodule MyApp.ReadModels.BacklogItem do
|
|
332
|
+
use Chronicle.ReadModels.ReadModel
|
|
333
|
+
defstruct id: nil, title: nil
|
|
334
|
+
|
|
335
|
+
variant_of MyApp.ReadModels.WorkItem, key: :id
|
|
336
|
+
enters_on MyApp.Events.IssueCreated
|
|
337
|
+
|
|
338
|
+
from MyApp.Events.IssueCreated, set: [id: :event_source_id, title: :title]
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
defmodule MyApp.ReadModels.PullRequestItem do
|
|
342
|
+
use Chronicle.ReadModels.ReadModel
|
|
343
|
+
defstruct id: nil, pull_request_url: nil, build_status: nil
|
|
344
|
+
|
|
345
|
+
variant_of MyApp.ReadModels.WorkItem, key: :id
|
|
346
|
+
enters_on MyApp.Events.PullRequestCreated
|
|
347
|
+
|
|
348
|
+
from MyApp.Events.PullRequestCreated,
|
|
349
|
+
set: [id: :event_source_id, pull_request_url: :pull_request_url]
|
|
350
|
+
|
|
351
|
+
# not the entering event -> automatically reclassified into an update-only join
|
|
352
|
+
from MyApp.Events.BuildCompleted, set: [build_status: :build_status]
|
|
353
|
+
end
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`variant_of/2` takes `:key` — **required** — the field on this variant that
|
|
357
|
+
carries the shared identity. `enters_on/1,2` is repeatable and its own `:key`
|
|
358
|
+
option names an *event* property (defaults to `:event_source_id`); every
|
|
359
|
+
`from`/`join` this variant declares for a non-entering event, whether declared
|
|
360
|
+
locally or merged from a shared handler, is automatically reclassified into an
|
|
361
|
+
update-only join keyed on `variant_of`'s `:key`.
|
|
362
|
+
|
|
363
|
+
A mapping shared across every variant of an identity is a
|
|
364
|
+
`Chronicle.Projections.GlobalHandler`, never registered as a projection on its
|
|
365
|
+
own:
|
|
366
|
+
|
|
367
|
+
```elixir
|
|
368
|
+
defmodule MyApp.Projections.WorkItemTitleHandler do
|
|
369
|
+
use Chronicle.Projections.GlobalHandler, identity: MyApp.ReadModels.WorkItem
|
|
370
|
+
|
|
371
|
+
from MyApp.Events.TitleChanged, set: [title: :title]
|
|
372
|
+
end
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Register it explicitly with `global_handlers: [...]` on `Chronicle.Client`, or
|
|
376
|
+
let `:otp_app` auto-discovery find it (modules exporting
|
|
377
|
+
`__chronicle_global_handler__/1`). A mapping that targets a field some variant
|
|
378
|
+
lacks raises `Chronicle.Projections.GlobalHandlerPropertyNotOnVariant` at
|
|
379
|
+
registration, not a silently skipped mapping.
|
|
380
|
+
|
|
381
|
+
**A variant with no `enters_on` raises
|
|
382
|
+
`Chronicle.Projections.VariantMustDeclareEntersOnEvent`** at registration — a
|
|
383
|
+
variant that could never be entered could never be written to at all, since
|
|
384
|
+
every other handler on it is update-only.
|
|
385
|
+
|
|
314
386
|
Querying (`lib/chronicle/read_models.ex`):
|
|
315
387
|
|
|
316
388
|
```elixir
|
|
@@ -416,6 +488,8 @@ attempt.
|
|
|
416
488
|
| Appending from a `Task` and expecting the correlation id | Ambient context is per process |
|
|
417
489
|
| Treating quiet observers as "no events" | Keepalive eviction silences observers while appends still succeed |
|
|
418
490
|
| Shipping the default TLS behavior | `skip_tls_validation` defaults to `true` |
|
|
491
|
+
| Forgetting `global_handlers:` or `:otp_app` discovery | A `GlobalHandler` module is never merged into its variants unless registered one way or the other |
|
|
492
|
+
| A shared handler mapping a field one variant lacks | `GlobalHandlerPropertyNotOnVariant` at registration, not a silently skipped mapping |
|
|
419
493
|
|
|
420
494
|
## Verify
|
|
421
495
|
|
|
@@ -428,6 +502,8 @@ attempt.
|
|
|
428
502
|
- Every `Chronicle.append/3` call site handles `{:error, _}` as well as `:ok`.
|
|
429
503
|
- Every reactor's `@handles` list matches the clauses of its `handle/2`.
|
|
430
504
|
- Read model module names have distinct final segments, or explicit ids.
|
|
505
|
+
- Every variant group has at least one `enters_on` per variant, and every
|
|
506
|
+
`GlobalHandler` member exists on every variant it targets.
|
|
431
507
|
- Every code example was copied from `Documentation/client-snippets/`, not from
|
|
432
508
|
the README.
|
|
433
509
|
- `mix compile --warnings-as-errors` and `mix test` are clean.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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
|
|
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 and declarative projections, variants (@VariantOf/@EntersOn/@GlobalFor or variantOf()/entersOn()) for an entity with mutually exclusive lifecycle shapes, 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
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -367,6 +367,84 @@ A model-bound projection puts the projection on the read model with `@FromEvent`
|
|
|
367
367
|
`NotRewindable`. The declarative
|
|
368
368
|
alternative is a class implementing `IProjectionFor<TReadModel>`.
|
|
369
369
|
|
|
370
|
+
### Variants — mutually exclusive read models for one entity's lifecycle
|
|
371
|
+
|
|
372
|
+
> Requires `io.cratis:chronicle` `6.3.0` or later — newer than this skill's
|
|
373
|
+
> `5.1.0` baseline (`Source/src/main/kotlin/io/cratis/chronicle/projections/VariantOf.kt`
|
|
374
|
+
> and siblings). Reverify before claiming support; take the version from Maven
|
|
375
|
+
> Central, not the checked-in source.
|
|
376
|
+
|
|
377
|
+
Some entities do not have one shape for their whole lifetime — a work item is a
|
|
378
|
+
backlog entry until a pull request exists for it, then it is a pull request
|
|
379
|
+
until it merges. **Variants** let several read models share one logical
|
|
380
|
+
identity and be mutually exclusive: entering one variant removes the entity
|
|
381
|
+
from every other variant in the group, and only the event named by
|
|
382
|
+
`@EntersOn`/`entersOn(...)` can create or resurrect a variant — every other
|
|
383
|
+
event it handles is automatically reclassified into an update-only join.
|
|
384
|
+
|
|
385
|
+
**Model-bound** (the annotations are ordinary Java annotations, so Kotlin and
|
|
386
|
+
Java use the identical shape):
|
|
387
|
+
|
|
388
|
+
```kotlin
|
|
389
|
+
class WorkItem // anchors the group; not itself a read model
|
|
390
|
+
|
|
391
|
+
@ReadModel
|
|
392
|
+
@VariantOf(WorkItem::class, key = "id")
|
|
393
|
+
@EntersOn(IssueCreated::class)
|
|
394
|
+
@FromEvent(IssueCreated::class)
|
|
395
|
+
data class BacklogItem(
|
|
396
|
+
@FromEventSourceId val id: String = "",
|
|
397
|
+
@SetFrom("title", IssueCreated::class) val title: String = ""
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
@ReadModel
|
|
401
|
+
@VariantOf(WorkItem::class, key = "id")
|
|
402
|
+
@EntersOn(PullRequestCreated::class)
|
|
403
|
+
@FromEvent(PullRequestCreated::class)
|
|
404
|
+
@FromEvent(BuildCompleted::class) // not the entering event -> update-only join
|
|
405
|
+
data class PullRequestItem(
|
|
406
|
+
@FromEventSourceId val id: String = "",
|
|
407
|
+
@SetFrom("pullRequestUrl", PullRequestCreated::class) val pullRequestUrl: String = "",
|
|
408
|
+
@SetFrom("buildStatus", BuildCompleted::class) val buildStatus: String = ""
|
|
409
|
+
)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
`@VariantOf(identity, key)` — **`key` is always an explicit property-name
|
|
413
|
+
string**, on both Kotlin and Java, because reflection resolves it rather than a
|
|
414
|
+
compile-time property reference. `@EntersOn(eventType, key = "EventSourceId")`
|
|
415
|
+
is repeatable — a variant may enter on more than one event — and its `key`
|
|
416
|
+
names an *event* property (not the read model's), defaulting to the event
|
|
417
|
+
source id. A mapping shared by every variant of an identity goes on a type
|
|
418
|
+
annotated `@GlobalFor(identity)` instead of being repeated per variant; every
|
|
419
|
+
variant it targets must actually have the member it maps, or
|
|
420
|
+
`GlobalHandlerPropertyNotOnVariant` is thrown at registration.
|
|
421
|
+
|
|
422
|
+
**Declarative** — `variantOf` and `entersOn` are members of
|
|
423
|
+
`IProjectionBuilderFor<TReadModel>` itself:
|
|
424
|
+
|
|
425
|
+
```kotlin
|
|
426
|
+
class PullRequestItemProjection : IProjectionFor<PullRequestItem> {
|
|
427
|
+
override fun define(builder: IProjectionBuilderFor<PullRequestItem>) {
|
|
428
|
+
builder
|
|
429
|
+
.variantOf(WorkItem::class, PullRequestItem::id) // or variantOf(WorkItem::class, "id")
|
|
430
|
+
.entersOn(PullRequestCreated::class)
|
|
431
|
+
.from(BuildCompleted::class) // update-only, same reason
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Java calls the same builder with a `Class` and a string key — there is no
|
|
437
|
+
property-reference overload in Java:
|
|
438
|
+
|
|
439
|
+
```java
|
|
440
|
+
builder.variantOf(WorkItem.class, "id").entersOn(PullRequestCreated.class);
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**A variant that declares no `@EntersOn`/`entersOn(...)` throws
|
|
444
|
+
`VariantMustDeclareEntersOnEvent`** at registration — a variant that could
|
|
445
|
+
never be entered could never be written to at all, since every other handler
|
|
446
|
+
on it is update-only.
|
|
447
|
+
|
|
370
448
|
## Discovery and registration
|
|
371
449
|
|
|
372
450
|
`ClientArtifacts` scans the classpath with ClassGraph
|
|
@@ -492,6 +570,8 @@ only; there is no in-process reactor, projection, or constraint scenario
|
|
|
492
570
|
| Leaving the default classpath scan on in a large app | Use `withArtifactsFrom(...)` or `artifact-packages` to narrow it (`ChronicleOptions.kt:58`) |
|
|
493
571
|
| Expecting WebFlux support from the starter | The web auto-configuration is `SERVLET`-only (`ChronicleWebAutoConfiguration.kt:29`) |
|
|
494
572
|
| Exiting `main` after an append | Reactors and reducers stop with the process; the stream is live |
|
|
573
|
+
| Passing a `KProperty1` as `@VariantOf`'s `key` | The annotation form always takes a plain string; only the declarative `variantOf(...)` builder accepts a property reference |
|
|
574
|
+
| A `@GlobalFor` mapping targeting a property one variant lacks | `GlobalHandlerPropertyNotOnVariant` at registration, not a silently skipped mapping |
|
|
495
575
|
|
|
496
576
|
## Verify
|
|
497
577
|
|
|
@@ -505,5 +585,7 @@ only; there is no in-process reactor, projection, or constraint scenario
|
|
|
505
585
|
class.
|
|
506
586
|
- `awaitRegistration()` is followed by a check that registration actually
|
|
507
587
|
succeeded, not treated as proof on its own.
|
|
588
|
+
- Every variant group has at least one `@EntersOn`/`entersOn(...)` per variant,
|
|
589
|
+
and every `@GlobalFor` member exists on every variant it targets.
|
|
508
590
|
- The build is clean and the specifications pass against the verified artifact
|
|
509
591
|
version.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cratis-chronicle-client-typescript
|
|
3
|
-
description: Talk to a Chronicle server from a Node.js or TypeScript application with @cratis/chronicle - reflect-metadata and decorator compiler settings, ChronicleClient and connection strings, @eventType classes, eventLog.append, reactors and reducers dispatched by camelCase method name, model-bound and declarative projections, glob-based artifact discovery, and the lazy connect and keepalive lifecycle. Use when a Node application appends to or observes a Chronicle event store. Do not use for the .NET, Kotlin, or Elixir clients, and do not use for Arc React frontends or generated Arc proxies.
|
|
3
|
+
description: Talk to a Chronicle server from a Node.js or TypeScript application with @cratis/chronicle - reflect-metadata and decorator compiler settings, ChronicleClient and connection strings, @eventType classes, eventLog.append, reactors and reducers dispatched by camelCase method name, model-bound and declarative projections, variants (@variantOf/@entersOn/@globalFor or .variantOf()/.entersOn()) for an entity with mutually exclusive lifecycle shapes, glob-based artifact discovery, and the lazy connect and keepalive lifecycle. Use when a Node application appends to or observes a Chronicle event store. Do not use for the .NET, Kotlin, or Elixir clients, and do not use for Arc React frontends or generated Arc proxies.
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -317,6 +317,70 @@ Constraints are `@constraint()` on a class implementing `IConstraint` with a
|
|
|
317
317
|
model-bound constraint decorators in this client** — the class-plus-builder form
|
|
318
318
|
is the only one.
|
|
319
319
|
|
|
320
|
+
### Variants — mutually exclusive read models for one entity's lifecycle
|
|
321
|
+
|
|
322
|
+
> Requires `@cratis/chronicle` `6.2.0` or later — newer than this skill's
|
|
323
|
+
> `5.1.0` baseline (`Source/projections/VariantReclassifier.ts` and siblings).
|
|
324
|
+
> Reverify before claiming support; take the version from npm.
|
|
325
|
+
|
|
326
|
+
Some entities do not have one shape for their whole lifetime — a work item is a
|
|
327
|
+
backlog entry until a pull request exists for it, then it is a pull request
|
|
328
|
+
until it merges. **Model-bound** — `@variantOf(identity, key)` and
|
|
329
|
+
`@entersOn(eventType, key?)`, alongside the ordinary `@fromEvent`/`@setFrom`
|
|
330
|
+
decorators:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
class WorkItem {} // anchors the group; not itself a read model
|
|
334
|
+
|
|
335
|
+
@variantOf(WorkItem, 'id')
|
|
336
|
+
@entersOn(IssueCreated)
|
|
337
|
+
@fromEvent(IssueCreated)
|
|
338
|
+
@readModel()
|
|
339
|
+
class BacklogItem {
|
|
340
|
+
id = '';
|
|
341
|
+
@setFrom(IssueCreated, 'title') title = '';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@variantOf(WorkItem, 'id')
|
|
345
|
+
@entersOn(PullRequestCreated)
|
|
346
|
+
@fromEvent(PullRequestCreated)
|
|
347
|
+
@fromEvent(BuildCompleted) // not the entering event -> update-only join
|
|
348
|
+
@readModel()
|
|
349
|
+
class PullRequestItem {
|
|
350
|
+
id = '';
|
|
351
|
+
@setFrom(PullRequestCreated, 'pullRequestUrl') pullRequestUrl = '';
|
|
352
|
+
@setFrom(BuildCompleted, 'buildStatus') buildStatus = '';
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`entersOn` is repeatable (a variant may enter on more than one event) and its
|
|
357
|
+
`key` argument names an *event* property, defaulting to the event source id. A
|
|
358
|
+
mapping shared by every variant of an identity goes on a class decorated
|
|
359
|
+
`@globalFor(identity)` instead of being repeated on each variant — every
|
|
360
|
+
variant it targets must actually declare the property it maps, or
|
|
361
|
+
`GlobalHandlerPropertyNotOnVariant` is thrown when the group is built. A class
|
|
362
|
+
carrying only `@globalFor` is never itself registered as a projection.
|
|
363
|
+
|
|
364
|
+
**Declarative** — `variantOf` and `entersOn` are members of
|
|
365
|
+
`IProjectionBuilderFor<TReadModel>` itself:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
@projection()
|
|
369
|
+
class PullRequestItemProjection implements IProjectionFor<PullRequestItem> {
|
|
370
|
+
define(builder: IProjectionBuilderFor<PullRequestItem>): void {
|
|
371
|
+
builder
|
|
372
|
+
.variantOf(WorkItem, m => m.id)
|
|
373
|
+
.entersOn(PullRequestCreated)
|
|
374
|
+
.from(BuildCompleted); // update-only, same reason
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**A variant that declares no `@entersOn`/`entersOn(...)` throws
|
|
380
|
+
`VariantMustDeclareEntersOnEvent`** when the group is built — a variant that
|
|
381
|
+
could never be entered could never be written to at all, since every other
|
|
382
|
+
handler on it is update-only.
|
|
383
|
+
|
|
320
384
|
## Discovery is a runtime file glob — this is the biggest difference
|
|
321
385
|
|
|
322
386
|
`ChronicleOptions.discoveryPatterns` defaults to
|
|
@@ -382,6 +446,8 @@ not work**. Use module-scope collaborators, as the shipped sample does.
|
|
|
382
446
|
| Constructor-injecting a dependency into a reactor | There is no DI; the client constructs it |
|
|
383
447
|
| Carrying a `3.x` example forward | Tags, the reducer context parameter, and two RPC names changed in `4.0.0` |
|
|
384
448
|
| Reading `result.sequenceNumber` as a number | It is a `bigint` behind `.value` |
|
|
449
|
+
| A `@globalFor` mapping targeting a property one variant lacks | `GlobalHandlerPropertyNotOnVariant` when the group is built, not a silently skipped mapping |
|
|
450
|
+
| Expecting a `@globalFor`-only class to appear as a projection | It is never registered on its own; it is merged into its variants |
|
|
385
451
|
|
|
386
452
|
## Verify
|
|
387
453
|
|
|
@@ -397,4 +463,6 @@ not work**. Use module-scope collaborators, as the shipped sample does.
|
|
|
397
463
|
class, and an appended event demonstrably reaches it.
|
|
398
464
|
- A production connection string carries credentials and `skipTlsValidation=false`.
|
|
399
465
|
- `client.dispose()` runs on shutdown.
|
|
466
|
+
- Every variant group has at least one `@entersOn`/`entersOn(...)` per variant,
|
|
467
|
+
and every `@globalFor` member exists on every variant it targets.
|
|
400
468
|
- Lint, `tsc`, and the test suite are clean against the verified package version.
|