@cratis/chronicle 3.1.0 → 4.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 (217) hide show
  1. package/ChronicleClient.ts +4 -3
  2. package/EventStore.ts +4 -3
  3. package/README.md +40 -28
  4. package/compliance/ComplianceContracts.ts +59 -0
  5. package/compliance/IPIIManager.ts +12 -0
  6. package/compliance/PIIManager.spec.ts +50 -0
  7. package/compliance/PIIManager.ts +9 -0
  8. package/compliance/PIINotSupportedOnEventSourceId.ts +21 -0
  9. package/compliance/index.ts +2 -0
  10. package/compliance/pii.spec.ts +37 -0
  11. package/compliance/pii.ts +16 -1
  12. package/compliance/subject.spec.ts +45 -0
  13. package/compliance/subject.ts +64 -0
  14. package/connection/callResults.spec.ts +90 -0
  15. package/connection/callResults.ts +112 -0
  16. package/dist/ChronicleClient.d.ts.map +1 -1
  17. package/dist/ChronicleClient.js +4 -3
  18. package/dist/ChronicleClient.js.map +1 -1
  19. package/dist/EventStore.d.ts.map +1 -1
  20. package/dist/EventStore.js +4 -3
  21. package/dist/EventStore.js.map +1 -1
  22. package/dist/compliance/ComplianceContracts.d.ts +31 -0
  23. package/dist/compliance/ComplianceContracts.d.ts.map +1 -1
  24. package/dist/compliance/ComplianceContracts.js +52 -0
  25. package/dist/compliance/ComplianceContracts.js.map +1 -1
  26. package/dist/compliance/IPIIManager.d.ts +11 -0
  27. package/dist/compliance/IPIIManager.d.ts.map +1 -1
  28. package/dist/compliance/PIIManager.d.ts +2 -0
  29. package/dist/compliance/PIIManager.d.ts.map +1 -1
  30. package/dist/compliance/PIIManager.js +8 -0
  31. package/dist/compliance/PIIManager.js.map +1 -1
  32. package/dist/compliance/PIINotSupportedOnEventSourceId.d.ts +11 -0
  33. package/dist/compliance/PIINotSupportedOnEventSourceId.d.ts.map +1 -0
  34. package/dist/compliance/PIINotSupportedOnEventSourceId.js +19 -0
  35. package/dist/compliance/PIINotSupportedOnEventSourceId.js.map +1 -0
  36. package/dist/compliance/index.d.ts +2 -0
  37. package/dist/compliance/index.d.ts.map +1 -1
  38. package/dist/compliance/index.js +2 -0
  39. package/dist/compliance/index.js.map +1 -1
  40. package/dist/compliance/pii.d.ts.map +1 -1
  41. package/dist/compliance/pii.js +13 -1
  42. package/dist/compliance/pii.js.map +1 -1
  43. package/dist/compliance/subject.d.ts +39 -0
  44. package/dist/compliance/subject.d.ts.map +1 -0
  45. package/dist/compliance/subject.js +58 -0
  46. package/dist/compliance/subject.js.map +1 -0
  47. package/dist/connection/callResults.d.ts +69 -0
  48. package/dist/connection/callResults.d.ts.map +1 -0
  49. package/dist/connection/callResults.js +74 -0
  50. package/dist/connection/callResults.js.map +1 -0
  51. package/dist/eventSequences/AppendOptions.d.ts +8 -0
  52. package/dist/eventSequences/AppendOptions.d.ts.map +1 -1
  53. package/dist/eventSequences/EventForEventSourceId.d.ts +7 -0
  54. package/dist/eventSequences/EventForEventSourceId.d.ts.map +1 -1
  55. package/dist/eventSequences/EventSequence.d.ts.map +1 -1
  56. package/dist/eventSequences/EventSequence.js +17 -6
  57. package/dist/eventSequences/EventSequence.js.map +1 -1
  58. package/dist/events/EventContext.d.ts +3 -0
  59. package/dist/events/EventContext.d.ts.map +1 -1
  60. package/dist/events/Tag.d.ts +10 -0
  61. package/dist/events/Tag.d.ts.map +1 -0
  62. package/dist/events/Tag.js +16 -0
  63. package/dist/events/Tag.js.map +1 -0
  64. package/dist/events/constraints/Constraints.js +3 -3
  65. package/dist/events/constraints/Constraints.js.map +1 -1
  66. package/dist/events/filterEventsByTagDecorator.d.ts +35 -0
  67. package/dist/events/filterEventsByTagDecorator.d.ts.map +1 -0
  68. package/dist/events/filterEventsByTagDecorator.js +48 -0
  69. package/dist/events/filterEventsByTagDecorator.js.map +1 -0
  70. package/dist/events/index.d.ts +4 -0
  71. package/dist/events/index.d.ts.map +1 -1
  72. package/dist/events/index.js +4 -0
  73. package/dist/events/index.js.map +1 -1
  74. package/dist/events/mergeTags.d.ts +12 -0
  75. package/dist/events/mergeTags.d.ts.map +1 -0
  76. package/dist/events/mergeTags.js +26 -0
  77. package/dist/events/mergeTags.js.map +1 -0
  78. package/dist/events/tagDecorator.d.ts +38 -0
  79. package/dist/events/tagDecorator.d.ts.map +1 -0
  80. package/dist/events/tagDecorator.js +54 -0
  81. package/dist/events/tagDecorator.js.map +1 -0
  82. package/dist/jobs/IJobs.d.ts +4 -4
  83. package/dist/jobs/IJobs.d.ts.map +1 -1
  84. package/dist/jobs/Jobs.d.ts +4 -4
  85. package/dist/jobs/Jobs.d.ts.map +1 -1
  86. package/dist/jobs/Jobs.js +12 -10
  87. package/dist/jobs/Jobs.js.map +1 -1
  88. package/dist/observation/ObserverRunningState.d.ts +9 -15
  89. package/dist/observation/ObserverRunningState.d.ts.map +1 -1
  90. package/dist/observation/ObserverRunningState.js +8 -14
  91. package/dist/observation/ObserverRunningState.js.map +1 -1
  92. package/dist/observation/index.d.ts +1 -0
  93. package/dist/observation/index.d.ts.map +1 -1
  94. package/dist/observation/index.js +1 -0
  95. package/dist/observation/index.js.map +1 -1
  96. package/dist/observation/toObserverRunningState.d.ts +9 -0
  97. package/dist/observation/toObserverRunningState.d.ts.map +1 -0
  98. package/dist/observation/toObserverRunningState.js +26 -0
  99. package/dist/observation/toObserverRunningState.js.map +1 -0
  100. package/dist/projections/IProjections.d.ts +71 -1
  101. package/dist/projections/IProjections.d.ts.map +1 -1
  102. package/dist/projections/ProjectionQueryResult.d.ts +8 -0
  103. package/dist/projections/ProjectionQueryResult.d.ts.map +1 -0
  104. package/dist/projections/ProjectionQueryResult.js +4 -0
  105. package/dist/projections/ProjectionQueryResult.js.map +1 -0
  106. package/dist/projections/ProjectionState.d.ts +18 -0
  107. package/dist/projections/ProjectionState.d.ts.map +1 -0
  108. package/dist/projections/ProjectionState.js +4 -0
  109. package/dist/projections/ProjectionState.js.map +1 -0
  110. package/dist/projections/Projections.d.ts +46 -1
  111. package/dist/projections/Projections.d.ts.map +1 -1
  112. package/dist/projections/Projections.js +153 -6
  113. package/dist/projections/Projections.js.map +1 -1
  114. package/dist/projections/UnableToQueryProjection.d.ts +12 -0
  115. package/dist/projections/UnableToQueryProjection.d.ts.map +1 -0
  116. package/dist/projections/UnableToQueryProjection.js +17 -0
  117. package/dist/projections/UnableToQueryProjection.js.map +1 -0
  118. package/dist/projections/index.d.ts +3 -0
  119. package/dist/projections/index.d.ts.map +1 -1
  120. package/dist/projections/index.js +1 -0
  121. package/dist/projections/index.js.map +1 -1
  122. package/dist/projections/modelBound/eventSequence.d.ts +22 -0
  123. package/dist/projections/modelBound/eventSequence.d.ts.map +1 -0
  124. package/dist/projections/modelBound/eventSequence.js +34 -0
  125. package/dist/projections/modelBound/eventSequence.js.map +1 -0
  126. package/dist/projections/modelBound/fromAll.d.ts +26 -0
  127. package/dist/projections/modelBound/fromAll.d.ts.map +1 -0
  128. package/dist/projections/modelBound/fromAll.js +31 -0
  129. package/dist/projections/modelBound/fromAll.js.map +1 -0
  130. package/dist/projections/modelBound/index.d.ts +4 -0
  131. package/dist/projections/modelBound/index.d.ts.map +1 -1
  132. package/dist/projections/modelBound/index.js +3 -0
  133. package/dist/projections/modelBound/index.js.map +1 -1
  134. package/dist/projections/modelBound/noAutoMap.d.ts +26 -0
  135. package/dist/projections/modelBound/noAutoMap.d.ts.map +1 -0
  136. package/dist/projections/modelBound/noAutoMap.js +44 -0
  137. package/dist/projections/modelBound/noAutoMap.js.map +1 -0
  138. package/dist/reactors/Reactors.d.ts.map +1 -1
  139. package/dist/reactors/Reactors.js +7 -3
  140. package/dist/reactors/Reactors.js.map +1 -1
  141. package/dist/readModels/MaterializedReadModels.d.ts +0 -1
  142. package/dist/readModels/MaterializedReadModels.d.ts.map +1 -1
  143. package/dist/readModels/MaterializedReadModels.js +2 -8
  144. package/dist/readModels/MaterializedReadModels.js.map +1 -1
  145. package/dist/readModels/ReadModelSubjectResolver.d.ts +22 -0
  146. package/dist/readModels/ReadModelSubjectResolver.d.ts.map +1 -0
  147. package/dist/readModels/ReadModelSubjectResolver.js +38 -0
  148. package/dist/readModels/ReadModelSubjectResolver.js.map +1 -0
  149. package/dist/readModels/ReadModels.d.ts.map +1 -1
  150. package/dist/readModels/ReadModels.js +7 -7
  151. package/dist/readModels/ReadModels.js.map +1 -1
  152. package/dist/readModels/index.d.ts +1 -0
  153. package/dist/readModels/index.d.ts.map +1 -1
  154. package/dist/readModels/index.js +1 -0
  155. package/dist/readModels/index.js.map +1 -1
  156. package/dist/reducers/Reducers.d.ts.map +1 -1
  157. package/dist/reducers/Reducers.js +21 -3
  158. package/dist/reducers/Reducers.js.map +1 -1
  159. package/dist/schemas/JsonSchema.d.ts +14 -0
  160. package/dist/schemas/JsonSchema.d.ts.map +1 -1
  161. package/dist/schemas/JsonSchemaGenerator.d.ts +61 -5
  162. package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
  163. package/dist/schemas/JsonSchemaGenerator.js +130 -26
  164. package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
  165. package/dist/schemas/index.d.ts +1 -1
  166. package/dist/schemas/index.d.ts.map +1 -1
  167. package/dist/seeding/EventSeeding.d.ts.map +1 -1
  168. package/dist/seeding/EventSeeding.js +3 -1
  169. package/dist/seeding/EventSeeding.js.map +1 -1
  170. package/dist/tsconfig.tsbuildinfo +1 -1
  171. package/eventSequences/AppendOptions.ts +9 -0
  172. package/eventSequences/EventForEventSourceId.ts +9 -0
  173. package/eventSequences/EventSequence.ts +20 -6
  174. package/events/EventContext.ts +4 -0
  175. package/events/Tag.ts +14 -0
  176. package/events/constraints/Constraints.ts +3 -3
  177. package/events/filterEventsByTagDecorator.ts +51 -0
  178. package/events/index.ts +4 -0
  179. package/events/mergeTags.spec.ts +50 -0
  180. package/events/mergeTags.ts +27 -0
  181. package/events/tagDecorator.spec.ts +115 -0
  182. package/events/tagDecorator.ts +59 -0
  183. package/jobs/IJobs.ts +4 -4
  184. package/jobs/Jobs.ts +16 -14
  185. package/observation/ObserverRunningState.ts +9 -18
  186. package/observation/index.ts +1 -0
  187. package/observation/toObserverRunningState.ts +27 -0
  188. package/package.json +25 -10
  189. package/projections/IProjections.ts +81 -1
  190. package/projections/ProjectionQueryResult.ts +10 -0
  191. package/projections/ProjectionState.ts +25 -0
  192. package/projections/Projections.childrenAndNested.spec.ts +1 -1
  193. package/projections/Projections.modelBoundCompleteness.spec.ts +224 -0
  194. package/projections/Projections.operationalSurface.spec.ts +257 -0
  195. package/projections/Projections.spec.ts +1 -1
  196. package/projections/Projections.ts +180 -5
  197. package/projections/UnableToQueryProjection.ts +17 -0
  198. package/projections/index.ts +3 -0
  199. package/projections/modelBound/eventSequence.ts +38 -0
  200. package/projections/modelBound/fromAll.ts +42 -0
  201. package/projections/modelBound/index.ts +4 -0
  202. package/projections/modelBound/noAutoMap.ts +47 -0
  203. package/reactors/Reactors.ts +7 -3
  204. package/readModels/MaterializedReadModels.spec.ts +98 -0
  205. package/readModels/MaterializedReadModels.ts +2 -8
  206. package/readModels/ReadModelSubjectResolver.spec.ts +84 -0
  207. package/readModels/ReadModelSubjectResolver.ts +44 -0
  208. package/readModels/ReadModels.spec.ts +118 -0
  209. package/readModels/ReadModels.ts +7 -7
  210. package/readModels/index.ts +1 -0
  211. package/reducers/Reducers.spec.ts +80 -0
  212. package/reducers/Reducers.ts +22 -3
  213. package/schemas/JsonSchema.ts +16 -0
  214. package/schemas/JsonSchemaGenerator.spec.ts +195 -0
  215. package/schemas/JsonSchemaGenerator.ts +145 -27
  216. package/schemas/index.ts +1 -1
  217. package/seeding/EventSeeding.ts +3 -1
