@cratis/chronicle 3.1.1 → 5.0.0

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 (223) hide show
  1. package/ChronicleClient.ts +3 -3
  2. package/EventStore.ts +4 -4
  3. package/compliance/ComplianceContracts.ts +59 -0
  4. package/compliance/IPIIManager.ts +12 -0
  5. package/compliance/PIIManager.spec.ts +50 -0
  6. package/compliance/PIIManager.ts +9 -0
  7. package/compliance/PIINotSupportedOnEventSourceId.ts +21 -0
  8. package/compliance/index.ts +2 -0
  9. package/compliance/pii.spec.ts +37 -0
  10. package/compliance/pii.ts +16 -1
  11. package/compliance/subject.spec.ts +45 -0
  12. package/compliance/subject.ts +64 -0
  13. package/connection/ChronicleConnection.ts +6 -0
  14. package/connection/ChronicleServices.ts +2 -0
  15. package/connection/callResults.ts +23 -0
  16. package/dist/ChronicleClient.js +3 -3
  17. package/dist/ChronicleClient.js.map +1 -1
  18. package/dist/EventStore.js +4 -4
  19. package/dist/EventStore.js.map +1 -1
  20. package/dist/compliance/ComplianceContracts.d.ts +31 -0
  21. package/dist/compliance/ComplianceContracts.d.ts.map +1 -1
  22. package/dist/compliance/ComplianceContracts.js +52 -0
  23. package/dist/compliance/ComplianceContracts.js.map +1 -1
  24. package/dist/compliance/IPIIManager.d.ts +11 -0
  25. package/dist/compliance/IPIIManager.d.ts.map +1 -1
  26. package/dist/compliance/PIIManager.d.ts +2 -0
  27. package/dist/compliance/PIIManager.d.ts.map +1 -1
  28. package/dist/compliance/PIIManager.js +8 -0
  29. package/dist/compliance/PIIManager.js.map +1 -1
  30. package/dist/compliance/PIINotSupportedOnEventSourceId.d.ts +11 -0
  31. package/dist/compliance/PIINotSupportedOnEventSourceId.d.ts.map +1 -0
  32. package/dist/compliance/PIINotSupportedOnEventSourceId.js +19 -0
  33. package/dist/compliance/PIINotSupportedOnEventSourceId.js.map +1 -0
  34. package/dist/compliance/index.d.ts +2 -0
  35. package/dist/compliance/index.d.ts.map +1 -1
  36. package/dist/compliance/index.js +2 -0
  37. package/dist/compliance/index.js.map +1 -1
  38. package/dist/compliance/pii.d.ts.map +1 -1
  39. package/dist/compliance/pii.js +13 -1
  40. package/dist/compliance/pii.js.map +1 -1
  41. package/dist/compliance/subject.d.ts +39 -0
  42. package/dist/compliance/subject.d.ts.map +1 -0
  43. package/dist/compliance/subject.js +58 -0
  44. package/dist/compliance/subject.js.map +1 -0
  45. package/dist/connection/ChronicleConnection.d.ts +1 -0
  46. package/dist/connection/ChronicleConnection.d.ts.map +1 -1
  47. package/dist/connection/ChronicleConnection.js +5 -1
  48. package/dist/connection/ChronicleConnection.js.map +1 -1
  49. package/dist/connection/ChronicleServices.d.ts +2 -1
  50. package/dist/connection/ChronicleServices.d.ts.map +1 -1
  51. package/dist/connection/callResults.d.ts +13 -0
  52. package/dist/connection/callResults.d.ts.map +1 -1
  53. package/dist/connection/callResults.js +14 -0
  54. package/dist/connection/callResults.js.map +1 -1
  55. package/dist/eventSequences/AppendOptions.d.ts +8 -0
  56. package/dist/eventSequences/AppendOptions.d.ts.map +1 -1
  57. package/dist/eventSequences/EventForEventSourceId.d.ts +7 -0
  58. package/dist/eventSequences/EventForEventSourceId.d.ts.map +1 -1
  59. package/dist/eventSequences/EventSequence.d.ts +2 -2
  60. package/dist/eventSequences/EventSequence.d.ts.map +1 -1
  61. package/dist/eventSequences/EventSequence.js +56 -63
  62. package/dist/eventSequences/EventSequence.js.map +1 -1
  63. package/dist/events/EventContext.d.ts +3 -0
  64. package/dist/events/EventContext.d.ts.map +1 -1
  65. package/dist/events/EventTypes.d.ts.map +1 -1
  66. package/dist/events/EventTypes.js +3 -2
  67. package/dist/events/EventTypes.js.map +1 -1
  68. package/dist/events/Tag.d.ts +10 -0
  69. package/dist/events/Tag.d.ts.map +1 -0
  70. package/dist/events/Tag.js +16 -0
  71. package/dist/events/Tag.js.map +1 -0
  72. package/dist/events/filterEventsByTagDecorator.d.ts +35 -0
  73. package/dist/events/filterEventsByTagDecorator.d.ts.map +1 -0
  74. package/dist/events/filterEventsByTagDecorator.js +48 -0
  75. package/dist/events/filterEventsByTagDecorator.js.map +1 -0
  76. package/dist/events/index.d.ts +4 -0
  77. package/dist/events/index.d.ts.map +1 -1
  78. package/dist/events/index.js +4 -0
  79. package/dist/events/index.js.map +1 -1
  80. package/dist/events/mergeTags.d.ts +12 -0
  81. package/dist/events/mergeTags.d.ts.map +1 -0
  82. package/dist/events/mergeTags.js +26 -0
  83. package/dist/events/mergeTags.js.map +1 -0
  84. package/dist/events/tagDecorator.d.ts +38 -0
  85. package/dist/events/tagDecorator.d.ts.map +1 -0
  86. package/dist/events/tagDecorator.js +54 -0
  87. package/dist/events/tagDecorator.js.map +1 -0
  88. package/dist/jobs/Jobs.js +3 -3
  89. package/dist/jobs/Jobs.js.map +1 -1
  90. package/dist/observation/ObserverRunningState.d.ts +9 -15
  91. package/dist/observation/ObserverRunningState.d.ts.map +1 -1
  92. package/dist/observation/ObserverRunningState.js +8 -14
  93. package/dist/observation/ObserverRunningState.js.map +1 -1
  94. package/dist/observation/index.d.ts +1 -0
  95. package/dist/observation/index.d.ts.map +1 -1
  96. package/dist/observation/index.js +1 -0
  97. package/dist/observation/index.js.map +1 -1
  98. package/dist/observation/toObserverRunningState.d.ts +9 -0
  99. package/dist/observation/toObserverRunningState.d.ts.map +1 -0
  100. package/dist/observation/toObserverRunningState.js +26 -0
  101. package/dist/observation/toObserverRunningState.js.map +1 -0
  102. package/dist/projections/IProjections.d.ts +71 -1
  103. package/dist/projections/IProjections.d.ts.map +1 -1
  104. package/dist/projections/ProjectionQueryResult.d.ts +8 -0
  105. package/dist/projections/ProjectionQueryResult.d.ts.map +1 -0
  106. package/dist/projections/ProjectionQueryResult.js +4 -0
  107. package/dist/projections/ProjectionQueryResult.js.map +1 -0
  108. package/dist/projections/ProjectionState.d.ts +18 -0
  109. package/dist/projections/ProjectionState.d.ts.map +1 -0
  110. package/dist/projections/ProjectionState.js +4 -0
  111. package/dist/projections/ProjectionState.js.map +1 -0
  112. package/dist/projections/Projections.d.ts +46 -1
  113. package/dist/projections/Projections.d.ts.map +1 -1
  114. package/dist/projections/Projections.js +153 -6
  115. package/dist/projections/Projections.js.map +1 -1
  116. package/dist/projections/UnableToQueryProjection.d.ts +12 -0
  117. package/dist/projections/UnableToQueryProjection.d.ts.map +1 -0
  118. package/dist/projections/UnableToQueryProjection.js +17 -0
  119. package/dist/projections/UnableToQueryProjection.js.map +1 -0
  120. package/dist/projections/index.d.ts +3 -0
  121. package/dist/projections/index.d.ts.map +1 -1
  122. package/dist/projections/index.js +1 -0
  123. package/dist/projections/index.js.map +1 -1
  124. package/dist/projections/modelBound/eventSequence.d.ts +22 -0
  125. package/dist/projections/modelBound/eventSequence.d.ts.map +1 -0
  126. package/dist/projections/modelBound/eventSequence.js +34 -0
  127. package/dist/projections/modelBound/eventSequence.js.map +1 -0
  128. package/dist/projections/modelBound/fromAll.d.ts +26 -0
  129. package/dist/projections/modelBound/fromAll.d.ts.map +1 -0
  130. package/dist/projections/modelBound/fromAll.js +31 -0
  131. package/dist/projections/modelBound/fromAll.js.map +1 -0
  132. package/dist/projections/modelBound/index.d.ts +4 -0
  133. package/dist/projections/modelBound/index.d.ts.map +1 -1
  134. package/dist/projections/modelBound/index.js +3 -0
  135. package/dist/projections/modelBound/index.js.map +1 -1
  136. package/dist/projections/modelBound/noAutoMap.d.ts +26 -0
  137. package/dist/projections/modelBound/noAutoMap.d.ts.map +1 -0
  138. package/dist/projections/modelBound/noAutoMap.js +44 -0
  139. package/dist/projections/modelBound/noAutoMap.js.map +1 -0
  140. package/dist/reactors/Reactors.d.ts.map +1 -1
  141. package/dist/reactors/Reactors.js +7 -3
  142. package/dist/reactors/Reactors.js.map +1 -1
  143. package/dist/readModels/MaterializedReadModels.d.ts +0 -1
  144. package/dist/readModels/MaterializedReadModels.d.ts.map +1 -1
  145. package/dist/readModels/MaterializedReadModels.js +2 -8
  146. package/dist/readModels/MaterializedReadModels.js.map +1 -1
  147. package/dist/readModels/ReadModelSubjectResolver.d.ts +22 -0
  148. package/dist/readModels/ReadModelSubjectResolver.d.ts.map +1 -0
  149. package/dist/readModels/ReadModelSubjectResolver.js +38 -0
  150. package/dist/readModels/ReadModelSubjectResolver.js.map +1 -0
  151. package/dist/readModels/ReadModels.d.ts.map +1 -1
  152. package/dist/readModels/ReadModels.js +15 -13
  153. package/dist/readModels/ReadModels.js.map +1 -1
  154. package/dist/readModels/index.d.ts +1 -0
  155. package/dist/readModels/index.d.ts.map +1 -1
  156. package/dist/readModels/index.js +1 -0
  157. package/dist/readModels/index.js.map +1 -1
  158. package/dist/reducers/Reducers.d.ts.map +1 -1
  159. package/dist/reducers/Reducers.js +21 -3
  160. package/dist/reducers/Reducers.js.map +1 -1
  161. package/dist/schemas/JsonSchema.d.ts +14 -0
  162. package/dist/schemas/JsonSchema.d.ts.map +1 -1
  163. package/dist/schemas/JsonSchemaGenerator.d.ts +61 -5
  164. package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
  165. package/dist/schemas/JsonSchemaGenerator.js +130 -26
  166. package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
  167. package/dist/schemas/index.d.ts +1 -1
  168. package/dist/schemas/index.d.ts.map +1 -1
  169. package/dist/seeding/EventSeeding.d.ts.map +1 -1
  170. package/dist/seeding/EventSeeding.js +5 -2
  171. package/dist/seeding/EventSeeding.js.map +1 -1
  172. package/dist/tsconfig.tsbuildinfo +1 -1
  173. package/dist/webhooks/Webhooks.d.ts.map +1 -1
  174. package/dist/webhooks/Webhooks.js +22 -10
  175. package/dist/webhooks/Webhooks.js.map +1 -1
  176. package/eventSequences/AppendOptions.ts +9 -0
  177. package/eventSequences/EventForEventSourceId.ts +9 -0
  178. package/eventSequences/EventSequence.spec.ts +83 -74
  179. package/eventSequences/EventSequence.ts +64 -69
  180. package/events/EventContext.ts +4 -0
  181. package/events/EventTypes.ts +3 -2
  182. package/events/Tag.ts +14 -0
  183. package/events/filterEventsByTagDecorator.ts +51 -0
  184. package/events/index.ts +4 -0
  185. package/events/mergeTags.spec.ts +50 -0
  186. package/events/mergeTags.ts +27 -0
  187. package/events/tagDecorator.spec.ts +115 -0
  188. package/events/tagDecorator.ts +59 -0
  189. package/jobs/Jobs.ts +3 -3
  190. package/observation/ObserverRunningState.ts +9 -18
  191. package/observation/index.ts +1 -0
  192. package/observation/toObserverRunningState.ts +27 -0
  193. package/package.json +9 -9
  194. package/projections/IProjections.ts +81 -1
  195. package/projections/ProjectionQueryResult.ts +10 -0
  196. package/projections/ProjectionState.ts +25 -0
  197. package/projections/Projections.childrenAndNested.spec.ts +1 -1
  198. package/projections/Projections.modelBoundCompleteness.spec.ts +224 -0
  199. package/projections/Projections.operationalSurface.spec.ts +257 -0
  200. package/projections/Projections.spec.ts +1 -1
  201. package/projections/Projections.ts +180 -5
  202. package/projections/UnableToQueryProjection.ts +17 -0
  203. package/projections/index.ts +3 -0
  204. package/projections/modelBound/eventSequence.ts +38 -0
  205. package/projections/modelBound/fromAll.ts +42 -0
  206. package/projections/modelBound/index.ts +4 -0
  207. package/projections/modelBound/noAutoMap.ts +47 -0
  208. package/reactors/Reactors.ts +7 -3
  209. package/readModels/MaterializedReadModels.spec.ts +98 -0
  210. package/readModels/MaterializedReadModels.ts +2 -8
  211. package/readModels/ReadModelSubjectResolver.spec.ts +84 -0
  212. package/readModels/ReadModelSubjectResolver.ts +44 -0
  213. package/readModels/ReadModels.spec.ts +118 -0
  214. package/readModels/ReadModels.ts +15 -13
  215. package/readModels/index.ts +1 -0
  216. package/reducers/Reducers.spec.ts +80 -0
  217. package/reducers/Reducers.ts +22 -3
  218. package/schemas/JsonSchema.ts +16 -0
  219. package/schemas/JsonSchemaGenerator.spec.ts +195 -0
  220. package/schemas/JsonSchemaGenerator.ts +145 -27
  221. package/schemas/index.ts +1 -1
  222. package/seeding/EventSeeding.ts +5 -2
  223. package/webhooks/Webhooks.ts +24 -10
