@cratis/chronicle 0.0.1 → 0.1.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 (157) hide show
  1. package/Auditing/Causation.ts +29 -0
  2. package/Auditing/CausationManager.ts +51 -0
  3. package/Auditing/CausationType.ts +38 -0
  4. package/Auditing/ICausationManager.ts +29 -0
  5. package/Auditing/index.ts +15 -0
  6. package/Correlation/CorrelationId.ts +33 -0
  7. package/Correlation/CorrelationIdManager.ts +30 -0
  8. package/Correlation/ICorrelationIdAccessor.ts +15 -0
  9. package/Correlation/ICorrelationIdSetter.ts +21 -0
  10. package/Correlation/index.ts +15 -0
  11. package/EventSequences/EventSequence.ts +48 -50
  12. package/Identity/IIdentityProvider.ts +26 -0
  13. package/Identity/Identity.ts +61 -0
  14. package/Identity/IdentityProvider.ts +28 -0
  15. package/Identity/index.ts +14 -0
  16. package/Projections/Projections.ts +75 -20
  17. package/Projections/declarative/ProjectionBuilderFor.ts +19 -2
  18. package/Projections/declarative/projection.ts +6 -2
  19. package/Projections/modelBound/index.ts +0 -2
  20. package/Projections/modelBound/setFromContext.ts +20 -2
  21. package/README.md +1 -1
  22. package/Reactors/Reactors.ts +1 -0
  23. package/Reducers/Reducers.ts +66 -2
  24. package/Schemas/JsonSchemaGenerator.ts +20 -3
  25. package/artifacts/DefaultClientArtifactsProvider.ts +0 -5
  26. package/artifacts/IClientArtifactsProvider.ts +0 -3
  27. package/connection/Guid.ts +29 -1
  28. package/dist/Auditing/Causation.d.ts +22 -0
  29. package/dist/Auditing/Causation.d.ts.map +1 -0
  30. package/dist/Auditing/Causation.js +30 -0
  31. package/dist/Auditing/Causation.js.map +1 -0
  32. package/dist/Auditing/CausationManager.d.ts +23 -0
  33. package/dist/Auditing/CausationManager.d.ts.map +1 -0
  34. package/dist/Auditing/CausationManager.js +45 -0
  35. package/dist/Auditing/CausationManager.js.map +1 -0
  36. package/dist/Auditing/CausationType.d.ts +30 -0
  37. package/dist/Auditing/CausationType.d.ts.map +1 -0
  38. package/dist/Auditing/CausationType.js +36 -0
  39. package/dist/Auditing/CausationType.js.map +1 -0
  40. package/dist/Auditing/ICausationManager.d.ts +24 -0
  41. package/dist/Auditing/ICausationManager.d.ts.map +1 -0
  42. package/dist/Auditing/ICausationManager.js +4 -0
  43. package/dist/Auditing/ICausationManager.js.map +1 -0
  44. package/dist/Auditing/index.d.ts +11 -0
  45. package/dist/Auditing/index.d.ts.map +1 -0
  46. package/dist/Auditing/index.js +12 -0
  47. package/dist/Auditing/index.js.map +1 -0
  48. package/dist/Correlation/CorrelationId.d.ts +23 -0
  49. package/dist/Correlation/CorrelationId.d.ts.map +1 -0
  50. package/dist/Correlation/CorrelationId.js +32 -0
  51. package/dist/Correlation/CorrelationId.js.map +1 -0
  52. package/dist/Correlation/CorrelationIdManager.d.ts +17 -0
  53. package/dist/Correlation/CorrelationIdManager.d.ts.map +1 -0
  54. package/dist/Correlation/CorrelationIdManager.js +24 -0
  55. package/dist/Correlation/CorrelationIdManager.js.map +1 -0
  56. package/dist/Correlation/ICorrelationIdAccessor.d.ts +12 -0
  57. package/dist/Correlation/ICorrelationIdAccessor.d.ts.map +1 -0
  58. package/dist/Correlation/ICorrelationIdAccessor.js +4 -0
  59. package/dist/Correlation/ICorrelationIdAccessor.js.map +1 -0
  60. package/dist/Correlation/ICorrelationIdSetter.d.ts +17 -0
  61. package/dist/Correlation/ICorrelationIdSetter.d.ts.map +1 -0
  62. package/dist/Correlation/ICorrelationIdSetter.js +4 -0
  63. package/dist/Correlation/ICorrelationIdSetter.js.map +1 -0
  64. package/dist/Correlation/index.d.ts +11 -0
  65. package/dist/Correlation/index.d.ts.map +1 -0
  66. package/dist/Correlation/index.js +11 -0
  67. package/dist/Correlation/index.js.map +1 -0
  68. package/dist/EventSequences/EventSequence.d.ts.map +1 -1
  69. package/dist/EventSequences/EventSequence.js +45 -46
  70. package/dist/EventSequences/EventSequence.js.map +1 -1
  71. package/dist/Identity/IIdentityProvider.d.ts +21 -0
  72. package/dist/Identity/IIdentityProvider.d.ts.map +1 -0
  73. package/dist/Identity/IIdentityProvider.js +4 -0
  74. package/dist/Identity/IIdentityProvider.js.map +1 -0
  75. package/dist/Identity/Identity.d.ts +37 -0
  76. package/dist/Identity/Identity.d.ts.map +1 -0
  77. package/dist/Identity/Identity.js +60 -0
  78. package/dist/Identity/Identity.js.map +1 -0
  79. package/dist/Identity/IdentityProvider.d.ts +15 -0
  80. package/dist/Identity/IdentityProvider.d.ts.map +1 -0
  81. package/dist/Identity/IdentityProvider.js +23 -0
  82. package/dist/Identity/IdentityProvider.js.map +1 -0
  83. package/dist/Identity/index.d.ts +10 -0
  84. package/dist/Identity/index.d.ts.map +1 -0
  85. package/dist/Identity/index.js +11 -0
  86. package/dist/Identity/index.js.map +1 -0
  87. package/dist/Projections/Projections.d.ts +9 -1
  88. package/dist/Projections/Projections.d.ts.map +1 -1
  89. package/dist/Projections/Projections.js +70 -19
  90. package/dist/Projections/Projections.js.map +1 -1
  91. package/dist/Projections/declarative/ProjectionBuilderFor.d.ts +6 -0
  92. package/dist/Projections/declarative/ProjectionBuilderFor.d.ts.map +1 -1
  93. package/dist/Projections/declarative/ProjectionBuilderFor.js +18 -2
  94. package/dist/Projections/declarative/ProjectionBuilderFor.js.map +1 -1
  95. package/dist/Projections/declarative/projection.d.ts +5 -1
  96. package/dist/Projections/declarative/projection.d.ts.map +1 -1
  97. package/dist/Projections/declarative/projection.js +3 -2
  98. package/dist/Projections/declarative/projection.js.map +1 -1
  99. package/dist/Projections/modelBound/index.d.ts +0 -2
  100. package/dist/Projections/modelBound/index.d.ts.map +1 -1
  101. package/dist/Projections/modelBound/index.js +0 -1
  102. package/dist/Projections/modelBound/index.js.map +1 -1
  103. package/dist/Projections/modelBound/setFromContext.d.ts +4 -1
  104. package/dist/Projections/modelBound/setFromContext.d.ts.map +1 -1
  105. package/dist/Projections/modelBound/setFromContext.js +12 -7
  106. package/dist/Projections/modelBound/setFromContext.js.map +1 -1
  107. package/dist/Reactors/Reactors.d.ts.map +1 -1
  108. package/dist/Reactors/Reactors.js +1 -0
  109. package/dist/Reactors/Reactors.js.map +1 -1
  110. package/dist/Reducers/Reducers.d.ts +2 -0
  111. package/dist/Reducers/Reducers.d.ts.map +1 -1
  112. package/dist/Reducers/Reducers.js +60 -2
  113. package/dist/Reducers/Reducers.js.map +1 -1
  114. package/dist/Schemas/JsonSchemaGenerator.d.ts.map +1 -1
  115. package/dist/Schemas/JsonSchemaGenerator.js +17 -3
  116. package/dist/Schemas/JsonSchemaGenerator.js.map +1 -1
  117. package/dist/artifacts/DefaultClientArtifactsProvider.d.ts +0 -2
  118. package/dist/artifacts/DefaultClientArtifactsProvider.d.ts.map +1 -1
  119. package/dist/artifacts/DefaultClientArtifactsProvider.js +0 -4
  120. package/dist/artifacts/DefaultClientArtifactsProvider.js.map +1 -1
  121. package/dist/artifacts/IClientArtifactsProvider.d.ts +0 -2
  122. package/dist/artifacts/IClientArtifactsProvider.d.ts.map +1 -1
  123. package/dist/connection/Guid.d.ts +8 -1
  124. package/dist/connection/Guid.d.ts.map +1 -1
  125. package/dist/connection/Guid.js +24 -1
  126. package/dist/connection/Guid.js.map +1 -1
  127. package/dist/index.d.ts +4 -0
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +4 -0
  130. package/dist/index.js.map +1 -1
  131. package/dist/sinks/WellKnownSinks.d.ts +15 -0
  132. package/dist/sinks/WellKnownSinks.d.ts.map +1 -0
  133. package/dist/sinks/WellKnownSinks.js +17 -0
  134. package/dist/sinks/WellKnownSinks.js.map +1 -0
  135. package/dist/sinks/index.d.ts +2 -0
  136. package/dist/sinks/index.d.ts.map +1 -0
  137. package/dist/sinks/index.js +4 -0
  138. package/dist/sinks/index.js.map +1 -0
  139. package/dist/tsconfig.tsbuildinfo +1 -1
  140. package/dist/types/DecoratorType.d.ts +1 -3
  141. package/dist/types/DecoratorType.d.ts.map +1 -1
  142. package/dist/types/DecoratorType.js +0 -2
  143. package/dist/types/DecoratorType.js.map +1 -1
  144. package/index.ts +4 -0
  145. package/package.json +2 -2
  146. package/sinks/WellKnownSinks.ts +21 -0
  147. package/sinks/index.ts +4 -0
  148. package/types/DecoratorType.ts +1 -4
  149. package/Projections/modelBound/modelBound.ts +0 -59
  150. package/dist/Grpc.d.ts +0 -19
  151. package/dist/Grpc.d.ts.map +0 -1
  152. package/dist/Grpc.js +0 -33
  153. package/dist/Grpc.js.map +0 -1
  154. package/dist/Projections/modelBound/modelBound.d.ts +0 -31
  155. package/dist/Projections/modelBound/modelBound.d.ts.map +0 -1
  156. package/dist/Projections/modelBound/modelBound.js +0 -39
  157. package/dist/Projections/modelBound/modelBound.js.map +0 -1
