@cratis/pi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +43 -0
  2. package/package.json +24 -0
  3. package/skills/cratis-application-react-specifications/LICENSE +2 -0
  4. package/skills/cratis-application-react-specifications/SKILL.md +192 -0
  5. package/skills/cratis-application-slice-specifications/LICENSE +2 -0
  6. package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
  7. package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
  8. package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
  9. package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
  10. package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
  11. package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
  12. package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
  13. package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
  14. package/skills/cratis-arc-command/LICENSE +2 -0
  15. package/skills/cratis-arc-command/SKILL.md +286 -0
  16. package/skills/cratis-arc-command/references/command-result.md +111 -0
  17. package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
  18. package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
  19. package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
  20. package/skills/cratis-arc-command-execution/LICENSE +2 -0
  21. package/skills/cratis-arc-command-execution/SKILL.md +148 -0
  22. package/skills/cratis-arc-command-validation/LICENSE +2 -0
  23. package/skills/cratis-arc-command-validation/SKILL.md +230 -0
  24. package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
  25. package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
  26. package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
  27. package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
  28. package/skills/cratis-arc-query-paging/LICENSE +2 -0
  29. package/skills/cratis-arc-query-paging/SKILL.md +173 -0
  30. package/skills/cratis-arc-react-page/LICENSE +2 -0
  31. package/skills/cratis-arc-react-page/SKILL.md +291 -0
  32. package/skills/cratis-arc-react-page/references/data-page.md +125 -0
  33. package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
  34. package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
  35. package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
  36. package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
  37. package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
  38. package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
  39. package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
  40. package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
  41. package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
  42. package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
  43. package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
  44. package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
  45. package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
  46. package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
  47. package/skills/cratis-chronicle-compliance/LICENSE +2 -0
  48. package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
  49. package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
  50. package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
  51. package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
  52. package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
  53. package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
  54. package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
  55. package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
  56. package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
  57. package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
  58. package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
  59. package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
  60. package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
  61. package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
  62. package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
  63. package/skills/cratis-chronicle-projection/LICENSE +2 -0
  64. package/skills/cratis-chronicle-projection/SKILL.md +162 -0
  65. package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
  66. package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
  67. package/skills/cratis-chronicle-reactor/LICENSE +2 -0
  68. package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
  69. package/skills/cratis-chronicle-read-model/LICENSE +2 -0
  70. package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
  71. package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
  72. package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
  73. package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
  74. package/skills/cratis-chronicle-reducer/LICENSE +2 -0
  75. package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
  76. package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
  77. package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
  78. package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
  79. package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
  80. package/skills/cratis-code-review/LICENSE +2 -0
  81. package/skills/cratis-code-review/SKILL.md +177 -0
  82. package/skills/cratis-components-accessibility/LICENSE +2 -0
  83. package/skills/cratis-components-accessibility/SKILL.md +176 -0
  84. package/skills/cratis-components-schema-editor/LICENSE +2 -0
  85. package/skills/cratis-components-schema-editor/SKILL.md +238 -0
  86. package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
  87. package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
  88. package/skills/cratis-components-styling/LICENSE +2 -0
  89. package/skills/cratis-components-styling/SKILL.md +221 -0
  90. package/skills/cratis-components-toolbar/LICENSE +2 -0
  91. package/skills/cratis-components-toolbar/SKILL.md +259 -0
  92. package/skills/cratis-documentation-writing/LICENSE +2 -0
  93. package/skills/cratis-documentation-writing/SKILL.md +121 -0
  94. package/skills/cratis-event-model-diagram/LICENSE +2 -0
  95. package/skills/cratis-event-model-diagram/SKILL.md +142 -0
  96. package/skills/cratis-fundamentals-concept/LICENSE +2 -0
  97. package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
  98. package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
  99. package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
  100. package/skills/cratis-governed-release-methodology/LICENSE +2 -0
  101. package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
  102. package/skills/cratis-lens-browser-extension/LICENSE +2 -0
  103. package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
  104. package/skills/cratis-performance-review/LICENSE +2 -0
  105. package/skills/cratis-performance-review/SKILL.md +139 -0
  106. package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
  107. package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
  108. package/skills/cratis-security-review/LICENSE +2 -0
  109. package/skills/cratis-security-review/SKILL.md +141 -0
  110. package/skills/cratis-specification-by-example/LICENSE +2 -0
  111. package/skills/cratis-specification-by-example/SKILL.md +188 -0
  112. package/skills/cratis-specifications-csharp/LICENSE +2 -0
  113. package/skills/cratis-specifications-csharp/SKILL.md +230 -0
  114. package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
  115. package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
  116. package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
  117. package/skills/cratis-specifications-typescript/LICENSE +2 -0
  118. package/skills/cratis-specifications-typescript/SKILL.md +202 -0
  119. package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
  120. package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
  121. package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
  122. package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
  123. package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
  124. package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
  125. package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