@@ -0,0 +1,59 @@
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 'reflect-metadata';
5
+ import { Tag } from './Tag';
6
+ import { mergeTags } from './mergeTags';
7
+
8
+ /** Metadata key used to store the tags labeling a class. */
9
+ const TAGS_METADATA_KEY = 'chronicle:tags';
10
+
11
+ /**
12
+ * TypeScript decorator that labels an event type, reactor, or reducer with one or more tags.
13
+ * This is the TypeScript equivalent of the C# `[Tag]` attribute.
14
+ *
15
+ * Applying the decorator more than once accumulates tags rather than replacing them, so
16
+ * `@tag('a')` followed by `@tag('b')` on the same class results in both `'a'` and `'b'`.
17
+ *
18
+ * @param values - The tags to apply.
19
+ * @returns A class decorator.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * @eventType()
24
+ * @tag('analytics', 'user-action')
25
+ * class UserLoggedIn {
26
+ * constructor(readonly userId: string) {}
27
+ * }
28
+ * ```
29
+ */
30
+ export function tag(...values: string[]): ClassDecorator {
31
+ return (target: object) => addTags(target, values);
32
+ }
33
+
34
+ /**
35
+ * TypeScript decorator that labels an event type, reactor, or reducer with one or more tags.
36
+ * This is the TypeScript equivalent of the C# `[Tags]` attribute, and behaves identically to
37
+ * {@link tag} - use whichever reads more naturally at the call site.
38
+ *
39
+ * @param values - The tags to apply.
40
+ * @returns A class decorator.
41
+ */
42
+ export function tags(...values: string[]): ClassDecorator {
43
+ return (target: object) => addTags(target, values);
44
+ }
45
+
46
+ function addTags(target: object, values: string[]): void {
47
+ const existing = (Reflect.getMetadata(TAGS_METADATA_KEY, target) as Tag[] | undefined) ?? [];
48
+ const merged = mergeTags(existing, values).map(value => new Tag(value));
49
+ Reflect.defineMetadata(TAGS_METADATA_KEY, merged, target);
50
+ }
51
+
52
+ /**
53
+ * Gets all tags applied to a class via {@link tag} or {@link tags}.
54
+ * @param target - The class constructor to retrieve tags for.
55
+ * @returns The tags applied to the class, or an empty array if none are.
56
+ */
57
+ export function getTagsFor(target: Function): Tag[] {
58
+ return (Reflect.getMetadata(TAGS_METADATA_KEY, target) as Tag[] | undefined) ?? [];
59
+ }
package/jobs/Jobs.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import type { JobSummaryResponse, JobStepSummaryResponse } from '@cratis/chronicle.contracts';
5
5
  import { Guid } from '@cratis/fundamentals';