@@ -0,0 +1,29 @@
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 { CausationType } from './CausationType';
5
+
6
+ /**
7
+ * Represents a causation instance.
8
+ */
9
+ export class Causation {
10
+ /**
11
+ * Creates an unknown causation instance.
12
+ * @returns A new {@link Causation} with the current time, type set to {@link CausationType.unknown}, and empty properties.
13
+ */
14
+ static unknown(): Causation {
15
+ return new Causation(new Date(), CausationType.unknown, {});
16
+ }
17
+
18
+ /**
19
+ * Initializes a new instance of the {@link Causation} class.
20
+ * @param occurred - When it occurred.
21
+ * @param type - Type of causation.
22
+ * @param properties - Any properties associated with the causation.
23
+ */
24
+ constructor(
25
+ readonly occurred: Date,
26
+ readonly type: CausationType,
27
+ readonly properties: Readonly<Record<string, string>>
28
+ ) {}
29
+ }
@@ -0,0 +1,51 @@
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 { AsyncLocalStorage } from 'async_hooks';
5
+ import { Causation } from './Causation';
6
+ import { CausationType } from './CausationType';
7
+ import { ICausationManager } from './ICausationManager';
8
+
9
+ /**
10
+ * Implements {@link ICausationManager} using {@link AsyncLocalStorage} to scope the causation chain to the active async call context.
11
+ */
12
+ export class CausationManager implements ICausationManager {
13
+ private readonly _storage = new AsyncLocalStorage<Causation[]>();
14
+ private _root: Causation = new Causation(new Date(), CausationType.root, {});
15
+
16
+ /** @inheritdoc */
17
+ get root(): Causation {
18
+ return this._root;
19
+ }
20
+
21
+ /** @inheritdoc */
22
+ getCurrentChain(): ReadonlyArray<Causation> {
23
+ const chain = this._getOrInitChain();
24
+ return chain;
25
+ }
26
+
27
+ /** @inheritdoc */
28
+ add(type: CausationType, properties: Record<string, string>): void {
29
+ const chain = this._getOrInitChain();
30
+ chain.push(new Causation(new Date(), type, properties));
31
+ }
32
+
33
+ /**
34
+ * Defines the root causation for the current process.
35
+ * @param properties - Properties associated with the root causation.
36
+ */
37
+ defineRoot(properties: Record<string, string>): void {
38
+ this._root = new Causation(new Date(), CausationType.root, properties);
39
+ }
40
+
41
+ private _getOrInitChain(): Causation[] {
42
+ let chain = this._storage.getStore();
43
+ if (chain === undefined) {
44
+ chain = [this._root];
45
+ this._storage.enterWith(chain);
46
+ } else if (chain.length === 0) {
47
+ chain.push(this._root);
48
+ }
49
+ return chain;
50
+ }
51
+ }
@@ -0,0 +1,38 @@
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 a type of causation.
6
+ */
7
+ export class CausationType {
8
+ /**
9
+ * Represents the root causation type.
10
+ */
11
+ static readonly root = new CausationType('Root');
12
+
13
+ /**
14
+ * Represents the unknown causation type.
15
+ */
16
+ static readonly unknown = new CausationType('Unknown');
17
+
18
+ /**
19
+ * Represents the causation type for a single event append via the TypeScript client.
20
+ */
21
+ static readonly appendEvent = new CausationType('TypeScriptClient.Append');
22
+
23
+ /**
24
+ * Represents the causation type for a batch event append via the TypeScript client.
25
+ */
26
+ static readonly appendManyEvents = new CausationType('TypeScriptClient.AppendMany');
27
+
28
+ /**
29
+ * Initializes a new instance of the {@link CausationType} class.
30
+ * @param name - The name of the causation type.
31
+ */
32
+ constructor(readonly name: string) {}
33
+
34
+ /** @inheritdoc */
35
+ toString(): string {
36
+ return this.name;
37
+ }
38
+ }
@@ -0,0 +1,29 @@
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 { Causation } from './Causation';
5
+ import { CausationType } from './CausationType';
6
+
7
+ /**
8
+ * Defines a system that manages causation for the active call context.
9
+ */
10
+ export interface ICausationManager {
11
+ /**
12
+ * Gets the root causation.
13
+ */
14
+ readonly root: Causation;
15
+
16
+ /**
17
+ * Gets the full causation chain for the current call context.
18
+ * The chain always starts with {@link root} if no other causation has been added.
19
+ * @returns An array of {@link Causation} representing the current chain.
20
+ */
21
+ getCurrentChain(): ReadonlyArray<Causation>;
22
+
23
+ /**
24
+ * Adds a causation entry to the current chain.
25
+ * @param type - The type of causation to add.
26
+ * @param properties - Properties associated with the causation.
27
+ */
28
+ add(type: CausationType, properties: Record<string, string>): void;
29
+ }
@@ -0,0 +1,15 @@
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
+ export { Causation } from './Causation';
5
+ export { CausationType } from './CausationType';
6
+ export type { ICausationManager } from './ICausationManager';
7
+ export { CausationManager } from './CausationManager';
8
+
9
+ import { CausationManager } from './CausationManager';
10
+
11
+ /**
12
+ * The default singleton {@link CausationManager} for the process.
13
+ * Use this to manage the causation chain for the current async call context.
14
+ */
15
+ export const causationManager = new CausationManager();
@@ -0,0 +1,33 @@
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 { Guid } from '@cratis/fundamentals';
5
+
6
+ /**
7
+ * Represents a correlation identifier used to track operations across call boundaries.
8
+ */
9
+ export class CorrelationId {
10
+ /**
11
+ * A well-known {@link CorrelationId} representing an unset/empty value.
12
+ */
13
+ static readonly notSet = new CorrelationId('00000000-0000-0000-0000-000000000000');
14
+
15
+ /**
16
+ * Creates a new unique {@link CorrelationId}.
17
+ * @returns A new {@link CorrelationId} backed by a freshly generated GUID.
18
+ */
19
+ static create(): CorrelationId {
20
+ return new CorrelationId(Guid.create().toString());
21
+ }
22
+
23
+ /**
24
+ * Initializes a new instance of the {@link CorrelationId} class.
25
+ * @param value - The string value of the correlation identifier.
26
+ */
27
+ constructor(readonly value: string) {}
28
+
29
+ /** @inheritdoc */
30
+ toString(): string {
31
+ return this.value;
32
+ }
33
+ }
@@ -0,0 +1,30 @@
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 { AsyncLocalStorage } from 'async_hooks';
5
+ import { CorrelationId } from './CorrelationId';
6
+ import { ICorrelationIdAccessor } from './ICorrelationIdAccessor';
7
+ import { ICorrelationIdSetter } from './ICorrelationIdSetter';
8
+
9
+ /**
10
+ * Implements both {@link ICorrelationIdAccessor} and {@link ICorrelationIdSetter},
11
+ * using {@link AsyncLocalStorage} to scope the correlation identifier to the active async call context.
12
+ */
13
+ export class CorrelationIdManager implements ICorrelationIdAccessor, ICorrelationIdSetter {
14
+ private readonly _storage = new AsyncLocalStorage<CorrelationId>();
15
+
16
+ /** @inheritdoc */
17
+ get current(): CorrelationId {
18
+ return this._storage.getStore() ?? CorrelationId.create();
19
+ }
20
+
21
+ /** @inheritdoc */
22
+ setCurrent(correlationId: CorrelationId): void {
23
+ this._storage.enterWith(correlationId);
24
+ }
25
+
26
+ /** @inheritdoc */
27
+ clear(): void {
28
+ this._storage.enterWith(CorrelationId.create());
29
+ }
30
+ }
@@ -0,0 +1,15 @@
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 { CorrelationId } from './CorrelationId';
5
+
6
+ /**
7
+ * Defines the read side of a correlation identifier provider scoped to the active call context.
8
+ */
9
+ export interface ICorrelationIdAccessor {
10
+ /**
11
+ * Gets the current correlation identifier for the active call context.
12
+ * @returns The current {@link CorrelationId}.
13
+ */
14
+ readonly current: CorrelationId;
15
+ }
@@ -0,0 +1,21 @@
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 { CorrelationId } from './CorrelationId';
5
+
6
+ /**
7
+ * Defines the write side of a correlation identifier provider scoped to the active call context.
8
+ */
9
+ export interface ICorrelationIdSetter {
10
+ /**
11
+ * Sets the current correlation identifier for the active call context.
12
+ * @param correlationId - The {@link CorrelationId} to set.
13
+ */
14
+ setCurrent(correlationId: CorrelationId): void;
15
+
16
+ /**
17
+ * Clears the current correlation identifier for the active call context,
18
+ * causing the next access to generate a new unique identifier.
19
+ */
20
+ clear(): void;
21
+ }
@@ -0,0 +1,15 @@
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
+ export { CorrelationId } from './CorrelationId';
5
+ export type { ICorrelationIdAccessor } from './ICorrelationIdAccessor';
6
+ export type { ICorrelationIdSetter } from './ICorrelationIdSetter';
7
+ export { CorrelationIdManager } from './CorrelationIdManager';
8
+
9
+ import { CorrelationIdManager } from './CorrelationIdManager';
10
+
11
+ /**
12
+ * The default singleton {@link CorrelationIdManager} for the process.
13
+ * Use this to get and set the correlation identifier for the current async call context.
14
+ */
15
+ export const correlationIdManager = new CorrelationIdManager();
@@ -1,10 +1,7 @@
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 {
5
- ChronicleConnection,
6
- Guid as ContractsGuid
7
- } from '../connection';
4
+ import { ChronicleConnection } from '../connection';
8
5
  import { SpanStatusCode } from '@opentelemetry/api';
