@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,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-chronicle-cli-operations
|
|
3
|
+
description: Inspect and operate a running Chronicle server with the cratis CLI - contexts and connection resolution, machine-readable output, failed partitions, observers, event sequences, read models, projections and jobs, and the authorization discipline for replay, retry, quarantine clearing and other mutations. Use when the question is about the state of a live store rather than source code. Do not use to diagnose a defect that reproduces locally in source.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Operate a running Chronicle with the `cratis` CLI
|
|
8
|
+
|
|
9
|
+
Source code says what *should* happen. When the question is what *is* happening
|
|
10
|
+
in a live store — a projection that will not move, an observer that stopped, an
|
|
11
|
+
event you are not sure was appended — read the server instead of the code.
|
|
12
|
+
|
|
13
|
+
## Verified product sources
|
|
14
|
+
|
|
15
|
+
This skill is verified against these exact sources:
|
|
16
|
+
|
|
17
|
+
| Package | Version | Purpose |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `Cratis.Cli` | `2.4.0` | the `cratis` command, its contexts, output formats, and command tree |
|
|
20
|
+
| `Cratis.Chronicle` | `16.45.2` | the observer, partition, sequence, and job concepts the CLI reads |
|
|
21
|
+
|
|
22
|
+
The CLI ships a **versioned machine-readable catalog of its own surface**, and
|
|
23
|
+
that catalog is the authority for exact command names on the installed version:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cratis llm-context # every command, option and argument as JSON
|
|
27
|
+
cratis llm-context --schema # the JSON Schema for that document
|
|
28
|
+
cratis <group> --help # the same, one group at a time
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Do not guess a command name from this file. This skill covers *when to reach for
|
|
32
|
+
the CLI and how to read what comes back*; the catalog covers *what to type*.
|
|
33
|
+
|
|
34
|
+
## Install and set up
|
|
35
|
+
|
|
36
|
+
`cratis` is a .NET tool published as `Cratis.Cli` with the command name
|
|
37
|
+
`cratis`. Install it with `dotnet tool install -g Cratis.Cli`, or through
|
|
38
|
+
Homebrew, or from a release binary. Follow with `cratis completions install`.
|
|
39
|
+
|
|
40
|
+
Once per project, so every agent working it can reach the store:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cratis init # detects the AI tools in use and writes project context
|
|
44
|
+
cratis init --refresh # re-capture after upgrading the CLI
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Two things worth knowing before running it:
|
|
48
|
+
|
|
49
|
+
- The embedded catalog is a **snapshot**, not a live lookup. After a CLI upgrade
|
|
50
|
+
it still describes the older surface; `cratis init` reports the mismatch and
|
|
51
|
+
`--refresh` fixes it.
|
|
52
|
+
- If the repository's instruction file is supplied by a shared package or a
|
|
53
|
+
generated adapter, pass `--no-context`. Keep project-specific Chronicle
|
|
54
|
+
connection guidance in a repository-owned project context file; never patch
|
|
55
|
+
packaged or generated bytes.
|
|
56
|
+
|
|
57
|
+
## Reach the right server
|
|
58
|
+
|
|
59
|
+
The connection string resolves in this exact order:
|
|
60
|
+
|
|
61
|
+
1. `--server <CONNECTION_STRING>`
|
|
62
|
+
2. the `CHRONICLE_CONNECTION_STRING` environment variable
|
|
63
|
+
3. the active context's server in `~/.cratis/config.json`
|
|
64
|
+
4. `chronicle://localhost:35000`
|
|
65
|
+
|
|
66
|
+
Credentials are composed separately, and only when the resolved string carries
|
|
67
|
+
no embedded `user:pass@` and no `apiKey=`: a cached access token first, then the
|
|
68
|
+
context's client id and secret.
|
|
69
|
+
|
|
70
|
+
Prefer a named context over repeating a connection string:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cratis context create <name> --server chronicle://<host>:35000
|
|
74
|
+
cratis context set <name>
|
|
75
|
+
cratis context show
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
A context also carries `--event-store` and `--namespace` defaults; both can be
|
|
79
|
+
overridden per command with `-e` and `-n`. The event store defaults to
|
|
80
|
+
`default` and the namespace to `Default`.
|
|
81
|
+
|
|
82
|
+
**Be deliberate about which store you are pointed at.** The same commands read
|
|
83
|
+
production and a local container, and several of them mutate. `cratis context
|
|
84
|
+
show` before anything that writes; `--debug` prints the resolved config path,
|
|
85
|
+
context, redacted connection string, and RPC timing to stderr.
|
|
86
|
+
|
|
87
|
+
## Choose the output format
|
|
88
|
+
|
|
89
|
+
There is one output flag, `-o` / `--output`, with values `table`, `plain`,
|
|
90
|
+
`json`, `json-compact`, and `auto`. **There is no `--json` flag.**
|
|
91
|
+
|
|
92
|
+
- `auto` (the default) resolves to `json-compact` when an AI-agent environment
|
|
93
|
+
is detected, `plain` when `NO_COLOR` is set, `json` when stdout is redirected,
|
|
94
|
+
and `table` otherwise.
|
|
95
|
+
- `-q` / `--quiet` prints identifiers only, one per line — what you want when
|
|
96
|
+
piping one command into another. Combined with a JSON format it yields a JSON
|
|
97
|
+
array of identifiers.
|
|
98
|
+
- Prefer `plain` for large listings; JSON repeats every field name on every row.
|
|
99
|
+
Prefer `json`/`json-compact` for `show`/`get` commands where you need the
|
|
100
|
+
nested structure.
|
|
101
|
+
|
|
102
|
+
Exit codes are stable and worth branching on: `0` success, `1` not found, `2`
|
|
103
|
+
connection error, `3` server error, `4` authentication error, `5` validation
|
|
104
|
+
error.
|
|
105
|
+
|
|
106
|
+
## Where to look
|
|
107
|
+
|
|
108
|
+
| Question | Where to look |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| Is the server healthy at all? | `cratis chronicle diagnose` (exit code 3 when unhealthy) |
|
|
111
|
+
| Why has this read model stopped updating? | `chronicle failed-partitions list --observer <id>` — the error is on the partition, not in the log |
|
|
112
|
+
| An observer is quarantined — why? | `chronicle failed-partitions show <observer> <partition> --detailed`, with attempt history |
|
|
113
|
+
| Did this event actually get appended? | `chronicle events get --event-type <type> --event-source-id <id>`, or `chronicle events tail` |
|
|
114
|
+
| What is this event's shape in the store? | `chronicle event-types list`, then `chronicle event-types show <type>` |
|
|
115
|
+
| Is this projection registered? | `chronicle projections list`, then `chronicle projections show <id>` |
|
|
116
|
+
| What does the read model actually hold? | `chronicle read-models get <read-model> <key>`, or `read-models instances` |
|
|
117
|
+
| Is a replay or migration still running? | `chronicle jobs list`, then `chronicle jobs get <id>` |
|
|
118
|
+
| What does the server itself recommend? | `chronicle recommendations list` |
|
|
119
|
+
|
|
120
|
+
Inspection commands are read-only: `diagnose`, `version`, `event-stores list`,
|
|
121
|
+
`namespaces list`, `event-types list|show`, `events get|tail`, `observers
|
|
122
|
+
list|show`, `failed-partitions list|show`, `projections list|show`,
|
|
123
|
+
`read-models list|instances|get|snapshots|occurrences`, `jobs list|get`,
|
|
124
|
+
`recommendations list`, `subscriptions list`, `identities list`, `users list`,
|
|
125
|
+
`applications list`, and `auth status`.
|
|
126
|
+
|
|
127
|
+
## Read what comes back
|
|
128
|
+
|
|
129
|
+
- **A failed partition does not retry itself.** It stays failed until something
|
|
130
|
+
clears it, so a stale value is permanent rather than slow. That distinction is
|
|
131
|
+
the whole diagnosis: "not arrived yet" and "will never arrive" look identical
|
|
132
|
+
from the outside.
|
|
133
|
+
- **Fix the cause before replaying.** Replaying into an unfixed handler fails the
|
|
134
|
+
same way and buries the original error under a newer one.
|
|
135
|
+
- **A quarantined observer does not resume by itself.** Clearing the quarantine
|
|
136
|
+
is an explicit operation, and it is the last step, not the first.
|
|
137
|
+
|
|
138
|
+
## Before you change anything
|
|
139
|
+
|
|
140
|
+
Default to read-only inspection. A request to diagnose a live store does not
|
|
141
|
+
authorize replay, retry, removal, quarantine clearing, or another mutation.
|
|
142
|
+
|
|
143
|
+
These commands mutate the running store:
|
|
144
|
+
|
|
145
|
+
- `chronicle observers replay`, `replay-partition`, `retry-partition`,
|
|
146
|
+
`clear-quarantine`
|
|
147
|
+
- `chronicle jobs stop`, `chronicle jobs resume`
|
|
148
|
+
- `chronicle recommendations perform`, `chronicle recommendations ignore`
|
|
149
|
+
- `chronicle subscriptions add`, `chronicle subscriptions remove`
|
|
150
|
+
- `chronicle users add`, `chronicle users remove`
|
|
151
|
+
- `chronicle applications add`, `chronicle applications remove`
|
|
152
|
+
|
|
153
|
+
Before any of them:
|
|
154
|
+
|
|
155
|
+
1. Name the exact server context, event store, namespace, observer, partition,
|
|
156
|
+
event sequence, or job that will change.
|
|
157
|
+
2. Capture its exact pre-state and the failure evidence that justifies the
|
|
158
|
+
operation.
|
|
159
|
+
3. Prepare the repository-approved inverse or safe compensation when one exists;
|
|
160
|
+
stop when the effect cannot be safely reversed unless the owner separately
|
|
161
|
+
authorizes that irreversible operation.
|
|
162
|
+
4. Show the dry run and obtain explicit authorization for that exact target and
|
|
163
|
+
action.
|
|
164
|
+
5. Re-read the target immediately before mutation and stop on drift.
|
|
165
|
+
|
|
166
|
+
### Two confirmation traps
|
|
167
|
+
|
|
168
|
+
**Confirmation prompts do not fire when stdout is not a terminal.** The CLI
|
|
169
|
+
treats a non-interactive session as an implicit yes, so a destructive command
|
|
170
|
+
run from a script, a pipeline, or an agent harness proceeds without `-y` and
|
|
171
|
+
without asking. Never rely on the prompt as the safety mechanism — the
|
|
172
|
+
authorization above is the safety mechanism.
|
|
173
|
+
|
|
174
|
+
**Not every mutating command prompts, even interactively.** `jobs stop` and
|
|
175
|
+
`jobs resume` describe themselves as prompting for confirmation, but no prompt
|
|
176
|
+
exists in the implementation. `users add`, `applications add`, and
|
|
177
|
+
`subscriptions add` do not prompt either.
|
|
178
|
+
|
|
179
|
+
Reaching for `--yes` to silence a prompt you have not read is how the wrong
|
|
180
|
+
store gets replayed. A failed partition you have not yet explained is not a
|
|
181
|
+
thing to clear: read it, fix the handler, then replay. Clearing it first
|
|
182
|
+
destroys the evidence and the same failure returns on the next event.
|
|
183
|
+
|
|
184
|
+
## What the CLI cannot do
|
|
185
|
+
|
|
186
|
+
There is no `delete`, `purge`, `redact`, or `revise` command. Event redaction
|
|
187
|
+
and revision are browser Workbench operations. Treat a request to "remove an
|
|
188
|
+
event with the CLI" as a request that needs a different tool and a separate
|
|
189
|
+
authorization.
|
|
190
|
+
|
|
191
|
+
## Related
|
|
192
|
+
|
|
193
|
+
- The terminal Workbench (`cratis chronicle workbench`) presents the same
|
|
194
|
+
read-only data as a navigable full-screen view; reach for it when you are
|
|
195
|
+
exploring rather than answering one question.
|
|
196
|
+
- The browser Workbench owns redaction, revision, and event-type authoring.
|
|
197
|
+
- Source-level diagnosis belongs in the application's own diagnostic workflow;
|
|
198
|
+
start there when the symptom reproduces locally, and start here when it is
|
|
199
|
+
only visible on a running server.
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-chronicle-client-dotnet
|
|
3
|
+
description: Talk to a Chronicle server from a standalone .NET application with the Cratis.Chronicle client - connection strings, ChronicleClient construction outside any host, AddCratisChronicle for a worker or ASP.NET host, [EventType] records, IEventSequence.Append, reactors and reducers found by assembly scanning, the connection lifecycle and registration wait, and the client/server compatibility check. Use when a console, worker, or service app connects to Chronicle directly. Do not use for Arc applications, the Chronicle kernel, or the Kotlin, TypeScript, and Elixir clients.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# The Chronicle client for .NET
|
|
8
|
+
|
|
9
|
+
`Cratis.Chronicle` is a **standalone client SDK**. A console app, a worker, or a
|
|
10
|
+
service constructs a client, asks it for an event store, and appends or observes.
|
|
11
|
+
There is no host requirement and no application architecture imposed.
|
|
12
|
+
|
|
13
|
+
> **This is not the Arc integration.** In an Arc application, Chronicle is wired
|
|
14
|
+
> in for you and commands return events that Arc appends. Everything below is the
|
|
15
|
+
> layer underneath: what an application that is *not* Arc has to do itself.
|
|
16
|
+
|
|
17
|
+
## Verified product sources
|
|
18
|
+
|
|
19
|
+
This skill is verified against the `Cratis/Chronicle` repository at tag
|
|
20
|
+
`v17.0.1`, whose client sources are byte-identical to `16.45.2` for every public
|
|
21
|
+
type cited here except the compatibility check noted below.
|
|
22
|
+
|
|
23
|
+
| Package | Line | Purpose |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `Cratis.Chronicle` | `17.x` | `ChronicleClient`, `IEventStore`, `[EventType]`, observers |
|
|
26
|
+
| `Cratis.Chronicle.Connections` | `17.x` | `ChronicleConnectionString`, connection lifecycle |
|
|
27
|
+
| `Cratis.Chronicle.AspNetCore` | `17.x` | ASP.NET Core wiring, header/subdomain namespace resolvers |
|
|
28
|
+
| `Cratis.Chronicle.Testing` | `17.x` | in-process scenarios |
|
|
29
|
+
| `Cratis.Chronicle.CodeAnalysis` | `17.x` | the analyzers, a **separate opt-in** package |
|
|
30
|
+
|
|
31
|
+
Take the exact version from nuget.org. The version in source is a `1.0.0`
|
|
32
|
+
placeholder injected at pack time (`Source/Directory.Build.props`), so the
|
|
33
|
+
repository never carries the real number. Reverify before claiming support for a
|
|
34
|
+
version you have not checked.
|
|
35
|
+
|
|
36
|
+
## Which package
|
|
37
|
+
|
|
38
|
+
```shell
|
|
39
|
+
dotnet add package Cratis.Chronicle # console / worker service
|
|
40
|
+
dotnet add package Cratis.Chronicle.AspNetCore # ASP.NET Core
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`Cratis.Chronicle.CodeAnalysis` is **not** pulled in by `Cratis.Chronicle`. Add
|
|
44
|
+
it deliberately — it is what warns about the mistakes an event model makes
|
|
45
|
+
silently, such as a nullable event property.
|
|
46
|
+
|
|
47
|
+
## Connecting
|
|
48
|
+
|
|
49
|
+
### The smallest real thing
|
|
50
|
+
|
|
51
|
+
```csharp
|
|
52
|
+
using Cratis.Chronicle;
|
|
53
|
+
using Cratis.Chronicle.Connections;
|
|
54
|
+
|
|
55
|
+
using var client = new ChronicleClient(ChronicleConnectionString.Development);
|
|
56
|
+
var eventStore = await client.GetEventStore("<EventStoreName>");
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
That is the compiled documentation snippet verbatim
|
|
60
|
+
(`Documentation/client-snippets/get-started/console/connect.md`). Those snippet
|
|
61
|
+
files are compiled in CI against the real client projects, which makes them the
|
|
62
|
+
safest source of .NET Chronicle example code in existence — prefer them over
|
|
63
|
+
prose documentation.
|
|
64
|
+
|
|
65
|
+
`IChronicleClient` — `Source/Clients/DotNET/IChronicleClient.cs:11`:
|
|
66
|
+
|
|
67
|
+
| Member | Line |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `ChronicleOptions Options { get; }` | `:16` |
|
|
70
|
+
| `ICausationManager CausationManager { get; }` | `:21` |
|
|
71
|
+
| `Task<IEventStore> GetEventStore(EventStoreName name, EventStoreNamespaceName? @namespace = default)` | `:32` |
|
|
72
|
+
| `Task<IEnumerable<EventStoreName>> GetEventStores(CancellationToken cancellationToken = default)` | `:39` |
|
|
73
|
+
| `void EvictEventStores()` | `:53` |
|
|
74
|
+
|
|
75
|
+
**There is no `IChronicleClient.Connect`.** Connecting happens inside
|
|
76
|
+
`GetEventStore`, which discovers artifacts and then connects
|
|
77
|
+
(`Source/Clients/DotNET/ChronicleClient.cs:403-409`). When you do need the
|
|
78
|
+
connection explicitly — in a worker's `ExecuteAsync`, for instance — it is
|
|
79
|
+
`eventStore.Connection.Connect()`.
|
|
80
|
+
|
|
81
|
+
`ChronicleClient` is `IDisposable`
|
|
82
|
+
(`Source/Clients/DotNET/ChronicleClient.cs:30`) with five public constructors
|
|
83
|
+
(`:76`, `:85`, `:94`, `:110`, `:183`). The one that matters for a real
|
|
84
|
+
application is `:110`:
|
|
85
|
+
|
|
86
|
+
```csharp
|
|
87
|
+
public ChronicleClient(
|
|
88
|
+
ChronicleOptions options,
|
|
89
|
+
IClientArtifactsProvider? artifactsProvider = null,
|
|
90
|
+
IServiceProvider? serviceProvider = null,
|
|
91
|
+
IIdentityProvider? identityProvider = null,
|
|
92
|
+
ICorrelationIdAccessor? correlationIdAccessor = null,
|
|
93
|
+
IEventStoreNamespaceResolver? namespaceResolver = null,
|
|
94
|
+
ILoggerFactory? loggerFactory = null,
|
|
95
|
+
INamingPolicy? namingPolicy = null)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The canonical standalone sample uses it — `Samples/SimpleConsole/Program.cs:28-33`:
|
|
99
|
+
|
|
100
|
+
```csharp
|
|
101
|
+
var options = ChronicleOptions.FromConnectionString("chronicle://chronicle-dev-client:chronicle-dev-secret@localhost:35000");
|
|
102
|
+
options.DefaultSinkTypeId = sinkType;
|
|
103
|
+
|
|
104
|
+
using var client = new ChronicleClient(options, loggerFactory: loggerFactory);
|
|
105
|
+
var store = await client.GetEventStore("TestStoreCS");
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Options
|
|
109
|
+
|
|
110
|
+
`ChronicleOptions` — `Source/Clients/DotNET/ChronicleOptions.cs`. Statics:
|
|
111
|
+
`FromConnectionString(string)` (`:191`), `FromConnectionString(ChronicleConnectionString)`
|
|
112
|
+
(`:198`), `FromDevelopmentConnectionString()` (`:214`). The parameterless
|
|
113
|
+
constructor (`:40`) is the development connection string.
|
|
114
|
+
|
|
115
|
+
Options worth knowing: `AutoDiscoverAndRegister` (default `true`, `:90`),
|
|
116
|
+
`ConnectTimeout` (default 5 seconds, `:95`), `SkipKeepAlive` (`:127` — turn the
|
|
117
|
+
watchdog off for a short-lived client), `DefaultSinkTypeId` (default
|
|
118
|
+
`WellKnownSinkTypes.MongoDB`), `Tls`, `Authentication`, `RegistrationRetry`.
|
|
119
|
+
|
|
120
|
+
### Connection strings, not URLs
|
|
121
|
+
|
|
122
|
+
**There is no `ChronicleUrl` type and no microservice id.** The identity of a
|
|
123
|
+
connection is a connection string plus an event store name and optionally a
|
|
124
|
+
namespace. `ChronicleConnectionString` lives in `Cratis.Chronicle.Connections`
|
|
125
|
+
(`Source/Clients/Connections/ChronicleConnectionString.cs:33`):
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
chronicle://host[:port]/?opts
|
|
129
|
+
chronicle://user:pass@host[:port]/?opts
|
|
130
|
+
chronicle+srv://host/?opts
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Default port `35000`. `Default` is `chronicle://localhost:35000` (`:48`);
|
|
134
|
+
`Development` adds the `chronicle-dev-client` / `chronicle-dev-secret`
|
|
135
|
+
credentials (`:57`). Query options: `apiKey`, `auth`, `skipTlsValidation`,
|
|
136
|
+
`loadBalancer`, `srvNameServer`, `certificatePath`, `certificatePassword`
|
|
137
|
+
(`ChronicleConnectionStringBuilder.cs:27-36`). There is an implicit conversion
|
|
138
|
+
from `string` (`:158`) and a `Redacted` form for logging (`:152`).
|
|
139
|
+
|
|
140
|
+
> **Certificate validation is skipped by default, and that is deliberate.** The
|
|
141
|
+
> Chronicle server always serves TLS, generating a self-signed certificate on
|
|
142
|
+
> every start when none is configured, so a development pair connects with no
|
|
143
|
+
> setup — the rationale is written out at `ChronicleClient.cs:141-146`. **A
|
|
144
|
+
> production connection string must set `skipTlsValidation=false`** (or
|
|
145
|
+
> `Tls.SkipCertificateValidation = false`) against a server whose certificate is
|
|
146
|
+
> actually verifiable.
|
|
147
|
+
|
|
148
|
+
### In a host
|
|
149
|
+
|
|
150
|
+
```csharp
|
|
151
|
+
var builder = Host.CreateApplicationBuilder(args);
|
|
152
|
+
builder.AddCratisChronicle(options => options.EventStore = "<EventStoreName>");
|
|
153
|
+
builder.Services.AddHostedService<<WorkerName>>();
|
|
154
|
+
await builder.Build().RunAsync();
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`AddCratisChronicle` is an extension on **`IHostApplicationBuilder`**
|
|
158
|
+
(`Source/Clients/DotNET/ChronicleHostApplicationBuilderExtensions.cs:28`) and binds
|
|
159
|
+
the `Cratis:Chronicle` configuration section by default (`:25`, `:37`), with
|
|
160
|
+
`ValidateDataAnnotations().ValidateOnStart()`. The bound type is
|
|
161
|
+
`ChronicleClientOptions : ChronicleOptions`, which adds a `[Required] EventStore`
|
|
162
|
+
and an optional `EventStoreNamespaceResolverType`.
|
|
163
|
+
|
|
164
|
+
> **`AddCratisChronicle` on `IServiceCollection` does not exist**, despite what
|
|
165
|
+
> `Documentation/clients/dotnet/getting-started.md:61` shows. The real extensions
|
|
166
|
+
> are on `IHostApplicationBuilder`, `WebApplicationBuilder` (in the AspNetCore
|
|
167
|
+
> package), and Aspire's `IDistributedApplicationBuilder`. `IHostBuilder.AddCratisChronicle()`
|
|
168
|
+
> also exists but only registers concept type converters
|
|
169
|
+
> (`Source/Clients/DotNET/Hosting/HostBuilderExtensions.cs:18-23`) — it is not
|
|
170
|
+
> the wiring entry point.
|
|
171
|
+
|
|
172
|
+
`IChronicleBuilder` extensions are exactly five:
|
|
173
|
+
`WithArtifactsProvider`, `WithIdentityProvider`, `WithCorrelationIdAccessor`,
|
|
174
|
+
`WithNamespaceResolver`, `WithCamelCaseNamingPolicy`
|
|
175
|
+
(`Source/Clients/DotNET/ChronicleBuilderExtensions.cs`). **There is no
|
|
176
|
+
`WithClaimsBasedNamespaceResolver`**, despite a doc comment at
|
|
177
|
+
`ChronicleOptions.cs:141` referring to one. Pass the resolver instead:
|
|
178
|
+
`new ChronicleClient(options, namespaceResolver: new ClaimsBasedNamespaceResolver("tenant_id"))`.
|
|
179
|
+
|
|
180
|
+
## The client and the server check each other
|
|
181
|
+
|
|
182
|
+
Since Chronicle 17 the compatibility check is a server-side RPC, and **the client
|
|
183
|
+
runs it automatically inside `Connect()`** —
|
|
184
|
+
`Source/Clients/Connections/ChronicleConnection.cs:310` calls
|
|
185
|
+
`CheckCompatibility` (`:405`), sending the client type, client version, protocol
|
|
186
|
+
version, and the descriptor set its contracts package was built with (`:411-417`).
|
|
187
|
+
The rationale is at `:400-404`: Chronicle has clients in four languages and only
|
|
188
|
+
some can build a descriptor set at runtime, so each ships the one it was built
|
|
189
|
+
with and the server does the single comparison.
|
|
190
|
+
|
|
191
|
+
Behavior you can rely on:
|
|
192
|
+
|
|
193
|
+
- A server too old to have the RPC answers `Unimplemented`, and the client falls
|
|
194
|
+
back to the previous client-side exchange (`:419-425`) — upgrading the client
|
|
195
|
+
does not silently drop the check.
|
|
196
|
+
- Any other transport error is **logged and ignored** (`:426-432`), on the stated
|
|
197
|
+
reasoning that failing to ask says nothing about whether the two sides agree.
|
|
198
|
+
- A genuine mismatch throws `IncompatibleServerException`
|
|
199
|
+
(`Source/Clients/Connections/IncompatibleServerException.cs:10`) whose message
|
|
200
|
+
names the server address, its version, its protocol version, and the specific
|
|
201
|
+
incompatibilities (`:436-443`).
|
|
202
|
+
|
|
203
|
+
The client identifies itself as `".NET"`
|
|
204
|
+
(`Source/Clients/Connections/ChronicleClientIdentity.cs:22`) with its assembly
|
|
205
|
+
informational version (`:27`) and the contracts protocol version (`:32`).
|
|
206
|
+
|
|
207
|
+
## Event types
|
|
208
|
+
|
|
209
|
+
```csharp
|
|
210
|
+
using Cratis.Chronicle.Events;
|
|
211
|
+
|
|
212
|
+
/// <summary><What happened, in the past tense.></summary>
|
|
213
|
+
[EventType]
|
|
214
|
+
public record <EventName>(<Type> <Property>);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`EventTypeAttribute` is in `Cratis.Chronicle.Events` —
|
|
218
|
+
`Source/Clients/DotNET/Events/EventTypeAttribute.cs:20`:
|
|
219
|
+
|
|
220
|
+
```csharp
|
|
221
|
+
public sealed class EventTypeAttribute(string id = "", uint generation = EventTypeGeneration.FirstValue) : Attribute
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Two arguments only** — `id` (empty means the type name) and `generation`
|
|
225
|
+
(`EventTypeGeneration.FirstValue` is `1U`). There is no `isPublic` parameter.
|
|
226
|
+
`AttributeUsage` is `AttributeTargets.Class`, which records satisfy.
|
|
227
|
+
|
|
228
|
+
For a new event, pass nothing: the type name is the identifier. Use `generation`
|
|
229
|
+
only when evolving a contract that already exists, and prefer
|
|
230
|
+
`EventTypeGenerationForAttribute<TEventType>` over a second `[EventType]` — the
|
|
231
|
+
guidance is in the attribute's own remarks (`EventTypeAttribute.cs:14-18`).
|
|
232
|
+
|
|
233
|
+
## Appending
|
|
234
|
+
|
|
235
|
+
`IEventLog : IEventSequence` is a marker
|
|
236
|
+
(`Source/Clients/DotNET/EventSequences/IEventLog.cs:9`); the surface is on
|
|
237
|
+
`IEventSequence` (`Source/Clients/DotNET/EventSequences/IEventSequence.cs:14`):
|
|
238
|
+
|
|
239
|
+
```csharp
|
|
240
|
+
Task<AppendResult> Append( // :115
|
|
241
|
+
EventSourceId eventSourceId,
|
|
242
|
+
object @event,
|
|
243
|
+
EventStreamType? eventStreamType = default,
|
|
244
|
+
EventStreamId? eventStreamId = default,
|
|
245
|
+
EventSourceType? eventSourceType = default,
|
|
246
|
+
CorrelationId? correlationId = default,
|
|
247
|
+
IEnumerable<string>? tags = default,
|
|
248
|
+
ConcurrencyScope? concurrencyScope = default,
|
|
249
|
+
DateTimeOffset? occurred = default,
|
|
250
|
+
Subject? subject = default);
|
|
251
|
+
|
|
252
|
+
Task<AppendManyResult> AppendMany(EventSourceId eventSourceId, IEnumerable<object> events, /* same optionals */); // :144
|
|
253
|
+
Task<AppendManyResult> AppendMany(IEnumerable<EventForEventSourceId> events, /* ... */); // :167
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```csharp
|
|
257
|
+
var result = await eventStore.EventLog.Append(<eventSourceId>, new <EventName>(<value>));
|
|
258
|
+
if (!result.IsSuccess)
|
|
259
|
+
{
|
|
260
|
+
// result.ConstraintViolations, result.ConcurrencyViolation, result.Errors
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
- `EventSourceId` is a `record EventSourceId(string Value) : ConceptAs<string>`
|
|
265
|
+
with implicit conversion from `string` and `Guid`
|
|
266
|
+
(`Source/Clients/DotNET/Events/EventSourceId.cs:12`, `:37`, `:44`).
|
|
267
|
+
- `AppendResult` carries `SequenceNumber`, `IsSuccess`,
|
|
268
|
+
`HasConstraintViolations`, `HasConcurrencyViolations`, `HasErrors`,
|
|
269
|
+
`ConstraintViolations`, `ConcurrencyViolation`, `Errors`, and the routing facts
|
|
270
|
+
(`Source/Clients/DotNET/EventSequences/AppendResult.cs:13-59`).
|
|
271
|
+
- The `IEnumerable<EventForEventSourceId>` overload is the way to append across
|
|
272
|
+
several event sources in one batch.
|
|
273
|
+
- **There is no `AppendAnonymous`.** It does not exist anywhere in the product.
|
|
274
|
+
|
|
275
|
+
Reading back is on the same interface: `GetForEventSourceIdAndEventTypes` (`:51`),
|
|
276
|
+
`HasEventsFor` (`:58`), `GetFromSequenceNumber` (`:67`), `GetNextSequenceNumber`
|
|
277
|
+
(`:73`), `GetTailSequenceNumber` (`:84`). Erasure is `Redact` (`:191`, `:200`).
|
|
278
|
+
|
|
279
|
+
Several appends as one unit:
|
|
280
|
+
|
|
281
|
+
```csharp
|
|
282
|
+
var unitOfWork = eventStore.UnitOfWorkManager.Begin(CorrelationId.New());
|
|
283
|
+
await eventStore.EventLog.Transactional.Append(<eventSourceId>, new <EventName>(<value>));
|
|
284
|
+
await unitOfWork.Commit();
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`ITransactionalEventSequence` returns `Task`, not a result
|
|
288
|
+
(`Source/Clients/DotNET/EventSequences/ITransactionalEventSequence.cs:33`); the
|
|
289
|
+
results live on the unit of work.
|
|
290
|
+
|
|
291
|
+
## Observing
|
|
292
|
+
|
|
293
|
+
| Artifact | Shape | Source |
|
|
294
|
+
| --- | --- | --- |
|
|
295
|
+
| Reactor | marker `IReactor` (empty interface), `[Reactor]` optional | `Reactors/IReactor.cs:9`, `Reactors/ReactorAttribute.cs:14` |
|
|
296
|
+
| Reducer | `IReducerFor<TReadModel>` | `Reducers/IReducerFor.cs:10` |
|
|
297
|
+
| Projection | `IProjectionFor<TReadModel>` with `Define(IProjectionBuilderFor<TReadModel>)` | `Projections/IProjectionFor.cs:10-17` |
|
|
298
|
+
| Model-bound projection | attributes on the read model | `Projections/ModelBound/` |
|
|
299
|
+
| Read model reactor | marker `IReadModelReactor`, methods `Added`/`Modified`/`Removed` | `ReadModels/IReadModelReactor.cs:10-18` |
|
|
300
|
+
|
|
301
|
+
**A reactor handler is found by its first parameter's type**, and further
|
|
302
|
+
parameters are resolved as dependencies — `EventContext`, a read model, a service
|
|
303
|
+
(`Source/Clients/DotNET/Reactors/EventHandlerMethods.cs:21-46`, `:89-103`). The
|
|
304
|
+
return may be `Task`, `void`, or a side-effect event type.
|
|
305
|
+
|
|
306
|
+
```csharp
|
|
307
|
+
using Cratis.Chronicle.Events;
|
|
308
|
+
using Cratis.Chronicle.Reactors;
|
|
309
|
+
|
|
310
|
+
public class <ReactorName>(<IDependency> <dependency>) : IReactor
|
|
311
|
+
{
|
|
312
|
+
public async Task <AnyMethodName>(<EventName> @event, EventContext context)
|
|
313
|
+
{
|
|
314
|
+
await <dependency>.<DoSomething>(context.EventSourceId, @event.<Property>);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
> **There is no `[Observer]` attribute and no `IObserver` client artifact.**
|
|
320
|
+
> "Observer" is the kernel's umbrella term for a projection, reducer, or reactor;
|
|
321
|
+
> it is not something you write. `Source/Clients/DotNET/Observation/` holds
|
|
322
|
+
> `ObserverId`, `ObserverRunningState`, and failed-partition types only.
|
|
323
|
+
|
|
324
|
+
Read models are queried through `eventStore.ReadModels`
|
|
325
|
+
(`Source/Clients/DotNET/ReadModels/IReadModels.cs`):
|
|
326
|
+
`GetInstanceById<TReadModel>(ReadModelKey, ReadModelSessionId?)` (`:38`),
|
|
327
|
+
`GetInstances<TReadModel>(EventCount?)` (`:55`),
|
|
328
|
+
`GetSnapshotsById<TReadModel>` (`:63`),
|
|
329
|
+
`IObservable<ReadModelChangeset<TReadModel>> Watch<TReadModel>()` (`:70`).
|
|
330
|
+
|
|
331
|
+
## Discovery
|
|
332
|
+
|
|
333
|
+
Artifacts are found by **assembly scanning**, with no registration call and no DI
|
|
334
|
+
container required. `DefaultClientArtifactsProvider.Default` composes the
|
|
335
|
+
project-referenced and package-referenced assemblies
|
|
336
|
+
(`Source/Clients/DotNET/DefaultClientArtifactsProvider.cs:35`), and the
|
|
337
|
+
predicates are exactly (`:230-238`):
|
|
338
|
+
|
|
339
|
+
| Kind | Predicate |
|
|
340
|
+
| --- | --- |
|
|
341
|
+
| event types | `HasAttribute<EventTypeAttribute>()` or `HasAttribute<EventTypeGenerationForAttribute>()` |
|
|
342
|
+
| projections | `HasInterface(typeof(IProjectionFor<>))` |
|
|
343
|
+
| model-bound projections | `HasModelBoundProjectionAttributes()` |
|
|
344
|
+
| reactors | `HasInterface<IReactor>()` and not generic |
|
|
345
|
+
| read model reactors | `HasInterface<IReadModelReactor>()` and not generic |
|
|
346
|
+
| reducers | `HasInterface(typeof(IReducerFor<>))` and not generic |
|
|
347
|
+
|
|
348
|
+
Explicit registration is available per family as an alternative —
|
|
349
|
+
`IEventTypes.Register`, `IConstraints.Register`, `IProjections.Register`,
|
|
350
|
+
`IReducers.Register`, `IReactors.Register<TReactor>()`,
|
|
351
|
+
`IReadModels.Register<TReadModel>()` — and is what you use with
|
|
352
|
+
`AutoDiscoverAndRegister = false`.
|
|
353
|
+
|
|
354
|
+
## Lifecycle
|
|
355
|
+
|
|
356
|
+
- **Registration is wired to the connection, not called by you.**
|
|
357
|
+
`EventStore.cs:265-268` subscribes `RegisterAll` to `Connection.Lifecycle.OnConnected`
|
|
358
|
+
when `autoDiscoverAndRegister` is on, so a reconnect re-registers everything.
|
|
359
|
+
- `DiscoverAll()` does event types first, then constraints, reactors, reducers,
|
|
360
|
+
projections, and seeding in parallel (`EventStore.cs:349-362`). `RegisterAll()`
|
|
361
|
+
is single-flighted with jittered backoff and a background retry loop (`:373-379`).
|
|
362
|
+
- **Wait with `WaitForRegistration`, not by polling `IsConnected`.**
|
|
363
|
+
`RegistrationWaitExtensions.WaitForRegistration(this IEventStore, TimeSpan? timeout = default)`
|
|
364
|
+
(`Source/Clients/DotNET/Registrations/RegistrationWaitExtensions.cs:42`, default
|
|
365
|
+
5 seconds) exists precisely for this, and its own remarks warn against the
|
|
366
|
+
`IConnectionLifecycle.IsConnected` alternative (`:34-39`). Connected is not
|
|
367
|
+
registered.
|
|
368
|
+
- **Keepalive is a bidirectional stream plus a watchdog.** The watchdog monitors
|
|
369
|
+
every `MonitorIntervalMilliseconds = 1000`
|
|
370
|
+
(`Source/Clients/Connections/ConnectionWatchdog.cs:34`) and a keepalive that
|
|
371
|
+
falls more than 5 seconds behind is treated as a lost connection, with
|
|
372
|
+
reconnect backoff capped at 30 seconds. **The failure mode is silence, not an
|
|
373
|
+
exception** — appends keep working while observers go quiet.
|
|
374
|
+
- `Dispose()` disposes read model reactors for created stores, cancels the owned
|
|
375
|
+
connection, and disposes the connection
|
|
376
|
+
(`Source/Clients/DotNET/ChronicleClient.cs:225-246`).
|
|
377
|
+
|
|
378
|
+
## What a standalone app owns that a host would have supplied
|
|
379
|
+
|
|
380
|
+
Every one of these has a silent default. Read them as a checklist, because the
|
|
381
|
+
defaults are reasonable for a sample and wrong for a service
|
|
382
|
+
(`ChronicleClient.cs:121-127`):
|
|
383
|
+
|
|
384
|
+
| Concern | Default when you pass nothing |
|
|
385
|
+
| --- | --- |
|
|
386
|
+
| `IClientArtifactsProvider` | `DefaultClientArtifactsProvider.Default` — full assembly scan |
|
|
387
|
+
| `IServiceProvider` | `DefaultServiceProvider`, which activates everything through `Activator.CreateInstance` (`DefaultServiceProvider.cs:41`) |
|
|
388
|
+
| `IIdentityProvider` | `BaseIdentityProvider` |
|
|
389
|
+
| `ICorrelationIdAccessor` | `CorrelationIdAccessor` |
|
|
390
|
+
| `IEventStoreNamespaceResolver` | `DefaultEventStoreNamespaceResolver` — always `"Default"` |
|
|
391
|
+
| `ILoggerFactory` | `new LoggerFactory()` — **a silent one** |
|
|
392
|
+
| Configuration binding | none; there is no `Cratis:Chronicle` section without a host |
|
|
393
|
+
|
|
394
|
+
The `IServiceProvider` default is the sharp one: **a reactor or reducer with
|
|
395
|
+
constructor dependencies is default-constructed** unless you pass a real
|
|
396
|
+
container. Pass one, or keep observers dependency-free.
|
|
397
|
+
|
|
398
|
+
The AspNetCore package additionally registers `AddUnitOfWork()`,
|
|
399
|
+
`AddCompliance()`, `AddCausation()`, `AddChronicleHealthCheck()`, and
|
|
400
|
+
`UseCratisChronicle()` — none of which the base package gives you.
|
|
401
|
+
|
|
402
|
+
**Keep the process alive.** Observation is a live gRPC duplex stream; a console
|
|
403
|
+
app that appends and returns from `Main` never sees a reactor run.
|
|
404
|
+
|
|
405
|
+
## Common pitfalls
|
|
406
|
+
|
|
407
|
+
| Pitfall | Why it bites |
|
|
408
|
+
| --- | --- |
|
|
409
|
+
| Looking for `IChronicleClient.Connect` | It does not exist; `GetEventStore` connects, and `eventStore.Connection.Connect()` is the explicit form |
|
|
410
|
+
| `services.AddCratisChronicle(...)` | Not a real extension; it is on `IHostApplicationBuilder`/`WebApplicationBuilder` |
|
|
411
|
+
| `builder.WithClaimsBasedNamespaceResolver()` | Not a real extension; pass the resolver to the client instead |
|
|
412
|
+
| Shipping the default TLS behavior to production | Certificate validation is skipped by default |
|
|
413
|
+
| Constructing a client without an `ILoggerFactory` | You get a silent logger and lose every diagnostic the client emits |
|
|
414
|
+
| A reactor with constructor dependencies and no `IServiceProvider` | It is built by `Activator.CreateInstance` with no arguments |
|
|
415
|
+
| Polling `IsConnected` to know it is ready | Connected is not registered; use `WaitForRegistration` |
|
|
416
|
+
| Treating a quiet reactor as "no events yet" | Keepalive loss stops observers while appends keep succeeding |
|
|
417
|
+
| Returning from `Main` after appending | The observation stream dies with the process |
|
|
418
|
+
| Expecting the analyzers | `Cratis.Chronicle.CodeAnalysis` is a separate opt-in package |
|
|
419
|
+
| Copying a version out of the repository | It is a `1.0.0` placeholder; take it from nuget.org |
|
|
420
|
+
|
|
421
|
+
## Verify
|
|
422
|
+
|
|
423
|
+
- The package version is the one you intended, taken from nuget.org.
|
|
424
|
+
- The connection succeeds and no `IncompatibleServerException` is thrown — and if
|
|
425
|
+
the compatibility check was skipped, you know why (a transport error is logged
|
|
426
|
+
and ignored by design).
|
|
427
|
+
- A production connection string sets `skipTlsValidation=false`.
|
|
428
|
+
- An `ILoggerFactory` is supplied, and client logs are visible.
|
|
429
|
+
- Observers that take dependencies get a real `IServiceProvider`.
|
|
430
|
+
- Readiness is established with `WaitForRegistration`, not with `IsConnected`.
|
|
431
|
+
- The process stays alive for as long as observation is expected.
|
|
432
|
+
- The build is clean and the specifications pass against the verified package
|
|
433
|
+
version.
|