@@ -0,0 +1,396 @@
1
+ ---
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.
4
+ license: MIT
5
+ ---
6
+
7
+ # The Chronicle client for TypeScript
8
+
9
+ `@cratis/chronicle` is a **standalone Node.js client SDK** that speaks gRPC to a
10
+ Chronicle server. It is not a browser library and it is not the Arc TypeScript
11
+ proxy layer — an Arc frontend calls generated command and query proxies over
12
+ HTTP and never sees this package.
13
+
14
+ ## Verified product sources
15
+
16
+ This skill is verified against `Cratis/Chronicle.TypeScript` at tag `v4.0.0`
17
+ (commit `bed0b86`), which is the version published on npm.
18
+
19
+ | Package | Version | Where |
20
+ | --- | --- | --- |
21
+ | `@cratis/chronicle` | `4.0.0` | `Source/package.json:2` |
22
+ | `@cratis/chronicle.contracts` | `17.0.0` | `Source/package.json` dependency |
23
+ | `@cratis/fundamentals` | `^7.14.0` | **peer dependency** — you install it |
24
+
25
+ > **Do not read the version out of the repository.** `Source/package.json` says
26
+ > `1.0.0`; `publish-version.js` overwrites it at publish time. Take the version
27
+ > from npm.
28
+
29
+ **`v4.0.0` is a major bump and it changed real API.** Guidance written against
30
+ `3.x` is wrong in at least three places — event tags did not exist, reducers took
31
+ two arguments, and two event-store RPCs were renamed. Do not carry `3.x` examples
32
+ forward.
33
+
34
+ ## Project setup
35
+
36
+ ```shell
37
+ yarn add @cratis/chronicle @cratis/fundamentals reflect-metadata
38
+ ```
39
+
40
+ `@cratis/fundamentals` is a peer dependency, so it does not arrive on its own.
41
+ `reflect-metadata` must be imported **once, at the entry point**, before any
42
+ decorated class is loaded (`Documentation/getting-started.md:16-27`):
43
+
44
+ ```typescript
45
+ import 'reflect-metadata';
46
+ ```
47
+
48
+ The package is **ESM-only** (`"type": "module"`, `Source/package.json:34`) and
49
+ requires decorator support:
50
+
51
+ ```jsonc
52
+ // tsconfig.json
53
+ {
54
+ "compilerOptions": {
55
+ "experimentalDecorators": true,
56
+ "emitDecoratorMetadata": true
57
+ }
58
+ }
59
+ ```
60
+
61
+ Both are set in the shipped sample (`Samples/Console/tsconfig.json:12-13`).
62
+
63
+ ## Connecting
64
+
65
+ ```typescript
66
+ import 'reflect-metadata';
67
+ import { ChronicleClient, ChronicleOptions } from '@cratis/chronicle';
68
+
69
+ const client = new ChronicleClient(ChronicleOptions.development());
70
+ try {
71
+ const store = await client.getEventStore('<EventStoreName>');
72
+ // ... use store ...
73
+ } finally {
74
+ client.dispose();
75
+ }
76
+ ```
77
+
78
+ `IChronicleClient` is three members — `Source/IChronicleClient.ts:16-39`:
79
+
80
+ | Member | Line |
81
+ | --- | --- |
82
+ | `readonly options: ChronicleOptions` | `:18` |
83
+ | `getEventStore(name, namespace?): Promise<IEventStore>` | `:27` |
84
+ | `getEventStores(): Promise<EventStoreName[]>` | `:33` |
85
+ | `dispose(): void` | `:38` |
86
+
87
+ `ChronicleClient`'s constructor takes exactly one argument —
88
+ `constructor(readonly options: ChronicleOptions)`, `Source/ChronicleClient.ts:58`.
89
+
90
+ **Connecting is implicit and lazy.** There is no `connect()`. The constructor
91
+ starts a health-check watchdog and kicks off artifact discovery; the actual
92
+ connect happens on the first `getEventStore`/`getEventStores` through an internal
93
+ `ensureConnected()`. `getEventStore` also *creates* the store if it does not
94
+ exist — it calls `ensureEventStore` (`Source/ChronicleClient.ts:135`).
95
+
96
+ `dispose()` is **synchronous** (`Source/ChronicleClient.ts:193`), not
97
+ `Symbol.dispose` or `Symbol.asyncDispose`. After disposal every call throws.
98
+
99
+ ### Options
100
+
101
+ `ChronicleOptions` has a **private constructor**
102
+ (`Source/ChronicleOptions.ts:67`); use a static factory:
103
+
104
+ - `ChronicleOptions.fromConnectionString(connectionString, options?)` — `:92`
105
+ - `ChronicleOptions.development(options?)` — `:116`
106
+
107
+ The optional bag accepts `clientArtifactsProvider`, `discoveryPatterns`, and
108
+ `defaultSinkTypeId`. `defaultSinkTypeId` defaults to `WellKnownSinks.MongoDB`
109
+ (`:84`).
110
+
111
+ > `programIdentifier`, `softwareVersion`, and `softwareCommit` exist on the type
112
+ > (`:69-71`) but **neither public factory forwards them**, so they cannot be set.
113
+ > Do not write guidance that configures them.
114
+
115
+ ### The connection string
116
+
117
+ Schemes `chronicle://` and `chronicle+srv://`, default port `35000`
118
+ (`Source/connection/ChronicleConnectionString.ts`). Query parameters: `apiKey`,
119
+ `disableTls`, `skipTlsValidation`, `certificatePath`, `certificatePassword`,
120
+ `loadBalancer`, `srvNameServer`.
121
+
122
+ Two facts to carry into production guidance:
123
+
124
+ - **`skipTlsValidation` defaults to `true`.** TLS is on but the certificate chain
125
+ is not validated, because a development kernel serves a self-signed
126
+ certificate. Set `?skipTlsValidation=false` against a real server.
127
+ - **A connection string with no credentials silently falls back to the
128
+ development client credentials** (`Source/connection/ChronicleConnection.ts`).
129
+ An anonymous-looking connection string is not anonymous; it is
130
+ `chronicle-dev-client`.
131
+
132
+ > **There is no `./connection` subpath export.** `ChronicleConnectionString`,
133
+ > `ChronicleConnectionStringBuilder`, `AuthenticationMode`, and `LoadBalancerMode`
134
+ > are unreachable from the public entry points — `Source/index.ts` never
135
+ > re-exports them and `Source/package.json`'s `exports` map has no
136
+ > `"./connection"` key. **Application code passes a connection *string*.** Do not
137
+ > write an example that imports a connection-string type.
138
+
139
+ The subpath exports that do exist: `.`, `./events`, `./eventSequences`,
140
+ `./eventStoreSubscriptions`, `./compliance`, `./reactors`, `./reducers`,
141
+ `./seeding`, `./readModels`, `./projections`, `./jobs`, `./webhooks`,
142
+ `./externalServices`, `./identities`, `./observation`, `./sinks`, `./schemas`,
143
+ `./types`, `./artifacts`, `./identity`, `./auditing`, `./correlation`,
144
+ `./transactions`.
145
+
146
+ ## Event types
147
+
148
+ ```typescript
149
+ import { eventType } from '@cratis/chronicle';
150
+
151
+ @eventType()
152
+ export class <EventName> {
153
+ constructor(readonly <property>: <Type> = <default>) {}
154
+ }
155
+ ```
156
+
157
+ Seven overloads — `Source/events/eventTypeDecorator.ts:54-60`:
158
+
159
+ ```typescript
160
+ export function eventType(): ClassDecorator;
161
+ export function eventType(id: string): ClassDecorator;
162
+ export function eventType(id: string, generation: number): ClassDecorator;
163
+ export function eventType(id: string, generation: number, tombstone: boolean): ClassDecorator;
164
+ export function eventType(generation: number): ClassDecorator;
165
+ export function eventType(generation: number, tombstone: boolean): ClassDecorator;
166
+ export function eventType(tombstone: boolean): ClassDecorator;
167
+ ```
168
+
169
+ **The event type id defaults to the class name** — `new EventTypeId(id || constructor.name)`
170
+ at `:91`. Two classes with the same name in different modules collide on the wire.
171
+
172
+ **Give constructor parameters default values.** Member discovery walks explicit
173
+ `@field` declarations from `@cratis/fundamentals` first, then tracked properties,
174
+ then the properties present on a *default-constructed* instance, then
175
+ `design:paramtypes` — `Source/types/TypeIntrospector.ts:52-60`, with the reason
176
+ spelled out at `:40-48`: **esbuild and tsx do not emit `design:type` or
177
+ `design:paramtypes`**. Under `tsx`, a class whose constructor parameters have no
178
+ defaults and no `@field` decorators produces no members and an empty schema. Every
179
+ sample event carries defaults for exactly this reason.
180
+
181
+ Tag an event type at declaration with `@tag(...)` / `@tags(...)` —
182
+ `Source/events/tagDecorator.ts:30`, `:42`.
183
+
184
+ ## Appending
185
+
186
+ `IEventLog` is an empty extension of `IEventSequence`
187
+ (`Source/eventSequences/IEventLog.ts:10`); the surface is on `IEventSequence`:
188
+
189
+ ```typescript
190
+ append(eventSourceId: string, event: object, options?: AppendOptions): Promise<AppendResult>; // :44
191
+ appendMany(eventSourceId: string, events: object[], options?: AppendOptions): Promise<AppendResult[]>; // :53
192
+ appendMany(events: EventForEventSourceId[], options?: AppendOptions): Promise<AppendResult[]>; // :61
193
+ ```
194
+
195
+ ```typescript
196
+ const result = await store.eventLog.append('<event-source-id>', new <EventName>('<value>'));
197
+ if (!result.isSuccess) {
198
+ // result.constraintViolations, result.concurrencyViolation, result.errors
199
+ }
200
+ ```
201
+
202
+ - **The event source id is a plain `string`.** There is no `EventSourceId` value
203
+ type at this boundary.
204
+ - `AppendResult` — `Source/eventSequences/AppendResult.ts:15-38`: `sequenceNumber`,
205
+ `constraintViolations`, `concurrencyViolation?`, `errors`, `isSuccess`, and
206
+ `waitForCompletion(timeoutMs?)` which defaults to 5000 ms.
207
+ - **`sequenceNumber` wraps a `bigint`.** Read it as `result.sequenceNumber.value`.
208
+ `EventContext.sequenceNumber` is a `bigint` too.
209
+ - `AppendOptions` — `Source/eventSequences/AppendOptions.ts`: `correlationId?`,
210
+ `eventSourceId?`, `concurrencyScope?`, `concurrencyScopes?`, and **`tags?`**
211
+ (`:22-29`, new in `4.0.0`).
212
+
213
+ > **Single `append()` cannot target a stream or a subject.** It hardcodes
214
+ > `EventSourceType: 'Default'`, `EventStreamType: 'Default'`,
215
+ > `EventStreamId: eventSourceId`, and `Subject: eventSourceId`
216
+ > (`Source/eventSequences/EventSequence.ts:100-119`). Only `tags` became a real
217
+ > option in `4.0.0` (`:117`). To target a different stream or subject, use the
218
+ > `EventForEventSourceId[]` overload, whose entries carry `eventStreamType`,
219
+ > `eventStreamId`, `eventSourceType`, `subject`, and `tags`.
220
+
221
+ Correlation id, identity, and the causation chain are picked up from the ambient
222
+ module-level managers (`identityProvider`, `causationManager`,
223
+ `correlationIdManager`) — you do not pass them.
224
+
225
+ Several appends as one unit:
226
+
227
+ ```typescript
228
+ const unitOfWork = store.unitOfWorkManager.begin();
229
+ await store.eventLog.transactional.append('<id>', new <EventName>('<value>'));
230
+ await unitOfWork.commit();
231
+ ```
232
+
233
+ `ITransactionalEventSequence` returns `Promise<void>`, not an `AppendResult`;
234
+ results are on the unit of work.
235
+
236
+ ## Observing
237
+
238
+ ### Handlers are found by camelCase method name
239
+
240
+ This is the defining convention of this client, and it is **not** the parameter
241
+ type. `Source/reactors/Reactors.ts:378-381` computes the method name from the
242
+ event class name and looks it up on the prototype:
243
+
244
+ ```typescript
245
+ const className = (eventTypeClass as Function).name;
246
+ const methodName = className.charAt(0).toLowerCase() + className.slice(1);
247
+
248
+ if (typeof proto[methodName] === 'function') { /* it is a handler */ }
249
+ ```
250
+
251
+ Reducers do the same (`Source/reducers/Reducers.ts:454-460`). A handler whose
252
+ method name does not match is simply never called — **there is no error**.
253
+
254
+ > The docstring at `Source/reactors/reactor.ts:28-29` claims dispatch is by the
255
+ > first parameter's type. **It is wrong**, and it is still wrong at `4.0.0`. The
256
+ > code is the authority; so are the client snippets under `Documentation/`, which
257
+ > state the name rule explicitly.
258
+
259
+ ```typescript
260
+ import { reactor } from '@cratis/chronicle/reactors';
261
+ import { EventContext, EventForEventSourceId } from '@cratis/chronicle';
262
+
263
+ @reactor()
264
+ export class <ReactorName> {
265
+ async <eventName>(event: <EventName>, context: EventContext): Promise<EventForEventSourceId> {
266
+ return {
267
+ eventSourceId: '<other-event-source-id>',
268
+ event: new <SideEffectEvent>(context.eventSourceId)
269
+ };
270
+ }
271
+ }
272
+ ```
273
+
274
+ A returned event, array, or `EventForEventSourceId` is appended as a side effect;
275
+ a failed side-effect append fails the partition.
276
+
277
+ > **The handler receives `JSON.parse(event.Content)` — a plain object, not an
278
+ > instance of your event class** (`Source/reactors/Reactors.ts:297`, `:320`;
279
+ > `Source/reducers/Reducers.ts:377`). `instanceof` checks and methods on the event
280
+ > class do not work. Read properties only.
281
+
282
+ `@reducer(id?, eventSequenceId?, readModel?, isActive?)` —
283
+ `Source/reducers/reducer.ts:58`. Passing `readModel` also registers that type as a
284
+ read model. **The reducer handler takes three arguments at `4.0.0`** —
285
+ `reducerInstance[methodName](content, currentState, context)`
286
+ (`Source/reducers/Reducers.ts:400`). The third is optional by arity, and a handler
287
+ may be sync or async.
288
+
289
+ Restrict an observer to tagged events with `@filterEventsByTag(value)` —
290
+ `Source/events/filterEventsByTagDecorator.ts:36`.
291
+
292
+ ### Read models and projections
293
+
294
+ `store.readModels` — `Source/readModels/IReadModels.ts`: `getInstanceById(type, key, sessionId?)`
295
+ (`:38`), `getInstances(type, eventCount?)` (`:46`), `getSnapshotsById` (`:54`),
296
+ `watch(type): AsyncIterable<ReadModelChangeset<T>>` (`:61`), plus `materialized`
297
+ for paged access.
298
+
299
+ Two projection styles:
300
+
301
+ - **Model-bound** — decorators on the read model, from
302
+ `@cratis/chronicle/projections`: `fromEvent`, `fromEvery`, `fromAll`, `setFrom`,
303
+ `setFromContext`, `setValue`, `join`, `addFrom`, `subtractFrom`, `increment`,
304
+ `decrement`, `count`, `childrenFrom`, `nested`, `clearWith`, `removedWith`,
305
+ `removedWithJoin`, `noAutoMap`, `notRewindable`, `passive`.
306
+ - **Declarative** — `@projection(id?, readModelType?, eventSequenceId?)` on a class
307
+ implementing `IProjectionFor<TReadModel>` with
308
+ `define(builder: IProjectionBuilderFor<TReadModel>): void`.
309
+
310
+ Constraints are `@constraint()` on a class implementing `IConstraint` with a
311
+ `define(builder)`; the builder gives `unique(...)`, `uniqueFor(...)`,
312
+ `perEventSourceType`, `perEventStreamType`, `perEventStreamId`. **There are no
313
+ model-bound constraint decorators in this client** — the class-plus-builder form
314
+ is the only one.
315
+
316
+ ## Discovery is a runtime file glob — this is the biggest difference
317
+
318
+ `ChronicleOptions.discoveryPatterns` defaults to
319
+ `['**/*.ts', '!**/*.d.ts', '!**/node_modules', '!**/dist', '!**/build', '!**/.git', '!**/.vscode', '!**/*.spec.ts', '!**/*.test.ts']`
320
+ (`Source/ChronicleOptions.ts:73-83`), and the client `import()`s every matching
321
+ file at construction time so that the decorators run. Registration is into a
322
+ process-wide static map.
323
+
324
+ Two consequences that decide whether an application works at all:
325
+
326
+ 1. **The default pattern matches `.ts`, not `.js`.** It works under a
327
+ TypeScript-capable runtime — the sample runs `tsx index.ts` — and a compiled
328
+ `dist/*.js` deployment discovers **nothing** unless `discoveryPatterns` is
329
+ overridden.
330
+ 2. **Decorators only register when their module is imported.** Side-effect imports
331
+ of event, reactor, reducer, and projection modules are load-bearing; the sample
332
+ does exactly this with a comment saying why.
333
+
334
+ `EventStore.registerArtifacts()` registers event types first, then everything else
335
+ in parallel, and re-runs for every cached event store on reconnect.
336
+
337
+ ## Lifecycle
338
+
339
+ - A health-check watchdog runs `getVersionInfo` every 5 seconds and reconnects on
340
+ failure; the interval handle is `unref()`'d so it does not hold the process open.
341
+ - Reconnect uses exponential backoff with jitter, capped at 30 seconds, rebuilding
342
+ the gRPC channel on every attempt.
343
+ - **Keepalive registration is what keeps observers alive.** Losing it triggers a
344
+ full reconnect, because the kernel tears down the client's observers otherwise.
345
+ - Failed operations are retried once after reconnecting, keyed on gRPC status
346
+ codes 4/13/14 plus a substring match over the error text.
347
+
348
+ > **The client version reported to the kernel is a hardcoded `'1.0.0'`** with a
349
+ > TODO — `Source/ChronicleClient.ts:429`, alongside `ClientType: 'TypeScript'`
350
+ > (`:434`). The Chronicle 17 server-side compatibility check therefore sees a
351
+ > placeholder from this client, and grepping `Source/` for `CheckCompatibility`,
352
+ > `ProtocolVersion`, or descriptor-set handling finds nothing. **Do not tell
353
+ > developers this client negotiates protocol compatibility** — it does not.
354
+
355
+ ## No dependency injection
356
+
357
+ There is none, and none is planned in the source: no container integration, no
358
+ `addChronicle`-style registration. Reactors, reducers, projections, constraints,
359
+ and seeders are instantiated by the client itself, so **constructor injection does
360
+ not work**. Use module-scope collaborators, as the shipped sample does.
361
+
362
+ ## Common pitfalls
363
+
364
+ | Pitfall | Why it bites |
365
+ | --- | --- |
366
+ | Naming a reactor method after the handler's purpose | Dispatch is by the camelCase event class name; a mismatch is silently never called |
367
+ | `instanceof` on the event inside a handler | It is a `JSON.parse`d plain object, not your class |
368
+ | Event class with no default constructor values under `tsx` | No decorator metadata is emitted, so the schema comes out empty |
369
+ | Two `@eventType()` classes sharing a class name | The id defaults to the class name and they collide |
370
+ | Deploying compiled `dist/*.js` with default discovery | The default glob matches `**/*.ts`; nothing is discovered |
371
+ | Omitting a side-effect import of an artifact module | Its decorator never runs, so it is never registered |
372
+ | Forgetting `import 'reflect-metadata'` at the entry point | Decorators do not work at runtime |
373
+ | Forgetting to install `@cratis/fundamentals` | It is a peer dependency and is not installed for you |
374
+ | Importing `ChronicleConnectionString` | There is no `./connection` export; pass a string |
375
+ | Expecting single `append()` to set a stream or subject | Those fields are hardcoded; use the `EventForEventSourceId[]` overload |
376
+ | A connection string with no credentials | Silently falls back to the development client credentials |
377
+ | Shipping the default TLS behavior | `skipTlsValidation` defaults to `true` |
378
+ | Constructor-injecting a dependency into a reactor | There is no DI; the client constructs it |
379
+ | Carrying a `3.x` example forward | Tags, the reducer context parameter, and two RPC names changed in `4.0.0` |
380
+ | Reading `result.sequenceNumber` as a number | It is a `bigint` behind `.value` |
381
+
382
+ ## Verify
383
+
384
+ - The installed version is the one you intended, taken from npm, and
385
+ `@cratis/fundamentals` is installed alongside it.
386
+ - `reflect-metadata` is imported exactly once, at the entry point.
387
+ - `experimentalDecorators` and `emitDecoratorMetadata` are both on.
388
+ - Every event class either has `@field` declarations or default constructor
389
+ values, and its schema is non-empty at runtime.
390
+ - `discoveryPatterns` matches the files that actually ship in the deployed
391
+ artifact.
392
+ - Every reactor and reducer handler is named as the exact camelCase of its event
393
+ class, and an appended event demonstrably reaches it.
394
+ - A production connection string carries credentials and `skipTlsValidation=false`.
395
+ - `client.dispose()` runs on shutdown.
396
+ - Lint, `tsc`, and the test suite are clean against the verified package version.
@@ -0,0 +1,2 @@
1
+ Copyright (c) Cratis. All rights reserved.
2
+ Licensed under the MIT license. See LICENSE file in the project root for full license information.
@@ -0,0 +1,246 @@
1
+ ---
2
+ name: cratis-chronicle-compliance
3
+ description: Model personal data in Chronicle with [PII] and [Subject], and erase it through crypto-shredding with IPIIManager. Use when an event or read model carries data about a natural person, when a subject must be identified for erasure, when a right-to-erasure request must be executed, or when redacting a stored event. Do not use for authorization, authentication, secret handling, or generic data-protection policy.
4
+ license: MIT
5
+ ---
6
+
7
+ # Chronicle compliance and personal data
8
+
9
+ Chronicle keeps events forever, so personal data cannot be deleted from the log.
10
+ Chronicle protects it by encrypting each marked value under a key owned by the
11
+ subject, and erases it by destroying that key. Decide the subject before you
12
+ decide the event shape — the subject is what erasure operates on.
13
+
14
+ ## Verified product sources
15
+
16
+ This skill is verified against this exact public release:
17
+
18
+ | Package | Version | Purpose |
19
+ | --- | --- | --- |
20
+ | `Cratis.Chronicle` | `16.45.3` | `Cratis.Chronicle.Compliance.GDPR`, `Cratis.Chronicle.SubjectAttribute`, `IEventSequence.Redact`, `IReadModels.Release` |
21
+ | `Cratis.Chronicle.CodeAnalysis` | `16.45.3` | `CHR0026`, `CHR0034`, `CHR0035`, `CHR0043`, `CHR0046` |
22
+
23
+ Reverify product sources before claiming support for another version.
24
+
25
+ ## Mark personal data with `[PII]`
26
+
27
+ `Cratis.Chronicle.Compliance.GDPR.PIIAttribute` marks a value as personal data.
28
+ It targets a class, a property, or a constructor parameter, and it is not
29
+ repeatable.
30
+
31
+ ```csharp
32
+ using Cratis.Chronicle.Compliance;
33
+ using Cratis.Chronicle.Compliance.GDPR;
34
+
35
+ [EventType]
36
+ public record <PersonEventName>(
37
+ [property: PII] string <PersonalPropertyName>,
38
+ <NonPersonalType> <BusinessPropertyName>);
39
+ ```
40
+
41
+ On a positional record the attribute must be written as `[property: PII]` or
42
+ placed on the constructor parameter; Chronicle looks for it on the property, the
43
+ property's declaring type, the property's own type, and the matching primary
44
+ constructor parameter.
45
+
46
+ Marking the concept type itself makes the value personal everywhere it appears,
47
+ which is usually what you want for an email, a phone number, or a personal name:
48
+
49
+ ```csharp
50
+ [PII]
51
+ [ComplianceDetails("<why this value is collected and under what basis>")]
52
+ public record <ConceptName>(string Value) : ConceptAs<string>(Value);
53
+ ```
54
+
55
+ `PIIAttribute` accepts an optional `details` string, **but Chronicle never reads
56
+ it.** The rationale that actually reaches the event schema comes from
57
+ `Cratis.Chronicle.Compliance.ComplianceDetailsAttribute`. Write `[PII]` for the
58
+ marker and `[ComplianceDetails("...")]` for the reason; do not write
59
+ `[PII("reason")]` and expect the reason to be recorded.
60
+
61
+ `PII` is the only compliance category Chronicle defines — there is exactly one
62
+ well-known `ComplianceMetadataType`. Chronicle has no `[Redact]`, `[Erase]`,
63
+ `[Anonymize]`, `[Encrypt]`, or `[NotAudited]` attribute; do not write one into
64
+ an example. Redaction is an API call, not an annotation.
65
+
66
+ Mark only genuinely personal values. Business metadata, identifiers of
67
+ non-persons, and amounts stay unmarked — every marked value costs an encryption
68
+ round trip and becomes unreadable the moment the subject is erased.
69
+
70
+ ## Decide the subject
71
+
72
+ The subject is the natural person a value belongs to, and it is the unit of
73
+ erasure. `Cratis.Chronicle.SubjectAttribute` marks the value that supplies it.
74
+ The attribute is a pure marker with no arguments and targets a property or a
75
+ constructor parameter.
76
+
77
+ For an appended event, Chronicle uses the explicit subject when one is supplied
78
+ and otherwise falls back to the event source id. That default is the shape you
79
+ want: a person-per-stream model needs no `[Subject]` at all.
80
+
81
+ ```csharp
82
+ [EventType]
83
+ public record <PersonEventName>(
84
+ [property: Subject] <SubjectValueType> <SubjectPropertyName>,
85
+ [property: PII] string <PersonalPropertyName>);
86
+ ```
87
+
88
+ Add `[Subject]` only when the person is **not** the event source — for example
89
+ an event on an order stream that carries the customer's personal data. A
90
+ reactor can supply the subject for its side-effect events by implementing
91
+ `Cratis.Chronicle.Events.ICanProvideSubject`, whose single member is
92
+ `Subject GetSubject()`.
93
+
94
+ `Cratis.Chronicle.Subject` is `record Subject(string Value) : ConceptAs<string>`
95
+ with a `NotSet` value, an `IsSet` test, and implicit conversion from `string`,
96
+ `Guid`, and `EventSourceId`.
97
+
98
+ For a read model instance, Chronicle resolves the subject from the `[Subject]`
99
+ property that has a value, then the `[Subject]` constructor parameter, then a
100
+ property named `Id`. A stored document carries one default subject; keep one
101
+ person's personal data in one document rather than mixing several people into
102
+ one row.
103
+
104
+ ### Do not put `[Subject]` or `[PII]` on the stream identity
105
+
106
+ An `EventSourceId<T>`-derived value already is both the key and the compliance
107
+ subject.
108
+
109
+ - `[Key]` or `[Subject]` on it is redundant — analyzer **CHR0026** (warning).
110
+ - `[PII]` on it is rejected — analyzer **CHR0034** (error), and at runtime
111
+ `Cratis.Chronicle.Compliance.GDPR.PIINotSupportedOnEventSourceId`.
112
+
113
+ Chronicle cannot encrypt an event source id. When the natural identifier is
114
+ itself sensitive, use a random surrogate stream id and carry the sensitive value
115
+ as a `[PII]` property.
116
+
117
+ ### Reserved read-model property names
118
+
119
+ Chronicle stamps `__subject` and `__subjects` onto stored read-model documents.
120
+ Analyzer **CHR0035** (error) rejects a `[ReadModel]` that declares `_subject`,
121
+ `__subject`, or `__subjects`. Rename the property.
122
+
123
+ ## Erase a subject
124
+
125
+ `IEventStore.PII` exposes `Cratis.Chronicle.Compliance.GDPR.IPIIManager`:
126
+
127
+ ```csharp
128
+ Task DeleteEncryptionKeyFor(EncryptionKeyIdentifier identifier);
129
+ Task AllowNewEncryptionKeyFor(EncryptionKeyIdentifier identifier);
130
+ ```
131
+
132
+ `EncryptionKeyIdentifier` is a `ConceptAs<string>` with implicit conversion from
133
+ `string`, so the subject value can be passed directly:
134
+
135
+ ```csharp
136
+ var eventStore = await chronicleClient.GetEventStore("<EventStoreName>");
137
+ await eventStore.PII.DeleteEncryptionKeyFor("<subject-value>");
138
+ ```
139
+
140
+ What this does and does not do:
141
+
142
+ - It destroys the subject's key and records an **erasure fence** so a later
143
+ write cannot resurrect a key for that subject. The fence is monotonic and is
144
+ recorded before any key is deleted.
145
+ - The events stay in the log. Their `[PII]` values remain as unreadable
146
+ ciphertext — this is crypto-shredding, not deletion.
147
+ - Reading an erased value yields an **empty string** rather than an exception,
148
+ so queries and read models over an erased subject keep working. An erased
149
+ container value comes back as an empty object or array.
150
+ - Appending a new `[PII]` value for an erased subject **fails** with
151
+ `EncryptionKeyErased`. It does not quietly mint a new key. Call
152
+ `AllowNewEncryptionKeyFor` deliberately if the subject must be re-enrolled.
153
+ - A partially completed erasure throws `EncryptionKeyErasureIncomplete`, which
154
+ carries the individual failures. Treat it as unfinished and retry — the fence
155
+ is already in place, so retrying is safe.
156
+
157
+ **Erasure is scoped to one namespace.** There is no cross-namespace erasure. In
158
+ a multi-tenant deployment, an erasure request that spans tenants is one call per
159
+ tenant namespace, and completing only some of them is an incomplete erasure.
160
+
161
+ ## Redact a stored event
162
+
163
+ Erasure removes a subject's readability. Redaction removes a specific event's
164
+ content. `Cratis.Chronicle.EventSequences.IEventSequence` exposes both forms:
165
+
166
+ ```csharp
167
+ Task Redact(EventSequenceNumber sequenceNumber, RedactionReason reason);
168
+ Task Redact(EventSourceId eventSourceId, RedactionReason reason, params Type[] clrEventTypes);
169
+ ```
170
+
171
+ `RedactionReason` is a `ConceptAs<string>` with an `Unknown` value and implicit
172
+ conversion from `string`. Chronicle records the redaction itself as an
173
+ `EventRedacted` event, so the fact that a redaction happened stays auditable.
174
+
175
+ Use redaction for content that should never have been appended. Use key erasure
176
+ for a subject exercising a right to erasure. They are different mechanisms and
177
+ one does not imply the other.
178
+
179
+ ## Read personal data back
180
+
181
+ The kernel decrypts on the paths it owns, so projections and observers see plain
182
+ values. When a read-model instance is fetched outside those paths, decrypt it
183
+ explicitly through `Cratis.Chronicle.ReadModels.IReadModels`:
184
+
185
+ ```csharp
186
+ Task<TReadModel> Release<TReadModel>(TReadModel instance);
187
+ Task<IEnumerable<TReadModel>> Release<TReadModel>(IEnumerable<TReadModel> instances);
188
+ ```
189
+
190
+ `Release` degrades quietly by design: it returns the instance unchanged when no
191
+ subject can be derived, when the type carries no compliance metadata, or when
192
+ decryption fails. It logs, it does not throw. Do not treat a value that came
193
+ back looking encrypted as proof the data is intact — check the subject
194
+ resolution first.
195
+
196
+ The asymmetry is deliberate and worth knowing: **applying** compliance on append
197
+ fails loudly, **releasing** it on read fails quietly.
198
+
199
+ ## Setup
200
+
201
+ An ASP.NET Core host that calls `builder.AddCratisChronicle()` needs no
202
+ compliance setup — `AddCompliance()` is already called for you, and the raw
203
+ client path discovers the metadata providers by convention. The developer-facing
204
+ requirement is only to annotate the types.
205
+
206
+ Key storage is a hosting decision, not an application one. Chronicle ships
207
+ MongoDB (the default), SQL, in-memory, HashiCorp Vault, and Azure Key Vault key
208
+ storage, configured under `Cratis:Chronicle:Compliance:Encryption`.
209
+
210
+ > **Operational trap.** Switching to a dedicated compliance key store without
211
+ > setting `MigrateFromDefaultStorage` leaves every existing key unreachable.
212
+ > Every `[PII]` value then reads back as an empty string, with no exception and
213
+ > no log — byte-for-byte indistinguishable from a completed erasure. Verify the
214
+ > migration flag before switching stores, and confirm a known subject still
215
+ > reads after the switch.
216
+
217
+ ## Modeling rules that follow from erasure
218
+
219
+ - Prefer **one subject per event stream** for person-level personal data. The
220
+ default subject is then correct everywhere and erasure is one call.
221
+ - A key redirected away from the event's own stream can carry a `[PII]` value
222
+ across a compliance subject boundary — analyzers **CHR0043** and **CHR0046**
223
+ (warnings) flag that shape. Treat the warning as a modeling question, not
224
+ noise: which person owns the resulting document?
225
+ - Bearer tokens, magic links, and signed URLs are secrets, not durable facts.
226
+ Store a keyed hash or an opaque reference. Chronicle has no attribute that
227
+ withholds a secret from the log.
228
+ - If a subject boundary cannot be made person-level without changing product
229
+ behavior, stop and surface the trade-off before implementing.
230
+
231
+ ## Verify
232
+
233
+ - Every value that identifies or describes a natural person carries `[PII]`;
234
+ business metadata does not.
235
+ - Rationale is written with `[ComplianceDetails]`, not with a `[PII]` argument.
236
+ - The subject is explicit wherever the person is not the event source, and
237
+ absent where the event source already is the person.
238
+ - No `[Key]`, `[Subject]`, or `[PII]` sits on an `EventSourceId<T>` value.
239
+ - No read model declares `_subject`, `__subject`, or `__subjects`.
240
+ - One stored read-model document holds one person's personal data.
241
+ - Erasure is executed per namespace, and an incomplete erasure is retried rather
242
+ than reported as done.
243
+ - Reads that must show personal data outside the kernel's own paths call
244
+ `Release`, and an unchanged instance is investigated rather than assumed.
245
+ - The build is clean with zero `CHR00xx` diagnostics outstanding, and the
246
+ relevant specifications pass against the verified package version.
@@ -0,0 +1,2 @@
1
+ Copyright (c) Cratis. All rights reserved.
2
+ Licensed under the MIT license. See LICENSE file in the project root for full license information.