9
6
  import { Guid } from '@cratis/fundamentals';
10
7
  import { getEventTypeFor } from '../Events/eventTypeDecorator';
@@ -16,6 +13,10 @@ import { EventSequenceId } from './EventSequenceId';
16
13
  import { EventSequenceNumber } from './EventSequenceNumber';
17
14
  import { ChronicleTracer } from '../Tracing';
18
15
  import { ChronicleMetrics } from '../Metrics';
16
+ import { identityProvider, Identity } from '../Identity';
17
+ import { causationManager, CausationType } from '../Auditing';
18
+ import { correlationIdManager } from '../Correlation';
19
+ import { toContractsGuid } from '../connection/Guid';
19
20
 
20
21
  /**
21
22
  * Implements {@link IEventSequence} by communicating with the Chronicle Kernel
@@ -33,10 +34,14 @@ export class EventSequence implements IEventSequence {
33
34
  async append(eventSourceId: string, event: object, options?: AppendOptions): Promise<AppendResult> {
34
35
  const eventType = getEventTypeFor(event.constructor as Function);
35
36
  const correlationId = options?.correlationId === undefined
36
- ? Guid.create()
37
+ ? Guid.as(correlationIdManager.current.value)
37
38
  : Guid.as(options.correlationId);
38
39
  const content = JSON.stringify(event);
39
40
 
41
+ causationManager.add(CausationType.appendEvent, { eventType: eventType.id.value });
42
+ const causationChain = causationManager.getCurrentChain();
43
+ const identity = identityProvider.getCurrent();
44
+
40
45
  const metricAttributes = {
41
46
  'chronicle.event_store': this._eventStoreName,
42
47
  'chronicle.namespace': this._namespace,
@@ -68,17 +73,12 @@ export class EventSequence implements IEventSequence {
68
73
  Tombstone: eventType.tombstone
69
74
  },
70
75
  Content: content,
71
- Causation: [{
72
- Occurred: { Value: new Date().toISOString() },
73
- Type: 'TypeScriptClient.Append',
74
- Properties: {}
75
- }],
76
- CausedBy: {
77
- Subject: '5d032c92-9d5e-41eb-947a-ee5314ed0032',
78
- Name: '[System]',
79
- UserName: '[System]',
80
- OnBehalfOf: undefined
81
- },
76
+ Causation: causationChain.map(c => ({
77
+ Occurred: { Value: c.occurred.toISOString() },
78
+ Type: c.type.name,
79
+ Properties: { ...c.properties }
80
+ })),
81
+ CausedBy: toContractsCausedBy(identity),
82
82
  ConcurrencyScope: {
83
83
  // ulong.MaxValue sent as BigInt so the server recognises it as ConcurrencyScope.None (no validation)
84
84
  SequenceNumber: 18446744073709551615n as unknown as number,
@@ -137,9 +137,13 @@ export class EventSequence implements IEventSequence {
137
137
  /** @inheritdoc */