6
6
  import { ChronicleConnection } from '../connection';
7
- import { ensureCommandSuccess, ensureQuerySuccess, firstQueryResult } from '../connection/callResults';
7
+ import { ensureCommandSuccess, ensureQuerySuccess } from '../connection/callResults';
8
8
  import { fromContractsGuid, toContractsGuid } from '../connection/Guid';
9
9
  import { IJobs } from './IJobs';
10
10
  import { JobId } from './JobId';
@@ -49,10 +49,10 @@ export class Jobs implements IJobs {
49
49
 
50
50
  /** @inheritdoc */
51
51
  async getJobs(): Promise<JobSummaryResponse[]> {
52
- const response = await firstQueryResult('get jobs', this._connection.jobs.allJobs({
52
+ const response = await this._connection.jobs.allJobs({
53
53
  EventStore: this._eventStore,
54
54
  Namespace: this._namespace
55
- }));
55
+ });
56
56
 
57
57
  return ensureQuerySuccess('get jobs', response);
58
58
  }
@@ -8,27 +8,18 @@ export enum ObserverRunningState {
8
8
  /** The observer is in an unknown state. */
9
9
  Unknown = 'Unknown',
10
10
 
11
- /** The observer is subscribing to the event sequence. */
12
- Subscribing = 'Subscribing',
13
-
14
- /** The observer is replaying events from the beginning. */
15
- Replaying = 'Replaying',
16
-
17
- /** The observer is resuming after a pause. */
18
- Resuming = 'Resuming',
19
-
20
- /** The observer is actively processing events. */
11
+ /** The observer is active and waiting for new events. */
21
12
  Active = 'Active',
22
13
 
23
- /** The observer is paused and not processing events. */
24
- Paused = 'Paused',
14
+ /** The observer is suspended. */
15
+ Suspended = 'Suspended',
25
16
 
26
- /** The observer has stopped processing events. */
27
- Stopped = 'Stopped',
17
+ /** The observer is replaying. */
18
+ Replaying = 'Replaying',
28
19
 
29
- /** The observer is in a failed state. */
30
- Failed = 'Failed',
20
+ /** The observer is disconnected. */
21
+ Disconnected = 'Disconnected',
31
22
 
32
- /** The observer has been disconnected. */
33
- Disconnected = 'Disconnected'
23
+ /** The observer is quarantined. */
24
+ Quarantined = 'Quarantined'
34
25
  }
@@ -7,5 +7,6 @@ export type { FailedPartition } from './FailedPartition';
7
7
  export type { FailedPartitionAttempt } from './FailedPartitionAttempt';
8
8
  export type { IFailedPartitions } from './IFailedPartitions';
9
9
  export { FailedPartitions } from './FailedPartitions';
10
+ export { toObserverRunningState } from './toObserverRunningState';
10
11
  export type { ICanBeNotifiedWhenReplay } from './ICanBeNotifiedWhenReplay';
11
12
  export type { ICanBeNotifiedWhenPartitionReplayed } from './ICanBeNotifiedWhenPartitionReplayed';
@@ -0,0 +1,27 @@
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 { ObserverRunningState as ContractObserverRunningState } from '@cratis/chronicle.contracts';
5
+ import { ObserverRunningState } from './ObserverRunningState';
6
+
7
+ /**
8
+ * Converts a wire {@link ContractObserverRunningState} into the client {@link ObserverRunningState}.
9
+ * @param state - The wire running state to convert.
10
+ * @returns The converted client running state.
11
+ */
12
+ export function toObserverRunningState(state: ContractObserverRunningState): ObserverRunningState {
13
+ switch (state) {
14
+ case ContractObserverRunningState.Active:
15
+ return ObserverRunningState.Active;
16
+ case ContractObserverRunningState.Suspended:
17
+ return ObserverRunningState.Suspended;
18
+ case ContractObserverRunningState.Replaying:
19
+ return ObserverRunningState.Replaying;
20
+ case ContractObserverRunningState.OBSERVER_RUNNING_STATE_Disconnected:
21
+ return ObserverRunningState.Disconnected;
22
+ case ContractObserverRunningState.Quarantined:
23
+ return ObserverRunningState.Quarantined;
24
+ default:
25
+ return ObserverRunningState.Unknown;
26
+ }
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/chronicle",
3
- "version": "3.1.1",
3
+ "version": "5.0.0",
4
4
  "description": "Event sourcing for TypeScript and Node.js — the idiomatic client for Cratis Chronicle, the open-source event-sourcing database",
5
5
  "author": "Cratis",
6
6
  "license": "MIT",
@@ -159,20 +159,20 @@
159
159
  "test": "vitest run"
160
160
  },
161
161
  "dependencies": {
162
- "@bufbuild/protobuf": "^2.12.0",
163
- "@cratis/chronicle.contracts": "17.0.0",
162
+ "@bufbuild/protobuf": "^2.14.1",
163
+ "@cratis/chronicle.contracts": "17.0.4",
164
164
  "@grpc/grpc-js": "^1.14.4",
165
165
  "@opentelemetry/api": "^1.9.1",
166
- "nice-grpc": "^2.1.16",
167
- "nice-grpc-common": "^2.0.3",
166
+ "nice-grpc": "^2.1.17",
167
+ "nice-grpc-common": "^2.0.4",
168
168
  "reflect-metadata": "^0.2.2",
169
- "undici": "^8.7.0"
169
+ "undici": "^8.10.2"
170
170
  },
171
171
  "devDependencies": {
172
- "@cratis/fundamentals": "7.14.0",
173
- "@types/node": "^25.9.1",
172
+ "@cratis/fundamentals": "7.18.3",
173
+ "@types/node": "^26.4.1",
174
174
  "typescript": "^6.0.3",
175
- "vitest": "^4.1.10"
175
+ "vitest": "^5.0.0"
176
176
  },
177
177
  "peerDependencies": {
178
178
  "@cratis/fundamentals": "^7.14.0"
@@ -1,8 +1,16 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
+ import { Constructor } from '@cratis/fundamentals';
5
+ import { FailedPartition } from '../observation/FailedPartition';
6
+ import { JobId } from '../jobs/JobId';
7
+ import { ProjectionId } from './ProjectionId';
8
+ import { ProjectionQueryResult } from './ProjectionQueryResult';
9
+ import { ProjectionState } from './ProjectionState';
10
+
4
11
  /**
5
- * Defines a system to work with projections, including discovery and registration with the Kernel.
12
+ * Defines a system to work with projections, including discovery, registration and operating
13
+ * on projections that are known to the Chronicle Kernel.
6
14
  */
7
15
  export interface IProjections {
8
16
  /**
@@ -16,4 +24,76 @@ export interface IProjections {
16
24
  * @returns A promise that resolves when registration is complete.
17
25
  */
18
26
  register(): Promise<void>;
27
+
28
+ /**
29
+ * Check if there is a definition for a specific projection identifier.
30
+ * @param projectionId - Identifier of the projection.
31
+ * @returns True if it exists, false if not.
32
+ */
33
+ hasFor(projectionId: ProjectionId | string): boolean;
34
+
35
+ /**
36
+ * Check if there is a definition for the projection that maintains a specific read model.
37
+ * @param readModelType - Type of read model to check for.
38
+ * @returns True if it exists, false if not.
39
+ */
40
+ hasForModel(readModelType: Constructor): boolean;
41
+
42
+ /**
43
+ * Get the {@link ProjectionId} for the projection that maintains a specific read model.
44
+ * @param readModelType - Type of read model to get for.
45
+ * @returns The {@link ProjectionId} for the read model.
46
+ * @remarks A model-bound projection has no projection type of its own - its read model type is the
47
+ * only handle it has. A declarative projection is only resolvable here when it declares its
48
+ * read model type explicitly (the second argument to the `projection()` decorator); one whose read
49
+ * model is inferred at registration time cannot be resolved before `register()` has run.
50
+ */
51
+ getProjectionIdFor(readModelType: Constructor): ProjectionId;
52
+
53
+ /**
54
+ * Get the state of a specific projection.
55
+ * @param projectionId - Identifier of the projection to get the state for.
56
+ * @returns The {@link ProjectionState}.
57
+ */
58
+ getStateFor(projectionId: ProjectionId | string): Promise<ProjectionState>;
59
+
60
+ /**
61
+ * Get the state of the projection that maintains a specific read model.
62
+ * @param readModelType - Type of read model to get the state for.
63
+ * @returns The {@link ProjectionState}.
64
+ */
65
+ getStateForModel(readModelType: Constructor): Promise<ProjectionState>;
66
+
67
+ /**
68
+ * Get any failed partitions for the projection that maintains a specific read model.
69
+ * @param readModelType - Type of read model to get for.
70
+ * @returns Collection of {@link FailedPartition}, if any.
71
+ */
72
+ getFailedPartitionsForModel(readModelType: Constructor): Promise<FailedPartition[]>;
73
+
74
+ /**
75
+ * Replay a specific projection by its identifier.
76
+ * @param projectionId - Identifier of the projection to replay.
77
+ * @returns The {@link JobId} of the replay job that was started or resumed.
78
+ */
79
+ replay(projectionId: ProjectionId | string): Promise<JobId>;
80
+
81
+ /**
82
+ * Replay the projection that maintains a specific read model.
83
+ * @param readModelType - Type of read model to replay the projection for.
84
+ * @returns The {@link JobId} of the replay job that was started or resumed.
85
+ */
86
+ replayForModel(readModelType: Constructor): Promise<JobId>;
87
+
88
+ /**
89
+ * Query a projection declaration against the event log without registering it.
90
+ * @param declaration - The Projection Declaration Language string to query.
91
+ * @param eventSequenceId - Optional event sequence identifier to query. Defaults to `"event-log"`.
92
+ * @returns A {@link ProjectionQueryResult} containing the resulting read model entries.
93
+ * @remarks The declaration may omit the `=> ReadModelType` target - in that case the read model
94
+ * schema is inferred from the events used in the projection. An inferred read model can never be
95
+ * registered as a permanent projection; query-only declarations are exclusively for ad-hoc
96
+ * exploration.
97
+ */
98
+ query(declaration: string, eventSequenceId?: string): Promise<ProjectionQueryResult>;
19
99
  }
@@ -0,0 +1,10 @@
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
+ /**
5
+ * Represents the result of querying a projection against the event log.
6
+ */
7
+ export interface ProjectionQueryResult {
8
+ /** Collection of JSON representations of the resulting read model entries. */
9
+ readonly readModelEntries: ReadonlyArray<string>;
10
+ }
@@ -0,0 +1,25 @@
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 { ObserverRunningState } from '../observation/ObserverRunningState';
5
+ import { EventSequenceNumber } from '../eventSequences/EventSequenceNumber';
6
+
7
+ /**
8
+ * Represents the state of a projection.
9
+ */
10
+ export interface ProjectionState {
11
+ /** The current running state of the projection. */
12
+ readonly runningState: ObserverRunningState;
13
+
14
+ /** Indicates whether the projection is subscribed to its handler. */
15
+ readonly isSubscribed: boolean;
16
+
17
+ /** The next event sequence number the projection will process. */
18
+ readonly nextEventSequenceNumber: EventSequenceNumber;
19
+
20
+ /** The last event sequence number the projection handled. */
21
+ readonly lastHandledEventSequenceNumber: EventSequenceNumber;
22
+
23
+ /** The current tail event sequence number of the event sequence the projection observes. */
24
+ readonly tailEventSequenceNumber: EventSequenceNumber;
25
+ }
@@ -124,7 +124,7 @@ function createProjections(readModels: (new (...args: unknown[]) => unknown)[])
124
124
  eventTypeMigrations: []
125
125
  };
126
126
 
127
- const projections = new Projections('test-store', connection, clientArtifacts, 'test-sink');
127
+ const projections = new Projections('test-store', 'test-namespace', connection, clientArtifacts, 'test-sink');
128
128
  return { projections, registerMock };
129
129
  }
