@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.
- package/README.md +43 -0
- package/package.json +24 -0
- package/skills/cratis-application-react-specifications/LICENSE +2 -0
- package/skills/cratis-application-react-specifications/SKILL.md +192 -0
- package/skills/cratis-application-slice-specifications/LICENSE +2 -0
- package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
- package/skills/cratis-arc-command/LICENSE +2 -0
- package/skills/cratis-arc-command/SKILL.md +286 -0
- package/skills/cratis-arc-command/references/command-result.md +111 -0
- package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
- package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
- package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
- package/skills/cratis-arc-command-execution/LICENSE +2 -0
- package/skills/cratis-arc-command-execution/SKILL.md +148 -0
- package/skills/cratis-arc-command-validation/LICENSE +2 -0
- package/skills/cratis-arc-command-validation/SKILL.md +230 -0
- package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
- package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
- package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
- package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
- package/skills/cratis-arc-query-paging/LICENSE +2 -0
- package/skills/cratis-arc-query-paging/SKILL.md +173 -0
- package/skills/cratis-arc-react-page/LICENSE +2 -0
- package/skills/cratis-arc-react-page/SKILL.md +291 -0
- package/skills/cratis-arc-react-page/references/data-page.md +125 -0
- package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
- package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
- package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
- package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
- package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
- package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
- package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
- package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
- package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
- package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
- package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
- package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
- package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
- package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
- package/skills/cratis-chronicle-compliance/LICENSE +2 -0
- package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
- package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
- package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
- package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
- package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
- package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
- package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
- package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
- package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
- package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
- package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
- package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
- package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
- package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
- package/skills/cratis-chronicle-projection/LICENSE +2 -0
- package/skills/cratis-chronicle-projection/SKILL.md +162 -0
- package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
- package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
- package/skills/cratis-chronicle-reactor/LICENSE +2 -0
- package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
- package/skills/cratis-chronicle-read-model/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
- package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
- package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
- package/skills/cratis-chronicle-reducer/LICENSE +2 -0
- package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
- package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
- package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
- package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
- package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
- package/skills/cratis-code-review/LICENSE +2 -0
- package/skills/cratis-code-review/SKILL.md +177 -0
- package/skills/cratis-components-accessibility/LICENSE +2 -0
- package/skills/cratis-components-accessibility/SKILL.md +176 -0
- package/skills/cratis-components-schema-editor/LICENSE +2 -0
- package/skills/cratis-components-schema-editor/SKILL.md +238 -0
- package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
- package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
- package/skills/cratis-components-styling/LICENSE +2 -0
- package/skills/cratis-components-styling/SKILL.md +221 -0
- package/skills/cratis-components-toolbar/LICENSE +2 -0
- package/skills/cratis-components-toolbar/SKILL.md +259 -0
- package/skills/cratis-documentation-writing/LICENSE +2 -0
- package/skills/cratis-documentation-writing/SKILL.md +121 -0
- package/skills/cratis-event-model-diagram/LICENSE +2 -0
- package/skills/cratis-event-model-diagram/SKILL.md +142 -0
- package/skills/cratis-fundamentals-concept/LICENSE +2 -0
- package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
- package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
- package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
- package/skills/cratis-governed-release-methodology/LICENSE +2 -0
- package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
- package/skills/cratis-lens-browser-extension/LICENSE +2 -0
- package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
- package/skills/cratis-performance-review/LICENSE +2 -0
- package/skills/cratis-performance-review/SKILL.md +139 -0
- package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
- package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
- package/skills/cratis-security-review/LICENSE +2 -0
- package/skills/cratis-security-review/SKILL.md +141 -0
- package/skills/cratis-specification-by-example/LICENSE +2 -0
- package/skills/cratis-specification-by-example/SKILL.md +188 -0
- package/skills/cratis-specifications-csharp/LICENSE +2 -0
- package/skills/cratis-specifications-csharp/SKILL.md +230 -0
- package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
- package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
- package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
- package/skills/cratis-specifications-typescript/LICENSE +2 -0
- package/skills/cratis-specifications-typescript/SKILL.md +202 -0
- package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
- package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
- package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
- package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
- package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-chronicle-projection
|
|
3
|
+
description: Add Chronicle projection behavior to an existing read model - model-bound attributes first, the fluent IProjectionFor<T> builder when they cannot express the shape. Covers AutoMap, keys, children and nested types, counters, event-sequence selection, and the startup-crash traps. Use when populating a read model from events; do not use to create the read model and its query surface, and do not reach for a reducer before exhausting these options.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Chronicle projections
|
|
8
|
+
|
|
9
|
+
A projection consumes **events** and produces read-model state. It is pure: no
|
|
10
|
+
side effects, and it never reads another read model. Prefer model-bound
|
|
11
|
+
attributes on the read model; fall back to the fluent builder only when the
|
|
12
|
+
attributes cannot express the shape; reach for a reducer only after both fail.
|
|
13
|
+
|
|
14
|
+
## Verified product sources
|
|
15
|
+
|
|
16
|
+
This skill is verified against this exact source:
|
|
17
|
+
|
|
18
|
+
| Package | Version | Purpose |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `Cratis.Chronicle` | `16.45.2` | `Cratis.Chronicle.Projections`, `Cratis.Chronicle.Projections.ModelBound`, `Cratis.Chronicle.Keys`, `Cratis.Chronicle.EventSequences` |
|
|
21
|
+
|
|
22
|
+
Reverify product sources before claiming support for another version.
|
|
23
|
+
|
|
24
|
+
> `[ReadModel]` and `[Path]` are **Arc** types (`Cratis.Arc.Queries.ModelBound`),
|
|
25
|
+
> not Chronicle types. A Chronicle read model that exists only as a projection
|
|
26
|
+
> target needs no `[ReadModel]` at all — that attribute is what gives it an Arc
|
|
27
|
+
> query surface.
|
|
28
|
+
|
|
29
|
+
## Model-bound projections — the default
|
|
30
|
+
|
|
31
|
+
Put the projection on the read model with attributes. No separate class.
|
|
32
|
+
|
|
33
|
+
```csharp
|
|
34
|
+
using Cratis.Chronicle.Keys;
|
|
35
|
+
using Cratis.Chronicle.Projections.ModelBound;
|
|
36
|
+
|
|
37
|
+
[FromEvent<<EventName>>]
|
|
38
|
+
public record <ReadModelName>(
|
|
39
|
+
[property: Key] <IdType> Id,
|
|
40
|
+
<PropertyType> <PropertyName>);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Matching property names map automatically. **AutoMap is on by default** — both
|
|
44
|
+
for model-bound types and for the fluent builder — so never call `.AutoMap()`
|
|
45
|
+
except inside a scope where you disabled it.
|
|
46
|
+
|
|
47
|
+
`[Key]` is `Cratis.Chronicle.Keys.KeyAttribute`. It has no arguments and targets
|
|
48
|
+
a property or a record constructor parameter, never the class.
|
|
49
|
+
|
|
50
|
+
Read [references/model-bound-attributes.md](references/model-bound-attributes.md)
|
|
51
|
+
for the complete attribute list with exact arities, parameter names, and valid
|
|
52
|
+
targets. Two things there are worth knowing before you write anything:
|
|
53
|
+
|
|
54
|
+
- `[Nested]`, `[FromAll]`, and `[FromEvery]` are **non-generic**.
|
|
55
|
+
- `ConstantKey` on `[Count<T>]`, `[Increment<T>]`, `[Decrement<T>]`, and
|
|
56
|
+
`[FromEvent<T>]` is an **object-initializer property**, never a constructor
|
|
57
|
+
argument: `[Count<TEvent>(ConstantKey = "<key>")]`.
|
|
58
|
+
|
|
59
|
+
## Fluent projections — the fallback
|
|
60
|
+
|
|
61
|
+
Use `Cratis.Chronicle.Projections.IProjectionFor<TReadModel>` when the attributes
|
|
62
|
+
cannot express the shape — for example when two events feed one child collection
|
|
63
|
+
through different key properties.
|
|
64
|
+
|
|
65
|
+
```csharp
|
|
66
|
+
using Cratis.Chronicle.Projections;
|
|
67
|
+
|
|
68
|
+
public class <ReadModelName>Projection : IProjectionFor<<ReadModelName>>
|
|
69
|
+
{
|
|
70
|
+
public void Define(IProjectionBuilderFor<<ReadModelName>> builder) =>
|
|
71
|
+
builder
|
|
72
|
+
.From<<EventName>>(from => from.UsingKey(@event => @event.<KeyProperty>))
|
|
73
|
+
.RemovedWith<<RemovalEventName>>();
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`Define` returns `void`. **There is no `Identifier` or `ProjectionId` member on
|
|
78
|
+
the interface** — do not add one. Use `[Projection(id: "<id>")]` when the
|
|
79
|
+
identity must be explicit.
|
|
80
|
+
|
|
81
|
+
Read [references/fluent-builder.md](references/fluent-builder.md) for the real
|
|
82
|
+
member list. The trap that costs the most time: **`UsingKey`, `UsingParentKey`,
|
|
83
|
+
`UsingCompositeKey`, and `UsingConstantKey` are not on the projection builder.**
|
|
84
|
+
They live on the per-event builder that `From<TEvent>` hands to your callback.
|
|
85
|
+
|
|
86
|
+
## Joins are on events, never on read models
|
|
87
|
+
|
|
88
|
+
Both `[Join<T>]` and the fluent `Join<TEvent>` take an **event** type. Joining on
|
|
89
|
+
a read model type is not supported and is not what the API means. If a read model
|
|
90
|
+
needs a field that only another read model has, the model is missing an event —
|
|
91
|
+
fix the event model rather than cross-reading at runtime.
|
|
92
|
+
|
|
93
|
+
## Selecting the source
|
|
94
|
+
|
|
95
|
+
Class-level `[EventSequence("<name>")]`, `[EventLog]`, or `[EventStore("<name>")]`
|
|
96
|
+
choose where a model-bound projection reads from.
|
|
97
|
+
|
|
98
|
+
> **`[FromEventSequence]` no longer exists.** It was removed. The replacement on a
|
|
99
|
+
> model-bound read model is `[EventSequence("<name>")]` or `[EventLog]`. The
|
|
100
|
+
> fluent builder still has a `FromEventSequence(EventSequenceId)` **method** —
|
|
101
|
+
> that one is alive; do not confuse the two.
|
|
102
|
+
|
|
103
|
+
## Projections cannot filter by appended metadata
|
|
104
|
+
|
|
105
|
+
Chronicle correlates appended metadata in two different ways:
|
|
106
|
+
|
|
107
|
+
- **Projections** select their input through event types, joins, and event
|
|
108
|
+
sequence configuration — and nothing else. A projection's definition has **no
|
|
109
|
+
field for a filter at all**.
|
|
110
|
+
- **Reducers and reactors** can additionally filter by appended tag, event source
|
|
111
|
+
type, and event stream type, using `[FilterEventsByTag]`, `[EventSourceType]`,
|
|
112
|
+
and `[EventStreamType]`.
|
|
113
|
+
|
|
114
|
+
`[Tag]` and `[Tags]` on a projection or read model label the artifact; they never
|
|
115
|
+
filter. If you need metadata-based selection, pair the projection with a reducer
|
|
116
|
+
or a reactor rather than annotating the projection.
|
|
117
|
+
|
|
118
|
+
## Startup-crash traps
|
|
119
|
+
|
|
120
|
+
- **Duplicate class-level `[FromEvent<T>]`.** Declaring `[FromEvent<T>]` with no
|
|
121
|
+
`key:` on **both** a parent and a nested or child type for the **same** event
|
|
122
|
+
throws a duplicate-key exception at startup. Keep `[FromEvent<T>]` on the
|
|
123
|
+
nested type only, or switch the nested type to property-level `[SetFrom<T>]`.
|
|
124
|
+
- **Duplicate `[SetFromContext<T>]`.** Two properties with
|
|
125
|
+
`[SetFromContext<SameEvent>]` on one read model crash at startup. Merge them,
|
|
126
|
+
or use `[FromEvery]`.
|
|
127
|
+
- **Chaining after `AutoMap()`/`NoAutoMap()`.** Those two return the base builder
|
|
128
|
+
interface, so `.NotRewindable()`, `.Passive()`, `.ContainerName()`, and
|
|
129
|
+
`.FromEventSequence()` will not compile after them. Put them last.
|
|
130
|
+
|
|
131
|
+
## `[FromAll]` versus `[FromEvery]`
|
|
132
|
+
|
|
133
|
+
- `[FromAll]` subscribes to **every** event type in the system. It is for audit
|
|
134
|
+
and log models; pair it with `[NotRewindable]`. It targets a **property only**,
|
|
135
|
+
so it cannot go on a record positional parameter.
|
|
136
|
+
- `[FromEvery]` captures across the events the model **already** declares through
|
|
137
|
+
its `[FromEvent<T>]` attributes — for example to stamp `EventContext` data. It
|
|
138
|
+
subscribes to nothing new.
|
|
139
|
+
|
|
140
|
+
Their two optional arguments are in **opposite order**: `[FromAll(contextProperty,
|
|
141
|
+
property)]` and `[FromEvery(property, contextProperty)]`. Name them.
|
|
142
|
+
|
|
143
|
+
## After creating
|
|
144
|
+
|
|
145
|
+
Build. Fix every error before completing. Then drive the contributing events
|
|
146
|
+
through the read-model specification for this model and assert the projected
|
|
147
|
+
state — seed each contributing stream with its own event-source setup when the
|
|
148
|
+
projection spans streams.
|
|
149
|
+
|
|
150
|
+
## Verify
|
|
151
|
+
|
|
152
|
+
- Model-bound attributes were exhausted before the fluent builder, and the
|
|
153
|
+
fluent builder before any reducer.
|
|
154
|
+
- `.AutoMap()` is called only inside a `.NoAutoMap()` scope.
|
|
155
|
+
- Every join is on an event type.
|
|
156
|
+
- No `[FromEventSequence]` attribute appears anywhere.
|
|
157
|
+
- No projection carries a filter attribute expecting it to filter.
|
|
158
|
+
- `ConstantKey` is written as a property initializer.
|
|
159
|
+
- The same event does not carry class-level `[FromEvent<T>]` on both a parent and
|
|
160
|
+
a nested or child type.
|
|
161
|
+
- The project builds clean and the read-model specifications pass against the
|
|
162
|
+
verified package version.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# The fluent projection builder
|
|
2
|
+
|
|
3
|
+
Verified against `Cratis.Chronicle` `16.45.2`, namespace
|
|
4
|
+
`Cratis.Chronicle.Projections`.
|
|
5
|
+
|
|
6
|
+
## The interfaces
|
|
7
|
+
|
|
8
|
+
```csharp
|
|
9
|
+
public interface IProjection;
|
|
10
|
+
|
|
11
|
+
public interface IProjectionFor<TReadModel> : IProjection
|
|
12
|
+
where TReadModel : class
|
|
13
|
+
{
|
|
14
|
+
void Define(IProjectionBuilderFor<TReadModel> builder);
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`IProjection` is an empty marker. `Define` returns `void`. **The interface has
|
|
19
|
+
no `Identifier` or `ProjectionId` member** — declaring one adds a plain class
|
|
20
|
+
member that Chronicle does not read. Use
|
|
21
|
+
`[Projection(id: "<id>", eventSequence: "<name>")]` when the identity or source
|
|
22
|
+
sequence must be explicit.
|
|
23
|
+
|
|
24
|
+
## `IProjectionBuilderFor<TReadModel>` — its own four members
|
|
25
|
+
|
|
26
|
+
```csharp
|
|
27
|
+
IProjectionBuilderFor<TReadModel> FromEventSequence(EventSequenceId eventSequenceId);
|
|
28
|
+
IProjectionBuilderFor<TReadModel> ContainerName(string containerName);
|
|
29
|
+
IProjectionBuilderFor<TReadModel> NotRewindable();
|
|
30
|
+
IProjectionBuilderFor<TReadModel> Passive();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Inherited from `IProjectionBuilder<TReadModel, TBuilder>`
|
|
34
|
+
|
|
35
|
+
```csharp
|
|
36
|
+
IProjectionBuilder<TReadModel, TBuilder> AutoMap(); // returns the BASE interface
|
|
37
|
+
IProjectionBuilder<TReadModel, TBuilder> NoAutoMap(); // returns the BASE interface
|
|
38
|
+
|
|
39
|
+
TBuilder WithInitialValues(Func<TReadModel> initialValueProviderCallback);
|
|
40
|
+
TBuilder From<TEvent>(Action<IFromBuilder<TReadModel, TEvent>>? builderCallback = default);
|
|
41
|
+
TBuilder Join<TEvent>(Action<IJoinBuilder<TReadModel, TEvent>>? builderCallback = default);
|
|
42
|
+
TBuilder FromEvery(Action<IFromEveryBuilder<TReadModel>> builderCallback); // callback required
|
|
43
|
+
TBuilder FromAll(Action<IFromAllBuilder<TReadModel>> builderCallback); // callback required
|
|
44
|
+
TBuilder RemovedWith<TEvent>(Action<RemovedWithBuilder<TReadModel, TEvent>>? builderCallback = default);
|
|
45
|
+
TBuilder RemovedWithJoin<TEvent>(Action<RemovedWithJoinBuilder<TReadModel, TEvent>>? builderCallback = default);
|
|
46
|
+
TBuilder Children<TChildModel>(
|
|
47
|
+
Expression<Func<TReadModel, IEnumerable<TChildModel>>> targetProperty,
|
|
48
|
+
Action<IChildrenBuilder<TReadModel, TChildModel>> builderCallback);
|
|
49
|
+
TBuilder Nested<TNestedModel>(
|
|
50
|
+
Expression<Func<TReadModel, TNestedModel?>> targetProperty,
|
|
51
|
+
Action<INestedBuilder<TReadModel, TNestedModel>> builderCallback);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**`AutoMap()` and `NoAutoMap()` return the base interface, not `TBuilder`.**
|
|
55
|
+
Chaining `.NotRewindable()`, `.Passive()`, `.ContainerName()`, or
|
|
56
|
+
`.FromEventSequence()` after either of them does not compile. Put them last.
|
|
57
|
+
|
|
58
|
+
`RemovedWith` and `RemovedWithJoin` hand you the **concrete**
|
|
59
|
+
`RemovedWithBuilder<,>` and `RemovedWithJoinBuilder<,>` classes, not interfaces.
|
|
60
|
+
|
|
61
|
+
## Keys live on the per-event builder
|
|
62
|
+
|
|
63
|
+
`From<TEvent>` hands the callback an `IFromBuilder<TReadModel, TEvent>`, which
|
|
64
|
+
derives from `IReadModelPropertiesBuilder<TReadModel, TEvent, TBuilder>`. That
|
|
65
|
+
is where every key method lives:
|
|
66
|
+
|
|
67
|
+
```csharp
|
|
68
|
+
TBuilder UsingKey<TProperty>(Expression<Func<TEvent, TProperty>> keyAccessor);
|
|
69
|
+
TBuilder UsingKeyFromContext<TProperty>(Expression<Func<EventContext, TProperty>> keyAccessor);
|
|
70
|
+
TBuilder UsingParentKey<TProperty>(Expression<Func<TEvent, TProperty>> keyAccessor);
|
|
71
|
+
TBuilder UsingParentKeyFromContext<TProperty>(Expression<Func<EventContext, TProperty>> keyAccessor);
|
|
72
|
+
TBuilder UsingCompositeKey<TKeyType>(Action<ICompositeKeyBuilder<TKeyType, TEvent>> builderCallback);
|
|
73
|
+
TBuilder UsingParentCompositeKey<TKeyType>(Action<ICompositeKeyBuilder<TKeyType, TEvent>> builderCallback);
|
|
74
|
+
TBuilder UsingConstantKey(string value);
|
|
75
|
+
TBuilder UsingConstantParentKey(string value);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The same interface carries the value operations:
|
|
79
|
+
|
|
80
|
+
```csharp
|
|
81
|
+
TBuilder Increment<TProperty>(...);
|
|
82
|
+
TBuilder Decrement<TProperty>(...);
|
|
83
|
+
TBuilder Add<TProperty>(...);
|
|
84
|
+
TBuilder Subtract<TProperty>(...);
|
|
85
|
+
TBuilder Count<TProperty>(...);
|
|
86
|
+
TBuilder AddChild<TChildModel>(...);
|
|
87
|
+
ISetBuilder<...> Set(PropertyPath propertyPath);
|
|
88
|
+
ISetBuilder<...> Set<TProperty>(Expression<Func<TReadModel, TProperty>> propertyExpression);
|
|
89
|
+
TBuilder SetThisValue();
|
|
90
|
+
TBuilder Clear<TProperty>(...); // default implementation throws unless the builder overrides it
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## `Set(...).To(...)`
|
|
94
|
+
|
|
95
|
+
```csharp
|
|
96
|
+
// ISetBuilder<TReadModel, TEvent, TParentBuilder>
|
|
97
|
+
TParentBuilder To(PropertyPath propertyPath);
|
|
98
|
+
TParentBuilder ToEventSourceId();
|
|
99
|
+
|
|
100
|
+
// ISetBuilder<TReadModel, TEvent, TProperty, TParentBuilder>
|
|
101
|
+
TParentBuilder ToValue(TProperty value);
|
|
102
|
+
TParentBuilder To(Expression<Func<TEvent, TProperty>> eventPropertyAccessor);
|
|
103
|
+
TParentBuilder ToEventContextProperty(Expression<Func<EventContext, object>> eventContextPropertyAccessor);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## When the fluent form is the only option
|
|
107
|
+
|
|
108
|
+
The clearest case is a child collection fed by events that carry **different key
|
|
109
|
+
properties**, which the single-key model-bound `[ChildrenFrom<T>]` cannot state:
|
|
110
|
+
|
|
111
|
+
```csharp
|
|
112
|
+
public void Define(IProjectionBuilderFor<<ReadModelName>> builder) =>
|
|
113
|
+
builder
|
|
114
|
+
.From<<AssignedEvent>>(from => from.UsingKey(@event => @event.<KeyA>))
|
|
115
|
+
.From<<UpdatedEvent>>(from => from.UsingKey(@event => @event.<KeyB>));
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Also reach for it for joins across several events, composite keys, initial
|
|
119
|
+
values, and conditional setters the attributes do not express.
|
|
120
|
+
|
|
121
|
+
## AutoMap default
|
|
122
|
+
|
|
123
|
+
`ProjectionBuilderFor` is constructed with AutoMap enabled, and the model-bound
|
|
124
|
+
builder sets it to enabled unless `[NoAutoMap]` is present on the read model.
|
|
125
|
+
Children and nested builders are created with an inherit setting, so they follow
|
|
126
|
+
the enclosing scope.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Model-bound projection attributes
|
|
2
|
+
|
|
3
|
+
Verified against `Cratis.Chronicle` `16.45.2`.
|
|
4
|
+
|
|
5
|
+
Unless noted, every attribute below lives in
|
|
6
|
+
`Cratis.Chronicle.Projections.ModelBound`. Generic arity is stated exactly:
|
|
7
|
+
`<T>` means one generic parameter naming the event type; "non-generic" means the
|
|
8
|
+
attribute takes no generic parameter at all.
|
|
9
|
+
|
|
10
|
+
## Namespaces that are not `Projections.ModelBound`
|
|
11
|
+
|
|
12
|
+
| Attribute | Namespace |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `[Key]` | `Cratis.Chronicle.Keys` |
|
|
15
|
+
| `[Index]` | `Cratis.Chronicle.ReadModels` |
|
|
16
|
+
| `[Passive]` | `Cratis.Chronicle.ReadModels` |
|
|
17
|
+
| `[NoAutoMap]` | `Cratis.Chronicle.Projections` |
|
|
18
|
+
| `[Projection]` | `Cratis.Chronicle.Projections` |
|
|
19
|
+
| `[EventSequence]`, `[EventLog]` | `Cratis.Chronicle.EventSequences` |
|
|
20
|
+
| `[EventStore]` | `Cratis.Chronicle.Events` |
|
|
21
|
+
|
|
22
|
+
`[ReadModel]` and `[Path]` are **Arc** types in `Cratis.Arc.Queries.ModelBound`.
|
|
23
|
+
|
|
24
|
+
## Source and shape
|
|
25
|
+
|
|
26
|
+
| Attribute | Arity | Parameters | Targets |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| `[FromEvent<T>]` | `<T>` | `(string? key = default, string? parentKey = default)`, plus `ConstantKey { get; init; }` | class, struct; repeatable |
|
|
29
|
+
| `[RemovedWith<T>]` | `<T>` | `(string? key = default, string? parentKey = default)` | class, struct, property, parameter; repeatable |
|
|
30
|
+
| `[RemovedWithJoin<T>]` | `<T>` | `(string? key = default)` | property, parameter; repeatable |
|
|
31
|
+
| `[Nested]` | non-generic | none | property, parameter; single |
|
|
32
|
+
| `[FromAll]` | non-generic | `(string? contextProperty = default, string? property = default)` | **property only**; single |
|
|
33
|
+
| `[FromEvery]` | non-generic | `(string? property = default, string? contextProperty = default)` | property, parameter; single |
|
|
34
|
+
| `[NoAutoMap]` | non-generic | none | class, struct, property, parameter; inherited |
|
|
35
|
+
| `[NotRewindable]` | non-generic | none | class, struct; single |
|
|
36
|
+
| `[Passive]` | non-generic | none | class, struct; single |
|
|
37
|
+
| `[Key]` | non-generic | none | property, parameter |
|
|
38
|
+
| `[Index]` | non-generic | none | property, parameter |
|
|
39
|
+
|
|
40
|
+
Note that `[FromAll]` and `[FromEvery]` take their two optional arguments in
|
|
41
|
+
**opposite order**. Always pass them by name.
|
|
42
|
+
|
|
43
|
+
## Setting values
|
|
44
|
+
|
|
45
|
+
| Attribute | Arity | Parameters | Targets |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `[SetFrom<T>]` | `<T>` | `(string? eventPropertyName = default)` | property, parameter; repeatable |
|
|
48
|
+
| `[SetValue<T>]` | `<T>` | `(object? value)` — **required**; pass `null` to clear | property, parameter; repeatable |
|
|
49
|
+
| `[SetFromContext<T>]` | `<T>` | `(string? contextPropertyName = default)` | property, parameter; repeatable |
|
|
50
|
+
| `[AddFrom<T>]` | `<T>` | `(string? eventPropertyName = default)` | property, parameter; repeatable |
|
|
51
|
+
| `[SubtractFrom<T>]` | `<T>` | `(string? eventPropertyName = default)` | property, parameter; repeatable |
|
|
52
|
+
| `[ClearWith<T>]` | `<T>` | **none** | class, property, parameter; repeatable |
|
|
53
|
+
|
|
54
|
+
## Counters
|
|
55
|
+
|
|
56
|
+
| Attribute | Arity | Parameters | Targets |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| `[Count<T>]` | `<T>` | none; `ConstantKey { get; init; }` | property, parameter; repeatable |
|
|
59
|
+
| `[Increment<T>]` | `<T>` | none; `ConstantKey { get; init; }` | property, parameter; repeatable |
|
|
60
|
+
| `[Decrement<T>]` | `<T>` | none; `ConstantKey { get; init; }` | property, parameter; repeatable |
|
|
61
|
+
|
|
62
|
+
`ConstantKey` is an **object-initializer property**, never a constructor
|
|
63
|
+
argument:
|
|
64
|
+
|
|
65
|
+
```csharp
|
|
66
|
+
[Count<<EventName>>(ConstantKey = "<key>")]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Setting it routes every matching event into a single aggregating document at
|
|
70
|
+
that key. That is a different mechanism from the fluent builder's
|
|
71
|
+
`UsingConstantKey("<key>")`, even though the effect is similar.
|
|
72
|
+
|
|
73
|
+
## Relationships
|
|
74
|
+
|
|
75
|
+
| Attribute | Arity | Parameters | Targets |
|
|
76
|
+
| --- | --- | --- | --- |
|
|
77
|
+
| `[ChildrenFrom<T>]` | `<T>` | `(string? key = default, string? identifiedBy = default, string? parentKey = default)` — three parameters, `identifiedBy` in the middle | property, parameter; repeatable |
|
|
78
|
+
| `[Join<T>]` | `<T>` | `(string? on = default, string? eventPropertyName = default)` | property, parameter; repeatable |
|
|
79
|
+
|
|
80
|
+
`[Join<T>]` joins on an **event**. Joining on a read model is not supported.
|
|
81
|
+
|
|
82
|
+
## Source selection
|
|
83
|
+
|
|
84
|
+
| Attribute | Parameters | Targets |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `[EventSequence("<name>")]` | `(string sequence)`; exposes `Sequence` | class |
|
|
87
|
+
| `[EventLog]` | none; equivalent to `[EventSequence]` for the event log | class |
|
|
88
|
+
| `[EventStore("<name>")]` | `(string eventStore)` | class, assembly; single |
|
|
89
|
+
|
|
90
|
+
**`[FromEventSequence]` was removed and does not exist.** The fluent builder's
|
|
91
|
+
`FromEventSequence(EventSequenceId)` method is unrelated and still present.
|
|
92
|
+
|
|
93
|
+
## Labels, not filters
|
|
94
|
+
|
|
95
|
+
| Attribute | Namespace | Parameters | Effect on a projection |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| `[Tag]` | `Cratis.Chronicle` | `(params string[] tags)`; repeatable | label only |
|
|
98
|
+
| `[Tags]` | `Cratis.Chronicle` | `(params string[] tags)`; repeatable | label only |
|
|
99
|
+
|
|
100
|
+
A projection's definition carries `Tags` but has **no filter field at all**.
|
|
101
|
+
`[FilterEventsByTag]`, `[EventSourceType]`, and `[EventStreamType]` only filter
|
|
102
|
+
for reducers and reactors.
|
|
103
|
+
|
|
104
|
+
## What makes a type a model-bound projection
|
|
105
|
+
|
|
106
|
+
Chronicle treats a type as a model-bound projection when the class, its primary
|
|
107
|
+
constructor parameters, or its public instance properties carry an
|
|
108
|
+
`[EventSequence]` attribute or any projection annotation — **except
|
|
109
|
+
`[Passive]`**, which is deliberately excluded. `[Passive]` on its own therefore
|
|
110
|
+
does not make a read model a projection; it changes how an existing projection
|
|
111
|
+
is registered.
|
|
112
|
+
|
|
113
|
+
## AutoMap
|
|
114
|
+
|
|
115
|
+
AutoMap is enabled by default for both model-bound types and the fluent builder.
|
|
116
|
+
`[NoAutoMap]` disables it, is inherited, and applies at class, struct, property,
|
|
117
|
+
or parameter level. Child and nested builders inherit the enclosing setting.
|
|
118
|
+
Call `.AutoMap()` only to re-enable inside a scope you disabled.
|