138
138
  async appendMany(eventSourceId: string, events: object[], options?: AppendOptions): Promise<AppendResult[]> {
139
139
  const correlationId = options?.correlationId === undefined
140
- ? Guid.create()
140
+ ? Guid.as(correlationIdManager.current.value)
141
141
  : Guid.as(options.correlationId);
142
142
 
143
+ causationManager.add(CausationType.appendManyEvents, { count: String(events.length) });
144
+ const batchCausationChain = causationManager.getCurrentChain();
145
+ const identity = identityProvider.getCurrent();
146
+
143
147
  const eventsToAppend = events.map(event => {
144
148
  const eventType = getEventTypeFor(event.constructor as Function);
145
149
  return {
@@ -153,17 +157,12 @@ export class EventSequence implements IEventSequence {
153
157
  Tombstone: eventType.tombstone
154
158
  },
155
159
  Content: JSON.stringify(event),
156
- Causation: [{
157
- Occurred: { Value: new Date().toISOString() },
158
- Type: 'TypeScriptClient.AppendMany.Event',
159
- Properties: {}
160
- }],
161
- CausedBy: {
162
- Subject: '5d032c92-9d5e-41eb-947a-ee5314ed0032',
163
- Name: '[System]',
164
- UserName: '[System]',
165
- OnBehalfOf: undefined
166
- },
160
+ Causation: batchCausationChain.map(c => ({
161
+ Occurred: { Value: c.occurred.toISOString() },
162
+ Type: c.type.name,
163
+ Properties: { ...c.properties }
164
+ })),
165
+ CausedBy: toContractsCausedBy(identity),
167
166
  ConcurrencyScope: {
168
167
  SequenceNumber: 18446744073709551615n as unknown as number,
169
168
  EventSourceId: false,
@@ -199,17 +198,12 @@ export class EventSequence implements IEventSequence {
199
198
  EventSequenceId: this.id.value,
200
199
  CorrelationId: toContractsGuid(correlationId),
201
200
  Events: eventsToAppend,
202
- Causation: [{
203
- Occurred: { Value: new Date().toISOString() },
204
- Type: 'TypeScriptClient.AppendMany.Batch',
205
- Properties: {}
206
- }],
207
- CausedBy: {
208
- Subject: '5d032c92-9d5e-41eb-947a-ee5314ed0032',
209
- Name: '[System]',
210
- UserName: '[System]',
211
- OnBehalfOf: undefined
212
- },
201
+ Causation: batchCausationChain.map(c => ({
202
+ Occurred: { Value: c.occurred.toISOString() },
203
+ Type: c.type.name,
204
+ Properties: { ...c.properties }
205
+ })),
206
+ CausedBy: toContractsCausedBy(identity),
213
207
  ConcurrencyScopes: {}
214
208
  });
215
209
 
@@ -355,18 +349,22 @@ export class EventSequence implements IEventSequence {
355
349
  * @param guid - The Guid to convert.
356
350
  * @returns The converted protobuf Guid with fixed64-safe hi/lo values.
357
351
  */
358
- function toContractsGuid(guid: Guid): ContractsGuid {
359
- const hex = guid.toString().replace(/-/g, '');
360
- const hi = BigInt(`0x${hex.substring(0, 16)}`);
361
- const lo = BigInt(`0x${hex.substring(16, 32)}`);
362
352
 
363
- // Mask to MAX_SAFE_INTEGER so the contracts package can decode the Guid
364
- // from the response without a longToNumber overflow. Correlation IDs are
365
- // opaque identifiers, so 52+52 bits of entropy is more than sufficient.
366
- const safe = BigInt(Number.MAX_SAFE_INTEGER);
367
- return {
368
- hi: Number(hi & safe),
369
- lo: Number(lo & safe)
353
+ /**
354
+ * Converts an {@link Identity} into the CausedBy shape used by Chronicle contracts.
355
+ * @param identity - The identity to convert.
356
+ * @returns The contracts CausedBy object.
357
+ */
358
+ function toContractsCausedBy(identity: Identity): object {
359
+ const result: Record<string, unknown> = {
360
+ Subject: identity.subject,
361
+ Name: identity.name,
362
+ UserName: identity.userName,
363
+ OnBehalfOf: undefined
370
364
  };
365
+ if (identity.onBehalfOf !== undefined) {
366
+ result.OnBehalfOf = toContractsCausedBy(identity.onBehalfOf);
367
+ }
368
+ return result;
371
369
  }
372
370
 
@@ -0,0 +1,26 @@
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 { Identity } from './Identity';
5
+
6
+ /**
7
+ * Defines a system that can provide and manage the current {@link Identity} for the active call context.
8
+ */
9
+ export interface IIdentityProvider {
10
+ /**
11
+ * Gets the current identity for the active call context.
12
+ * @returns The current {@link Identity}, or {@link Identity.system} if none has been set.
13
+ */
14
+ getCurrent(): Identity;
15
+
16
+ /**
17
+ * Sets the current identity for the active call context.
18
+ * @param identity - The {@link Identity} to set.
19
+ */
20
+ setCurrentIdentity(identity: Identity): void;
21
+
22
+ /**
23
+ * Clears the current identity for the active call context, resetting it to {@link Identity.system}.
24
+ */
25
+ clearCurrentIdentity(): void;
26
+ }
@@ -0,0 +1,61 @@
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 an identity of something that is responsible for causing a state change.
6
+ * An identity can be a user, a system, a service or anything else that can be identified.
7
+ */
8
+ export class Identity {
9
+ /**
10
+ * The identity used when not set.
11
+ */
12
+ static readonly notSet = new Identity('1efc9b81-0612-4466-962c-86acc4e9a028', '[Not Set]', '[Not Set]');
13
+
14
+ /**
15
+ * The identity used when the identity is not known.
16
+ */
17
+ static readonly unknown = new Identity('3321cf62-db16-425e-8173-99fcfefe11dd', '[Unknown]', '[Unknown]');
18
+
19
+ /**
20
+ * The identity used when the system is the cause.
21
+ */
22
+ static readonly system = new Identity('5d032c92-9d5e-41eb-947a-ee5314ed0032', '[System]', '[System]');
23
+
24
+ /**
25
+ * Initializes a new instance of the {@link Identity} class.
26
+ * @param subject - The identifier of the identity, referred to as subject.
27
+ * @param name - Name of the identity.
28
+ * @param userName - Optional username, defaults to empty string.
29
+ * @param onBehalfOf - Optional behalf of {@link Identity}.
30
+ */
31
+ constructor(
32
+ readonly subject: string,
33
+ readonly name: string,
34
+ readonly userName: string = '',
35
+ readonly onBehalfOf?: Identity
36
+ ) {}
37
+
38
+ /**
39
+ * Returns a new {@link Identity} chain with duplicate subjects removed.
40
+ * The first occurrence of each subject is kept.
41
+ * @returns A new {@link Identity} with duplicates removed from the chain.
42
+ */
43
+ withoutDuplicates(): Identity {
44
+ const seen = new Set<string>();
45
+ const chain: Identity[] = [];
46
+ let current: Identity | undefined = this;
47
+ while (current !== undefined) {
48
+ if (!seen.has(current.subject)) {
49
+ seen.add(current.subject);
50
+ chain.push(current);
51
+ }
52
+ current = current.onBehalfOf;
53
+ }
54
+
55
+ let result: Identity | undefined;
56
+ for (let i = chain.length - 1; i >= 0; i--) {
57
+ result = new Identity(chain[i].subject, chain[i].name, chain[i].userName, result);
58
+ }
59
+ return result!;
60
+ }
61
+ }
@@ -0,0 +1,28 @@
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 { AsyncLocalStorage } from 'async_hooks';
5
+ import { Identity } from './Identity';
6
+ import { IIdentityProvider } from './IIdentityProvider';
7
+
8
+ /**
9
+ * Implements {@link IIdentityProvider} using {@link AsyncLocalStorage} to scope the identity to the active async call context.
10
+ */
11
+ export class IdentityProvider implements IIdentityProvider {
12
+ private readonly _storage = new AsyncLocalStorage<Identity>();
13
+
14
+ /** @inheritdoc */
15
+ getCurrent(): Identity {
16
+ return this._storage.getStore() ?? Identity.system;
17
+ }
18
+
19
+ /** @inheritdoc */
20
+ setCurrentIdentity(identity: Identity): void {
21
+ this._storage.enterWith(identity);
22
+ }
23
+
24
+ /** @inheritdoc */
25
+ clearCurrentIdentity(): void {
26
+ this._storage.enterWith(Identity.system);
27
+ }
28
+ }
@@ -0,0 +1,14 @@
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
+ export { Identity } from './Identity';
5
+ export type { IIdentityProvider } from './IIdentityProvider';
6
+ export { IdentityProvider } from './IdentityProvider';
7
+
8
+ import { IdentityProvider } from './IdentityProvider';
9
+
10
+ /**
11
+ * The default singleton {@link IdentityProvider} for the process.
12
+ * Use this to get and set the identity for the current async call context.
13
+ */
14
+ export const identityProvider = new IdentityProvider();