130
130
 
@@ -0,0 +1,224 @@
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 'reflect-metadata';
5
+ import { AutoMap } from '@cratis/chronicle.contracts';
6
+ import { describe, expect, it, vi } from 'vitest';
7
+ import { IClientArtifactsProvider } from '../artifacts';
8
+ import { ChronicleConnection } from '../connection';
9
+ import { eventType } from '../events/eventTypeDecorator';
10
+ import { readModel } from '../readModels/readModel';
11
+ import { clearWith } from './modelBound/clearWith';
12
+ import { eventLog, eventSequence } from './modelBound/eventSequence';
13
+ import { fromAll } from './modelBound/fromAll';
14
+ import { fromEvent } from './modelBound/fromEvent';
15
+ import { noAutoMap } from './modelBound/noAutoMap';
16
+ import { setFrom } from './modelBound/setFrom';
17
+ import { Projections } from './Projections';
18
+
19
+ // Decorators are applied as plain function calls (rather than `@decorator` syntax) so these
20
+ // fixtures don't depend on the test runner's decorator-syntax support.
21
+
22
+ class MbWorkArrangementSet {
23
+ location!: string;
24
+ workMode!: number;
25
+ }
26
+ eventType()(MbWorkArrangementSet);
27
+
28
+ class MbCandidateSubmitted {
29
+ name!: string;
30
+ location!: string;
31
+ }
32
+ eventType()(MbCandidateSubmitted);
33
+
34
+ class MbAssignmentSummary {
35
+ id!: string;
36
+ location!: string;
37
+ candidateName!: string;
38
+ }
39
+ setFrom(MbWorkArrangementSet, 'location')(MbAssignmentSummary.prototype, 'location');
40
+ noAutoMap(MbAssignmentSummary.prototype, 'location');
41
+ setFrom(MbCandidateSubmitted, 'name')(MbAssignmentSummary.prototype, 'candidateName');
42
+ fromEvent(MbWorkArrangementSet)(MbAssignmentSummary);
43
+ readModel()(MbAssignmentSummary);
44
+
45
+ class MbFullyExcluded {
46
+ id!: string;
47
+ location!: string;
48
+ }
49
+ setFrom(MbWorkArrangementSet, 'location')(MbFullyExcluded.prototype, 'location');
50
+ fromEvent(MbWorkArrangementSet)(MbFullyExcluded);
51
+ noAutoMap(MbFullyExcluded);
52
+ readModel()(MbFullyExcluded);
53
+
54
+ class MbProductRenamed {
55
+ name!: string;
56
+ version!: number;
57
+ }
58
+ eventType()(MbProductRenamed);
59
+
60
+ class MbProductPriceChanged {
61
+ price!: number;
62
+ version!: number;
63
+ }
64
+ eventType()(MbProductPriceChanged);
65
+
66
+ class MbProductVersion {
67
+ id!: string;
68
+ name!: string;
69
+ price!: number;
70
+ version!: number;
71
+ }
72
+ fromAll('version')(MbProductVersion.prototype, 'version');
73
+ fromEvent(MbProductRenamed)(MbProductVersion);
74
+ fromEvent(MbProductPriceChanged)(MbProductVersion);
75
+ readModel()(MbProductVersion);
76
+
77
+ class MbOrderPlaced {
78
+ amount!: number;
79
+ }
80
+ eventType()(MbOrderPlaced);
81
+
82
+ class MbOrderSummaryWithCustomSequence {
83
+ id!: string;
84
+ totalAmount!: number;
85
+ }
86
+ setFrom(MbOrderPlaced, 'amount')(MbOrderSummaryWithCustomSequence.prototype, 'totalAmount');
87
+ fromEvent(MbOrderPlaced)(MbOrderSummaryWithCustomSequence);
88
+ eventSequence('custom-sequence')(MbOrderSummaryWithCustomSequence);
89
+ readModel()(MbOrderSummaryWithCustomSequence);
90
+
91
+ class MbLocalEvent {
92
+ data!: string;
93
+ }
94
+ eventType()(MbLocalEvent);
95
+
96
+ class MbLocalSnapshot {
97
+ id!: string;
98
+ data!: string;
99
+ }
100
+ setFrom(MbLocalEvent, 'data')(MbLocalSnapshot.prototype, 'data');
101
+ fromEvent(MbLocalEvent)(MbLocalSnapshot);
102
+ eventLog(MbLocalSnapshot);
103
+ readModel()(MbLocalSnapshot);
104
+
105
+ class MbProjectNoted {
106
+ note!: string;
107
+ }
108
+ eventType()(MbProjectNoted);
109
+
110
+ class MbProjectNoteCleared {}
111
+ eventType()(MbProjectNoteCleared);
112
+
113
+ class MbProjectNotes {
114
+ id!: string;
115
+ note!: string | undefined;
116
+ }
117
+ setFrom(MbProjectNoted, 'note')(MbProjectNotes.prototype, 'note');
118
+ clearWith(MbProjectNoteCleared)(MbProjectNotes.prototype, 'note');
119
+ fromEvent(MbProjectNoted)(MbProjectNotes);
120
+ readModel()(MbProjectNotes);
121
+
122
+ interface BuiltFromEntry {
123
+ Key: { Id: string };
124
+ Value: { Properties: Record<string, string>; Key: string; ParentKey: string };
125
+ }
126
+
127
+ interface BuiltDefinition {
128
+ EventSequenceId: string;
129
+ AutoMap: AutoMap;
130
+ NoAutoMapProperties: string[];
131
+ All: { Properties: Record<string, string> };
132
+ From: BuiltFromEntry[];
133
+ }
134
+
135
+ function createProjections(readModels: (new (...args: unknown[]) => unknown)[]) {
136
+ const registerMock = vi.fn().mockResolvedValue(undefined);
137
+ const registerManyMock = vi.fn().mockResolvedValue(undefined);
138
+ const connection = {
139
+ readModels: { registerMany: registerManyMock },
140
+ projections: { register: registerMock }
141
+ } as unknown as ChronicleConnection;
142
+
143
+ const clientArtifacts: IClientArtifactsProvider = {
144
+ eventTypes: [],
145
+ readModels: readModels as unknown as IClientArtifactsProvider['readModels'],
146
+ reactors: [],
147
+ reducers: [],
148
+ seeders: [],
149
+ constraints: [],
150
+ projections: [],
151
+ webhooks: [],
152
+ eventTypeMigrations: []
153
+ };
154
+
155
+ const projections = new Projections('test-store', 'test-namespace', connection, clientArtifacts, 'test-sink');
156
+ return { projections, registerMock };
157
+ }
158
+
159
+ async function registerAndGetDefinition(readModelType: new (...args: unknown[]) => unknown): Promise<BuiltDefinition> {
160
+ const { projections, registerMock } = createProjections([readModelType]);
161
+ await projections.register();
162
+ return registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition;
163
+ }
164
+
165
+ function findFromEntry(definition: BuiltDefinition, eventTypeId: string): BuiltFromEntry {
166
+ const entry = definition.From.find(candidate => candidate.Key.Id === eventTypeId);
167
+ if (!entry) {
168
+ throw new Error(`No From entry found for event type '${eventTypeId}'.`);
169
+ }
170
+ return entry;
171
+ }
172
+
173
+ describe('Projections model-bound completeness', () => {
174
+ describe('when a property is excluded from AutoMap with noAutoMap', () => {
175
+ it('should keep AutoMap enabled at the root and list only the excluded property', async () => {
176
+ const definition = await registerAndGetDefinition(MbAssignmentSummary);
177
+
178
+ expect(definition.AutoMap).toBe(AutoMap.Enabled);
179
+ expect(definition.NoAutoMapProperties).toEqual(['location']);
180
+
181
+ const workArrangementSet = findFromEntry(definition, 'MbWorkArrangementSet');
182
+ expect(workArrangementSet.Value.Properties.location).toBe('location');
183
+
184
+ const candidateSubmitted = findFromEntry(definition, 'MbCandidateSubmitted');
185
+ expect(candidateSubmitted.Value.Properties.candidateName).toBe('name');
186
+ });
187
+ });
188
+
189
+ describe('when a whole model-bound projection is excluded from AutoMap with class-level noAutoMap', () => {
190
+ it('should disable AutoMap for the whole definition', async () => {
191
+ const definition = await registerAndGetDefinition(MbFullyExcluded);
192
+ expect(definition.AutoMap).toBe(AutoMap.Disabled);
193
+ });
194
+ });
195
+
196
+ describe('when a property uses fromAll to read from every event type', () => {
197
+ it('should include it in the All properties map', async () => {
198
+ const definition = await registerAndGetDefinition(MbProductVersion);
199
+ expect(definition.All.Properties.version).toBe('version');
200
+ });
201
+ });
202
+
203
+ describe('when a model-bound projection declares a custom event sequence', () => {
204
+ it('should use the declared event sequence instead of the event log', async () => {
205
+ const definition = await registerAndGetDefinition(MbOrderSummaryWithCustomSequence);
206
+ expect(definition.EventSequenceId).toBe('custom-sequence');
207
+ });
208
+ });
209
+
210
+ describe('when a model-bound projection is explicitly pinned to the event log', () => {
211
+ it('should use the event log sequence', async () => {
212
+ const definition = await registerAndGetDefinition(MbLocalSnapshot);
213
+ expect(definition.EventSequenceId).toBe('event-log');
214
+ });
215
+ });
216
+
217
+ describe('when a root scalar property is cleared with an event', () => {
218
+ it('should add a $null mapping for that property on the clearing event', async () => {
219
+ const definition = await registerAndGetDefinition(MbProjectNotes);
220
+ const clearedBy = findFromEntry(definition, 'MbProjectNoteCleared');
221
+ expect(clearedBy.Value.Properties.note).toBe('$null');
222
+ });
223
+ });
224
+ });