@cratis/chronicle 0.2.0 → 0.2.2

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 (161) hide show
  1. package/EventStore.ts +8 -0
  2. package/IEventStore.ts +8 -0
  3. package/compliance/ComplianceContracts.ts +63 -0
  4. package/compliance/ComplianceMetadata.ts +19 -0
  5. package/compliance/ComplianceMetadataResolver.ts +51 -0
  6. package/compliance/ComplianceMetadataType.ts +36 -0
  7. package/compliance/index.ts +7 -0
  8. package/compliance/pii.ts +115 -0
  9. package/connection/ChronicleConnection.ts +19 -1
  10. package/connection/ChronicleServices.ts +6 -0
  11. package/dist/EventStore.d.ts +4 -0
  12. package/dist/EventStore.d.ts.map +1 -1
  13. package/dist/EventStore.js +6 -0
  14. package/dist/EventStore.js.map +1 -1
  15. package/dist/IEventStore.d.ts +6 -0
  16. package/dist/IEventStore.d.ts.map +1 -1
  17. package/dist/compliance/ComplianceContracts.d.ts +56 -0
  18. package/dist/compliance/ComplianceContracts.d.ts.map +1 -0
  19. package/dist/compliance/ComplianceContracts.js +25 -0
  20. package/dist/compliance/ComplianceContracts.js.map +1 -0
  21. package/dist/compliance/ComplianceMetadata.d.ts +15 -0
  22. package/dist/compliance/ComplianceMetadata.d.ts.map +1 -0
  23. package/dist/compliance/ComplianceMetadata.js +4 -0
  24. package/dist/compliance/ComplianceMetadata.js.map +1 -0
  25. package/dist/compliance/ComplianceMetadataResolver.d.ts +27 -0
  26. package/dist/compliance/ComplianceMetadataResolver.d.ts.map +1 -0
  27. package/dist/compliance/ComplianceMetadataResolver.js +45 -0
  28. package/dist/compliance/ComplianceMetadataResolver.js.map +1 -0
  29. package/dist/compliance/ComplianceMetadataType.d.ts +24 -0
  30. package/dist/compliance/ComplianceMetadataType.d.ts.map +1 -0
  31. package/dist/compliance/ComplianceMetadataType.js +32 -0
  32. package/dist/compliance/ComplianceMetadataType.js.map +1 -0
  33. package/dist/compliance/index.d.ts +5 -0
  34. package/dist/compliance/index.d.ts.map +1 -0
  35. package/dist/compliance/index.js +6 -0
  36. package/dist/compliance/index.js.map +1 -0
  37. package/dist/compliance/pii.d.ts +69 -0
  38. package/dist/compliance/pii.d.ts.map +1 -0
  39. package/dist/compliance/pii.js +106 -0
  40. package/dist/compliance/pii.js.map +1 -0
  41. package/dist/connection/ChronicleConnection.d.ts +3 -0
  42. package/dist/connection/ChronicleConnection.d.ts.map +1 -1
  43. package/dist/connection/ChronicleConnection.js +16 -2
  44. package/dist/connection/ChronicleConnection.js.map +1 -1
  45. package/dist/connection/ChronicleServices.d.ts +6 -1
  46. package/dist/connection/ChronicleServices.d.ts.map +1 -1
  47. package/dist/eventSequences/EventSequence.js +3 -3
  48. package/dist/eventSequences/EventSequence.js.map +1 -1
  49. package/dist/eventStoreSubscriptions/EventStoreSubscriptionBuilder.d.ts +20 -0
  50. package/dist/eventStoreSubscriptions/EventStoreSubscriptionBuilder.d.ts.map +1 -0
  51. package/dist/eventStoreSubscriptions/EventStoreSubscriptionBuilder.js +41 -0
  52. package/dist/eventStoreSubscriptions/EventStoreSubscriptionBuilder.js.map +1 -0
  53. package/dist/eventStoreSubscriptions/EventStoreSubscriptionDefinition.d.ts +12 -0
  54. package/dist/eventStoreSubscriptions/EventStoreSubscriptionDefinition.d.ts.map +1 -0
  55. package/dist/eventStoreSubscriptions/EventStoreSubscriptionDefinition.js +16 -0
  56. package/dist/eventStoreSubscriptions/EventStoreSubscriptionDefinition.js.map +1 -0
  57. package/dist/eventStoreSubscriptions/EventStoreSubscriptionId.d.ts +10 -0
  58. package/dist/eventStoreSubscriptions/EventStoreSubscriptionId.d.ts.map +1 -0
  59. package/dist/eventStoreSubscriptions/EventStoreSubscriptionId.js +16 -0
  60. package/dist/eventStoreSubscriptions/EventStoreSubscriptionId.js.map +1 -0
  61. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.d.ts +21 -0
  62. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.d.ts.map +1 -0
  63. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js +60 -0
  64. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js.map +1 -0
  65. package/dist/eventStoreSubscriptions/IEventStoreSubscriptionBuilder.d.ts +20 -0
  66. package/dist/eventStoreSubscriptions/IEventStoreSubscriptionBuilder.d.ts.map +1 -0
  67. package/dist/eventStoreSubscriptions/IEventStoreSubscriptionBuilder.js +4 -0
  68. package/dist/eventStoreSubscriptions/IEventStoreSubscriptionBuilder.js.map +1 -0
  69. package/dist/eventStoreSubscriptions/IEventStoreSubscriptions.d.ts +21 -0
  70. package/dist/eventStoreSubscriptions/IEventStoreSubscriptions.d.ts.map +1 -0
  71. package/dist/eventStoreSubscriptions/IEventStoreSubscriptions.js +4 -0
  72. package/dist/eventStoreSubscriptions/IEventStoreSubscriptions.js.map +1 -0
  73. package/dist/eventStoreSubscriptions/contracts.d.ts +71 -0
  74. package/dist/eventStoreSubscriptions/contracts.d.ts.map +1 -0
  75. package/dist/eventStoreSubscriptions/contracts.js +310 -0
  76. package/dist/eventStoreSubscriptions/contracts.js.map +1 -0
  77. package/dist/eventStoreSubscriptions/index.d.ts +7 -0
  78. package/dist/eventStoreSubscriptions/index.d.ts.map +1 -0
  79. package/dist/eventStoreSubscriptions/index.js +7 -0
  80. package/dist/eventStoreSubscriptions/index.js.map +1 -0
  81. package/dist/events/constraints/IUniqueConstraintBuilder.d.ts +3 -2
  82. package/dist/events/constraints/IUniqueConstraintBuilder.d.ts.map +1 -1
  83. package/dist/events/constraints/UniqueConstraintBuilder.d.ts +2 -8
  84. package/dist/events/constraints/UniqueConstraintBuilder.d.ts.map +1 -1
  85. package/dist/events/constraints/UniqueConstraintBuilder.js +4 -15
  86. package/dist/events/constraints/UniqueConstraintBuilder.js.map +1 -1
  87. package/dist/index.d.ts +5 -1
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +5 -1
  90. package/dist/index.js.map +1 -1
  91. package/dist/reactors/Reactors.js +1 -1
  92. package/dist/reactors/Reactors.js.map +1 -1
  93. package/dist/readModels/IMaterializedReadModels.d.ts +23 -0
  94. package/dist/readModels/IMaterializedReadModels.d.ts.map +1 -0
  95. package/dist/readModels/IMaterializedReadModels.js +4 -0
  96. package/dist/readModels/IMaterializedReadModels.js.map +1 -0
  97. package/dist/readModels/IReadModels.d.ts +75 -0
  98. package/dist/readModels/IReadModels.d.ts.map +1 -0
  99. package/dist/readModels/IReadModels.js +4 -0
  100. package/dist/readModels/IReadModels.js.map +1 -0
  101. package/dist/readModels/MaterializedReadModels.d.ts +25 -0
  102. package/dist/readModels/MaterializedReadModels.d.ts.map +1 -0
  103. package/dist/readModels/MaterializedReadModels.js +125 -0
  104. package/dist/readModels/MaterializedReadModels.js.map +1 -0
  105. package/dist/readModels/ReadModelChangeset.d.ts +14 -0
  106. package/dist/readModels/ReadModelChangeset.d.ts.map +1 -0
  107. package/dist/readModels/ReadModelChangeset.js +4 -0
  108. package/dist/readModels/ReadModelChangeset.js.map +1 -0
  109. package/dist/readModels/ReadModelSnapshot.d.ts +15 -0
  110. package/dist/readModels/ReadModelSnapshot.d.ts.map +1 -0
  111. package/dist/readModels/ReadModelSnapshot.js +4 -0
  112. package/dist/readModels/ReadModelSnapshot.js.map +1 -0
  113. package/dist/readModels/ReadModels.d.ts +43 -0
  114. package/dist/readModels/ReadModels.d.ts.map +1 -0
  115. package/dist/readModels/ReadModels.js +280 -0
  116. package/dist/readModels/ReadModels.js.map +1 -0
  117. package/dist/readModels/index.d.ts +6 -0
  118. package/dist/readModels/index.d.ts.map +1 -1
  119. package/dist/readModels/index.js +2 -0
  120. package/dist/readModels/index.js.map +1 -1
  121. package/dist/readModels/readModel.d.ts.map +1 -1
  122. package/dist/readModels/readModel.js +0 -20
  123. package/dist/readModels/readModel.js.map +1 -1
  124. package/dist/reducers/Reducers.d.ts +1 -0
  125. package/dist/reducers/Reducers.d.ts.map +1 -1
  126. package/dist/reducers/Reducers.js +10 -3
  127. package/dist/reducers/Reducers.js.map +1 -1
  128. package/dist/schemas/JsonSchemaGenerator.d.ts +9 -0
  129. package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
  130. package/dist/schemas/JsonSchemaGenerator.js +53 -1
  131. package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
  132. package/dist/tsconfig.tsbuildinfo +1 -1
  133. package/dist/types/TypeIntrospector.d.ts +11 -1
  134. package/dist/types/TypeIntrospector.d.ts.map +1 -1
  135. package/dist/types/TypeIntrospector.js +29 -3
  136. package/dist/types/TypeIntrospector.js.map +1 -1
  137. package/eventSequences/EventSequence.ts +3 -3
  138. package/eventStoreSubscriptions/EventStoreSubscriptionBuilder.ts +52 -0
  139. package/eventStoreSubscriptions/EventStoreSubscriptionDefinition.ts +16 -0
  140. package/eventStoreSubscriptions/EventStoreSubscriptionId.ts +14 -0
  141. package/eventStoreSubscriptions/EventStoreSubscriptions.ts +74 -0
  142. package/eventStoreSubscriptions/IEventStoreSubscriptionBuilder.ts +24 -0
  143. package/eventStoreSubscriptions/IEventStoreSubscriptions.ts +29 -0
  144. package/eventStoreSubscriptions/contracts.ts +372 -0
  145. package/eventStoreSubscriptions/index.ts +9 -0
  146. package/events/constraints/IUniqueConstraintBuilder.ts +3 -2
  147. package/events/constraints/UniqueConstraintBuilder.ts +5 -17
  148. package/index.ts +6 -0
  149. package/package.json +17 -7
  150. package/reactors/Reactors.ts +1 -1
  151. package/readModels/IMaterializedReadModels.ts +27 -0
  152. package/readModels/IReadModels.ts +87 -0
  153. package/readModels/MaterializedReadModels.ts +151 -0
  154. package/readModels/ReadModelChangeset.ts +19 -0
  155. package/readModels/ReadModelSnapshot.ts +21 -0
  156. package/readModels/ReadModels.ts +348 -0
  157. package/readModels/index.ts +6 -0
  158. package/readModels/readModel.ts +0 -22
  159. package/reducers/Reducers.ts +12 -3
  160. package/schemas/JsonSchemaGenerator.ts +61 -1
  161. package/types/TypeIntrospector.ts +32 -3