package/jobs/Jobs.ts CHANGED
@@ -1,10 +1,11 @@
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 type { Job, JobStep } from '@cratis/chronicle.contracts';
4
+ import type { JobSummaryResponse, JobStepSummaryResponse } from '@cratis/chronicle.contracts';
5
5
  import { Guid } from '@cratis/fundamentals';
6
6
  import { ChronicleConnection } from '../connection';
7
- import { toContractsGuid } from '../connection/Guid';
7
+ import { ensureCommandSuccess, ensureQuerySuccess, firstQueryResult } from '../connection/callResults';
8
+ import { fromContractsGuid, toContractsGuid } from '../connection/Guid';
8
9
  import { IJobs } from './IJobs';
9
10
  import { JobId } from './JobId';
10
11
 
@@ -26,39 +27,40 @@ export class Jobs implements IJobs {
26
27
 
27
28
  /** @inheritdoc */
28
29
  async stop(jobId: JobId | Guid | string): Promise<void> {
29
- await this._connection.jobs.stop(this.createJobRequest(jobId));
30
+ ensureCommandSuccess('stop job', await this._connection.jobs.stopJob(this.createJobRequest(jobId)));
30
31
  }
31
32
 
32
33
  /** @inheritdoc */
33
34
  async resume(jobId: JobId | Guid | string): Promise<void> {
34
- await this._connection.jobs.resume(this.createJobRequest(jobId));
35
+ ensureCommandSuccess('resume job', await this._connection.jobs.resumeJob(this.createJobRequest(jobId)));
35
36
  }
36
37
 
37
38
  /** @inheritdoc */
38
39
  async delete(jobId: JobId | Guid | string): Promise<void> {
39
- await this._connection.jobs.delete(this.createJobRequest(jobId));
40
+ ensureCommandSuccess('delete job', await this._connection.jobs.deleteJob(this.createJobRequest(jobId)));
40
41
  }
41
42
 
42
43
  /** @inheritdoc */
43
- async getJob(jobId: JobId | Guid | string): Promise<Job | undefined> {
44
- const result = await this._connection.jobs.getJob(this.createJobRequest(jobId));
45
- return result.Value0;
44
+ async getJob(jobId: JobId | Guid | string): Promise<JobSummaryResponse | undefined> {
45
+ const target = this.normalizeJobId(jobId).toString();
46
+ const jobs = await this.getJobs();
47
+ return jobs.find(job => fromContractsGuid(job.Id).toString() === target);
46
48
  }
47
49
 
48
50
  /** @inheritdoc */
49
- async getJobs(): Promise<Job[]> {
50
- const response = await this._connection.jobs.getJobs({
51
+ async getJobs(): Promise<JobSummaryResponse[]> {
52
+ const response = await firstQueryResult('get jobs', this._connection.jobs.allJobs({
51
53
  EventStore: this._eventStore,
52
54
  Namespace: this._namespace
53
- });
55
+ }));
54
56
 
55
- return response.items ?? [];
57
+ return ensureQuerySuccess('get jobs', response);
56
58
  }
57
59
 
58
60
  /** @inheritdoc */
59
- async getJobSteps(jobId: JobId | Guid | string): Promise<JobStep[]> {
61
+ async getJobSteps(jobId: JobId | Guid | string): Promise<JobStepSummaryResponse[]> {
60
62
  const response = await this._connection.jobs.getJobSteps(this.createJobRequest(jobId));
61
- return response.items ?? [];
63
+ return ensureQuerySuccess('get job steps', response);
62
64
  }
63
65
 
64
66
  private createJobRequest(jobId: JobId | Guid | string): { EventStore: string; Namespace: string; JobId: ReturnType<typeof toContractsGuid> } {
@@ -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.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,9 +1,24 @@
1
1
  {
2
2
  "name": "@cratis/chronicle",
3
- "version": "3.1.0",
4
- "description": "TypeScript idiomatic client for Cratis Chronicle",
3
+ "version": "4.0.0",
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",
7
+ "homepage": "https://www.cratis.io/chronicle/",
8
+ "keywords": [
9
+ "event-sourcing",
10
+ "eventsourcing",
11
+ "event-store",
12
+ "eventstore",
13
+ "cqrs",
14
+ "ddd",
15
+ "projections",
16
+ "grpc",
17
+ "typescript",
18
+ "nodejs",
19
+ "chronicle",
20
+ "cratis"
21
+ ],
7
22
  "repository": {
8
23
  "type": "git",
9
24
  "url": "git+https://github.com/Cratis/Chronicle.TypeScript.git"
@@ -144,20 +159,20 @@
144
159
  "test": "vitest run"
145
160
  },
146
161
  "dependencies": {
147
- "@bufbuild/protobuf": "^2.12.0",
148
- "@cratis/chronicle.contracts": "16.13.4",
162
+ "@bufbuild/protobuf": "^2.14.1",
163
+ "@cratis/chronicle.contracts": "17.0.0",
149
164
  "@grpc/grpc-js": "^1.14.4",
150
165
  "@opentelemetry/api": "^1.9.1",
151
- "nice-grpc": "^2.1.16",
152
- "nice-grpc-common": "^2.0.3",
166
+ "nice-grpc": "^2.1.17",
167
+ "nice-grpc-common": "^2.0.4",
153
168
  "reflect-metadata": "^0.2.2",
154
- "undici": "^8.7.0"
169
+ "undici": "^8.10.1"
155
170
  },
156
171
  "devDependencies": {
157
- "@cratis/fundamentals": "7.14.0",
158
- "@types/node": "^25.9.1",
172
+ "@cratis/fundamentals": "7.18.2",
173
+ "@types/node": "^26.4.0",
159
174
  "typescript": "^6.0.3",
160
- "vitest": "^4.1.10"
175
+ "vitest": "^4.1.11"
161
176
  },
162
177
  "peerDependencies": {
163
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
+ });