@@ -0,0 +1,348 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import type {
5
+ AppendedEvent,
6
+ ReadModelObserverType
7
+ } from '@cratis/chronicle.contracts';
8
+ import {
9
+ ReadModelObserverType as ContractReadModelObserverType
10
+ } from '@cratis/chronicle.contracts';
11
+ import type { Constructor } from '@cratis/fundamentals';
12
+ import { JsonSerializer } from '@cratis/fundamentals';
13
+ import { IClientArtifactsProvider } from '../artifacts';
14
+ import { toContractsGuid } from '../connection/Guid';
15
+ import { ChronicleConnection } from '../connection';
16
+ import { EventSequenceId } from '../eventSequences/EventSequenceId';
17
+ import { getProjectionMetadata } from '../projections/declarative/projection';
18
+ import { hasFromEventMetadata } from '../projections/modelBound/fromEvent';
19
+ import { getReducerMetadata } from '../reducers/reducer';
20
+ import { JsonSchemaGenerator } from '../schemas';
21
+ import { WellKnownSinks } from '../sinks';
22
+ import { getReadModelMetadata } from './readModel';
23
+ import type { IMaterializedReadModels } from './IMaterializedReadModels';
24
+ import { MaterializedReadModels } from './MaterializedReadModels';
25
+ import type { IReadModels } from './IReadModels';
26
+ import type { ReadModelChangeset } from './ReadModelChangeset';
27
+ import type { ReadModelSnapshot } from './ReadModelSnapshot';
28
+
29
+ const unlimitedEventCount = BigInt('18446744073709551615');
30
+
31
+ interface ResolvedReadModel {
32
+ readonly type: Constructor;
33
+ readonly identifier: string;
34
+ readonly eventSequenceId: string;
35
+ readonly observerType: ReadModelObserverType;
36
+ readonly observerIdentifier: string;
37
+ readonly schema: string;
38
+ }
39
+
40
+ /**
41
+ * Implements {@link IReadModels} by working with the Chronicle read-model gRPC service.
42
+ */
43
+ export class ReadModels implements IReadModels {
44
+ readonly materialized: IMaterializedReadModels;
45
+
46
+ constructor(
47
+ private readonly _eventStore: string,
48
+ private readonly _namespace: string,
49
+ private readonly _connection: ChronicleConnection,
50
+ private readonly _clientArtifacts: IClientArtifactsProvider
51
+ ) {
52
+ this.materialized = new MaterializedReadModels(_eventStore, _namespace, _connection);
53
+ }
54
+
55
+ /** @inheritdoc */
56
+ async register<TReadModel>(readModelType?: Constructor<TReadModel>): Promise<void> {
57
+ const readModels = this.resolveReadModels(readModelType);
58
+ if (readModels.length === 0) {
59
+ return;
60
+ }
61
+
62
+ await this._connection.readModels.registerMany({
63
+ EventStore: this._eventStore,
64
+ Owner: 1,
65
+ Source: 1,
66
+ ReadModels: readModels.map(readModel => this.toDefinition(readModel))
67
+ });
68
+ }
69
+
70
+ /** @inheritdoc */
71
+ async getInstanceById<TReadModel>(readModelType: Constructor<TReadModel>, key: string, sessionId?: string): Promise<TReadModel> {
72
+ const readModel = this.resolveReadModel(readModelType);
73
+ const response = await this._connection.readModels.getInstanceByKey({
74
+ EventStore: this._eventStore,
75
+ Namespace: this._namespace,
76
+ ReadModelIdentifier: readModel.identifier,
77
+ EventSequenceId: readModel.eventSequenceId,
78
+ ReadModelKey: key,
79
+ SessionId: sessionId ?? ''
80
+ });
81
+
82
+ const instance = this.deserializeReadModel(readModelType, response.ReadModel);
83
+
84
+ if (readModel.observerType === ContractReadModelObserverType.Reducer && this.schemaHasComplianceMetadata(readModel.schema)) {
85
+ return this.release(readModelType, instance);
86
+ }
87
+
88
+ return instance;
89
+ }
90
+
91
+ /** @inheritdoc */
92
+ async getInstances<TReadModel>(readModelType: Constructor<TReadModel>, eventCount: bigint = unlimitedEventCount): Promise<TReadModel[]> {
93
+ const readModel = this.resolveReadModel(readModelType);
94
+ const response = await this._connection.readModels.getAllInstances({
95
+ EventStore: this._eventStore,
96
+ Namespace: this._namespace,
97
+ ReadModelIdentifier: readModel.identifier,
98
+ EventSequenceId: readModel.eventSequenceId,
99
+ EventCount: eventCount
100
+ });
101
+
102
+ const instances = response.Instances.map(instance => this.deserializeReadModel(readModelType, instance));
103
+
104
+ if (readModel.observerType === ContractReadModelObserverType.Reducer && this.schemaHasComplianceMetadata(readModel.schema)) {
105
+ return this.releaseMany(readModelType, instances);
106
+ }
107
+
108
+ return instances;
109
+ }
110
+
111
+ /** @inheritdoc */
112
+ async getSnapshotsById<TReadModel>(readModelType: Constructor<TReadModel>, key: string): Promise<ReadModelSnapshot<TReadModel>[]> {
113
+ const readModel = this.resolveReadModel(readModelType);
114
+ const response = await this._connection.readModels.getSnapshotsByKey({
115
+ EventStore: this._eventStore,
116
+ Namespace: this._namespace,
117
+ ReadModelIdentifier: readModel.identifier,
118
+ EventSequenceId: readModel.eventSequenceId,
119
+ ReadModelKey: key
120
+ });
121
+
122
+ const snapshots: ReadModelSnapshot<TReadModel>[] = response.Snapshots.map(snapshot => ({
123
+ readModel: this.deserializeReadModel(readModelType, snapshot.ReadModel),
124
+ events: (snapshot.Events ?? []) as AppendedEvent[],
125
+ occurred: snapshot.Occurred?.Value ? new Date(snapshot.Occurred.Value) : undefined,
126
+ correlationId: snapshot.CorrelationId
127
+ }));
128
+
129
+ if (readModel.observerType === ContractReadModelObserverType.Reducer && this.schemaHasComplianceMetadata(readModel.schema)) {
130
+ return this.releaseSnapshotInstances(readModelType, snapshots);
131
+ }
132
+
133
+ return snapshots;
134
+ }
135
+
136
+ /** @inheritdoc */
137
+ async *watch<TReadModel>(readModelType: Constructor<TReadModel>): AsyncIterable<ReadModelChangeset<TReadModel>> {
138
+ const readModel = this.resolveReadModel(readModelType);
139
+
140
+ for await (const changeset of this._connection.readModels.watch({
141
+ EventStore: this._eventStore,
142
+ Namespace: this._namespace,
143
+ ReadModelIdentifier: readModel.identifier,
144
+ EventSequenceId: readModel.eventSequenceId
145
+ })) {
146
+ yield {
147
+ namespace: changeset.Namespace,
148
+ key: changeset.ModelKey,
149
+ readModel: this.deserializeReadModel(readModelType, changeset.ReadModel),
150
+ removed: changeset.Removed
151
+ };
152
+ }
153
+ }
154
+
155
+ /** @inheritdoc */
156
+ async dehydrateSession<TReadModel>(sessionId: string, readModelType: Constructor<TReadModel>, key: string): Promise<void> {
157
+ const readModel = this.resolveReadModel(readModelType);
158
+ await this._connection.readModels.dehydrateSession({
159
+ EventStore: this._eventStore,
160
+ Namespace: this._namespace,
161
+ ReadModelIdentifier: readModel.identifier,
162
+ EventSequenceId: readModel.eventSequenceId,
163
+ ReadModelKey: key,
164
+ SessionId: sessionId
165
+ });
166
+ }
167
+
168
+ /** @inheritdoc */
169
+ async release<TReadModel>(readModelType: Constructor<TReadModel>, instance: TReadModel): Promise<TReadModel> {
170
+ const readModel = this.resolveReadModel(readModelType);
171
+ const schema = this.getReadModelSchema(readModelType, readModel.identifier);
172
+ const payload = JsonSerializer.serialize(instance);
173
+ const subject = this.extractSubject(instance);
174
+
175
+ const response = await this._connection.compliance.release({
176
+ EventStore: this._eventStore,
177
+ Namespace: this._namespace,
178
+ Subject: subject,
179
+ Schema: schema,
180
+ Payload: payload
181
+ });
182
+
183
+ if (response.HasError) {
184
+ throw new Error(`Failed to release PII: ${response.Error}`);
185
+ }
186
+
187
+ return this.deserializeReadModel(readModelType, response.Payload);
188
+ }
189
+
190
+ /** @inheritdoc */
191
+ async releaseMany<TReadModel>(readModelType: Constructor<TReadModel>, instances: TReadModel[]): Promise<TReadModel[]> {
192
+ const releasePromises = instances.map(instance => this.release(readModelType, instance));
193
+ return Promise.all(releasePromises);
194
+ }
195
+
196
+ private async releaseSnapshotInstances<TReadModel>(readModelType: Constructor<TReadModel>, snapshots: ReadModelSnapshot<TReadModel>[]): Promise<ReadModelSnapshot<TReadModel>[]> {
197
+ return Promise.all(
198
+ snapshots.map(async snapshot => ({
199
+ ...snapshot,
200
+ readModel: await this.release(readModelType, snapshot.readModel)
201
+ }))
202
+ );
203
+ }
204
+
205
+ private resolveReadModels<TReadModel>(readModelType?: Constructor<TReadModel>): ResolvedReadModel[] {
206
+ const resolved = new Map<string, ResolvedReadModel>();
207
+
208
+ for (const projectionType of this._clientArtifacts.projections) {
209
+ const metadata = getProjectionMetadata(projectionType);
210
+ if (!metadata?.readModelType) {
211
+ continue;
212
+ }
213
+
214
+ if (readModelType && metadata.readModelType !== readModelType) {
215
+ continue;
216
+ }
217
+
218
+ const readModelMetadata = getReadModelMetadata(metadata.readModelType);
219
+ const identifier = readModelMetadata?.id.value ?? metadata.readModelType.name;
220
+ resolved.set(identifier, {
221
+ type: metadata.readModelType,
222
+ identifier,
223
+ eventSequenceId: metadata.eventSequenceId ?? EventSequenceId.eventLog.value,
224
+ observerType: ContractReadModelObserverType.Projection,
225
+ observerIdentifier: metadata.id.value,
226
+ schema: this.getReadModelSchema(metadata.readModelType, identifier)
227
+ });
228
+ }
229
+
230
+ for (const modelBoundType of this._clientArtifacts.readModels) {
231
+ if (!hasFromEventMetadata(modelBoundType)) {
232
+ continue;
233
+ }
234
+
235
+ if (readModelType && modelBoundType !== readModelType) {
236
+ continue;
237
+ }
238
+
239
+ const metadata = getReadModelMetadata(modelBoundType);
240
+ if (!metadata) {
241
+ continue;
242
+ }
243
+
244
+ resolved.set(metadata.id.value, {
245
+ type: modelBoundType,
246
+ identifier: metadata.id.value,
247
+ eventSequenceId: EventSequenceId.eventLog.value,
248
+ observerType: ContractReadModelObserverType.Projection,
249
+ observerIdentifier: metadata.id.value,
250
+ schema: JSON.stringify(metadata.schema)
251
+ });
252
+ }
253
+
254
+ for (const reducerType of this._clientArtifacts.reducers) {
255
+ const metadata = getReducerMetadata(reducerType);
256
+ if (!metadata?.readModel) {
257
+ continue;
258
+ }
259
+
260
+ if (readModelType && metadata.readModel !== readModelType) {
261
+ continue;
262
+ }
263
+
264
+ const readModelMetadata = getReadModelMetadata(metadata.readModel);
265
+ const identifier = readModelMetadata?.id.value ?? metadata.readModel.name;
266
+ resolved.set(identifier, {
267
+ type: metadata.readModel,
268
+ identifier,
269
+ eventSequenceId: metadata.eventSequenceId ?? EventSequenceId.eventLog.value,
270
+ observerType: ContractReadModelObserverType.Reducer,
271
+ observerIdentifier: metadata.id.value,
272
+ schema: this.getReadModelSchema(metadata.readModel, identifier)
273
+ });
274
+ }
275
+
276
+ return Array.from(resolved.values());
277
+ }
278
+
279
+ private resolveReadModel<TReadModel>(readModelType: Constructor<TReadModel>): ResolvedReadModel {
280
+ const [resolved] = this.resolveReadModels(readModelType);
281
+ if (!resolved) {
282
+ throw new Error(`Unknown read model '${readModelType.name}'. Make sure it is decorated and discoverable.`);
283
+ }
284
+
285
+ return resolved;
286
+ }
287
+
288
+ private toDefinition(readModel: ResolvedReadModel) {
289
+ return {
290
+ Type: {
291
+ Identifier: readModel.identifier,
292
+ Generation: 1
293
+ },
294
+ ContainerName: readModel.identifier,
295
+ DisplayName: readModel.identifier,
296
+ Sink: {
297
+ ConfigurationId: toContractsGuid(WellKnownSinks.Null),
298
+ TypeId: toContractsGuid(WellKnownSinks.MongoDB)
299
+ },
300
+ Schema: readModel.schema,
301
+ Indexes: [],
302
+ ObserverType: readModel.observerType,
303
+ ObserverIdentifier: readModel.observerIdentifier,
304
+ Owner: 1,
305
+ Source: 1
306
+ };
307
+ }
308
+
309
+ private getReadModelSchema(readModelType: Constructor, identifier: string): string {
310
+ const metadata = getReadModelMetadata(readModelType);
311
+ if (metadata) {
312
+ return JSON.stringify(metadata.schema);
313
+ }
314
+
315
+ return JSON.stringify(JsonSchemaGenerator.generate(readModelType) ?? JsonSchemaGenerator.createEmptySchema(identifier));
316
+ }
317
+
318
+ private deserializeReadModel<TReadModel>(readModelType: Constructor<TReadModel>, json: string): TReadModel {
319
+ if (!json) {
320
+ return Object.create(readModelType.prototype) as TReadModel;
321
+ }
322
+ return JsonSerializer.deserialize(readModelType as Constructor<object>, json) as TReadModel;
323
+ }
324
+
325
+ private schemaHasComplianceMetadata(schema: string): boolean {
326
+ try {
327
+ const parsed = JSON.parse(schema) as Record<string, unknown>;
328
+ const properties = parsed.properties as Record<string, Record<string, unknown>> | undefined;
329
+ if (!properties) {
330
+ return false;
331
+ }
332
+ return Object.values(properties).some(
333
+ property => Array.isArray(property.compliance) && (property.compliance as unknown[]).length > 0
334
+ );
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
339
+
340
+ private extractSubject<TReadModel>(instance: TReadModel): string {
341
+ // By convention, use the 'id' property as the subject
342
+ const anyInstance = instance as any;
343
+ if (anyInstance.id !== undefined && anyInstance.id !== null) {
344
+ return String(anyInstance.id);
345
+ }
346
+ throw new Error('Read model instance must have an "id" property to serve as the subject for PII release');
347
+ }
348
+ }
@@ -2,5 +2,11 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  export { ReadModelId } from './ReadModelId';
5
+ export type { IReadModels } from './IReadModels';
6
+ export type { IMaterializedReadModels } from './IMaterializedReadModels';
7
+ export { ReadModels } from './ReadModels';
8
+ export { MaterializedReadModels } from './MaterializedReadModels';
9
+ export type { ReadModelChangeset } from './ReadModelChangeset';
10
+ export type { ReadModelSnapshot } from './ReadModelSnapshot';
5
11
  export { readModel, getReadModelMetadata, isReadModel } from './readModel';
6
12
  export type { ReadModelMetadata } from './readModel';
@@ -33,7 +33,6 @@ export function readModel(id: string = ''): ClassDecorator {
33
33
  return (target: object) => {
34
34
  const constructor = target as Function;
35
35
  const readModelId = new ReadModelId(id || constructor.name);
36
- trackInstanceProperties(constructor);
37
36
  const members = TypeIntrospector.getMembers(constructor);
38
37
  const metadata: ReadModelMetadata = {
39
38
  id: readModelId,
@@ -49,27 +48,6 @@ export function readModel(id: string = ''): ClassDecorator {
49
48
  };
50
49
  }
51
50
 
52
- function trackInstanceProperties(constructor: Function): void {
53
- const instance = tryCreateInstance(constructor);
54
- if (!instance || typeof instance !== 'object') {
55
- return;
56
- }
57
-
58
- for (const propertyName of Object.keys(instance as Record<string, unknown>)) {
59
- TypeIntrospector.trackProperty(constructor, propertyName);
60
- }
61
- }
62
-
63
- function tryCreateInstance(constructor: Function): unknown {
64
- try {
65
- const parameterCount = constructor.length;
66
- const parameters = Array.from({ length: parameterCount }, () => undefined);
67
- return Reflect.construct(constructor as new (...args: unknown[]) => unknown, parameters);
68
- } catch {
69
- return undefined;
70
- }
71
- }
72
-
73
51
  /**
74
52
  * Gets the {@link ReadModelMetadata} associated with a class decorated with {@link readModel}.
75
53
  * @param target - The class constructor to retrieve metadata for.
@@ -159,7 +159,7 @@ export class Reducers implements IReducers {
159
159
  }
160
160
 
161
161
  const readModels = Array.from(this._reducers.entries()).map(([id, reducerType]) => {
162
- const readModelName = (reducerType as Function).name;
162
+ const readModelName = this.getReducerReadModelIdentifier(reducerType);
163
163
  return {
164
164
  Type: {
165
165
  Identifier: readModelName,
@@ -212,7 +212,7 @@ export class Reducers implements IReducers {
212
212
  const metadata = getReducerMetadata(reducerType)!;
213
213
  const eventSequenceId = metadata.eventSequenceId ?? EventSequenceId.eventLog.value;
214
214
  const eventTypes = this.getEventTypesFor(reducerType);
215
- const readModelName = (reducerType as Function).name;
215
+ const readModelName = this.getReducerReadModelIdentifier(reducerType);
216
216
 
217
217
  this._logger.info('Starting reducer observation', {
218
218
  reducerId: id,
@@ -227,6 +227,15 @@ export class Reducers implements IReducers {
227
227
  });
228
228
  }
229
229
 
230
+ private getReducerReadModelIdentifier(reducerType: Constructor): string {
231
+ const metadata = getReducerMetadata(reducerType);
232
+ if (metadata?.readModel) {
233
+ return getReadModelMetadata(metadata.readModel)?.id.value ?? metadata.readModel.name;
234
+ }
235
+
236
+ return (reducerType as Function).name;
237
+ }
238
+
230
239
  private async observeReducer(
231
240
  id: string,
232
241
  reducerType: Constructor,
@@ -309,7 +318,7 @@ export class Reducers implements IReducers {
309
318
  this._logger.info('Invoking reducer handler', {
310
319
  reducerId: id,
311
320
  method: entry.methodName,
312
- sequenceNumber: event.Context!.SequenceNumber,
321
+ sequenceNumber: event.Context!.SequenceNumber.toString(),
313
322
  eventTypeId,
314
323
  hasState: currentState !== undefined
315
324
  });
@@ -2,9 +2,10 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import 'reflect-metadata';
5
- import { Guid } from '@cratis/fundamentals';
5
+ import { ConceptAs, Guid } from '@cratis/fundamentals';
6
6
  import { JsonSchema } from './JsonSchema';
7
7
  import { TypeIntrospector } from '../types';
8
+ import { ComplianceMetadataResolver } from '../compliance/ComplianceMetadataResolver';
8
9
 
9
10
  /**
10
11
  * Generates JSON schemas for class constructors using reflection metadata.
@@ -44,12 +45,15 @@ export class JsonSchemaGenerator {
44
45
  static generate(target: Function, members?: ReadonlyMap<string, Function | undefined>): JsonSchema {
45
46
  const membersToUse = members ?? TypeIntrospector.getMembers(target);
46
47
  const schemaProperties: Record<string, JsonSchema> = {};
48
+ const prototype = target.prototype;
47
49
 
48
50
  for (const [memberName, memberType] of membersToUse.entries()) {
49
51
  const propertySchema = this.mapRuntimeTypeToSchema(memberType);
50
52
  // Only include properties whose type was resolved. An empty schema ({}) means
51
53
  // the runtime type was unavailable (e.g. esbuild/tsx omits design:paramtypes).
52
54
  if (Object.keys(propertySchema).length > 0) {
55
+ // Add compliance metadata to property schema if present (both property and type-level)
56
+ this.addComplianceMetadata(propertySchema, prototype, memberName, memberType);
53
57
  schemaProperties[memberName] = propertySchema;
54
58
  }
55
59
  }
@@ -94,6 +98,16 @@ export class JsonSchemaGenerator {
94
98
  return {};
95
99
  }
96
100
 
101
+ // A ConceptAs<T> serializes as its underlying primitive value, so the schema must
102
+ // describe that primitive rather than the wrapper object. The generic argument T is
103
+ // not available at runtime, so resolve it from the 'value' property's design:type
104
+ // when present (ts-node/webpack with emitDecoratorMetadata) and fall back to string
105
+ // when it is not (esbuild/tsx) — string-backed concepts are by far the common case.
106
+ if (this.isConceptAs(runtimeType)) {
107
+ const valueType = Reflect.getMetadata('design:type', runtimeType.prototype, 'value') as Function | undefined;
108
+ return this.mapRuntimeTypeToSchema(valueType ?? String);
109
+ }
110
+
97
111
  if (runtimeType !== Object) {
98
112
  return this.generate(runtimeType);
99
113
  }
@@ -101,6 +115,19 @@ export class JsonSchemaGenerator {
101
115
  return { type: 'object' };
102
116
  }
103
117
 
118
+ private static isConceptAs(runtimeType: Function): boolean {
119
+ let current: Function | null = runtimeType;
120
+ while (current && current !== Function.prototype) {
121
+ if (current === ConceptAs) {
122
+ return true;
123
+ }
124
+
125
+ current = Object.getPrototypeOf(current) as Function | null;
126
+ }
127
+
128
+ return false;
129
+ }
130
+
104
131
  private static getKnownTypeFormat(runtimeType: Function | undefined): JsonSchema | undefined {
105
132
  if (!runtimeType) {
106
133
  return undefined;
@@ -121,4 +148,37 @@ export class JsonSchemaGenerator {
121
148
  const normalized = format.toLowerCase();
122
149
  return this._formatAliases.get(normalized) ?? normalized;
123
150
  }
151
+
152
+ /**
153
+ * Adds compliance metadata to a property schema if the property has compliance decorators.
154
+ * Also checks if the property's type itself is marked as PII (e.g., ConceptAs types).
155
+ * @param schema - The property schema to add compliance metadata to.
156
+ * @param target - The class prototype.
157
+ * @param propertyKey - The property name.
158
+ */
159
+ private static addComplianceMetadata(schema: JsonSchema, target: object, propertyKey: string, propertyType?: Function): void {
160
+ const complianceArray: Array<{ metadataType: string; details: string }> = [];
161
+
162
+ // Check for property-level compliance decorators
163
+ if (ComplianceMetadataResolver.hasMetadataFor(target, propertyKey)) {
164
+ const propertyMetadata = ComplianceMetadataResolver.getMetadataFor(target, propertyKey);
165
+ complianceArray.push(...propertyMetadata.map(metadata => ({
166
+ metadataType: metadata.metadataType.value.toString(),
167
+ details: metadata.details
168
+ })));
169
+ }
170
+
171
+ // Check for type-level compliance decorators (e.g., @pii on ConceptAs)
172
+ if (propertyType) {
173
+ const typeMetadata = ComplianceMetadataResolver.getMetadataForType(propertyType);
174
+ complianceArray.push(...typeMetadata.map(metadata => ({
175
+ metadataType: metadata.metadataType.value.toString(),
176
+ details: metadata.details
177
+ })));
178
+ }
179
+
180
+ if (complianceArray.length > 0) {
181
+ (schema as Record<string, unknown>).compliance = complianceArray;
182
+ }
183
+ }
124
184
  }
@@ -2,6 +2,7 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import 'reflect-metadata';
5
+ import { Constructor, Fields } from '@cratis/fundamentals';
5
6
 
6
7
  /** Metadata key for tracked schema properties on a target type. */
7
8
  const TRACKED_PROPERTIES_METADATA_KEY = 'chronicle:typeIntrospection:properties';
@@ -35,16 +36,35 @@ export class TypeIntrospector {
35
36
 
36
37
  /**
37
38
  * Gets members and their runtime types for a class.
38
- * Members are discovered from tracked properties and constructor parameters.
39
+ * Members are discovered from explicit `@field` decorators, tracked properties, the
40
+ * properties present on a default-constructed instance, and constructor parameters.
41
+ *
42
+ * `@field` declarations are the authoritative source: they carry the exact declared
43
+ * type (including `ConceptAs` types) regardless of whether the property has an
44
+ * initializer, so event types — whose properties are typically declared with definite
45
+ * assignment (`prop!: T`) and no default value — resolve their schema just like read
46
+ * models. Default-value and `design:type` resolution remain as fallbacks for plain
47
+ * classes that don't use `@field`, and keep working even under esbuild/tsx, which does
48
+ * not emit decorator metadata ('design:type'/'design:paramtypes').
39
49
  * @param target - The class constructor to inspect.
40
50
  * @returns A map of member name to runtime type.
41
51
  */
42
52
  static getMembers(target: Function): Map<string, Function | undefined> {
43
53
  const members = new Map<string, Function | undefined>();
44
54
  const defaultValues = this.tryGetDefaultPropertyValues(target);
55
+ const fieldTypes = this.getFieldTypes(target);
45
56
 
46
- for (const property of this.getTrackedProperties(target)) {
47
- let runtimeType = Reflect.getMetadata('design:type', target.prototype, property) as Function | undefined;
57
+ const propertyNames = new Set<string>([
58
+ ...this.getTrackedProperties(target),
59
+ ...fieldTypes.keys(),
60
+ ...Object.keys(defaultValues)
61
+ ]);
62
+
63
+ for (const property of propertyNames) {
64
+ let runtimeType = fieldTypes.get(property);
65
+ if (!runtimeType || runtimeType === Object) {
66
+ runtimeType = Reflect.getMetadata('design:type', target.prototype, property) as Function | undefined;
67
+ }
48
68
  if (!runtimeType || runtimeType === Object) {
49
69
  runtimeType = this.getRuntimeTypeFromValue(defaultValues[property]);
50
70
  }
@@ -67,6 +87,15 @@ export class TypeIntrospector {
67
87
  return members;
68
88
  }
69
89
 
90
+ private static getFieldTypes(target: Function): Map<string, Function | undefined> {
91
+ const fieldTypes = new Map<string, Function | undefined>();
92
+ for (const field of Fields.getFieldsForType(target as Constructor)) {
93
+ fieldTypes.set(field.name, field.type);
94
+ }
95
+
96
+ return fieldTypes;
97
+ }
98
+
70
99
  private static tryGetDefaultPropertyValues(target: Function): Record<string, unknown> {
71
100
  try {
72
101
  const parameters = Array.from({ length: target.length }, () => undefined);