Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Source/Auditing/Causation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { CausationType } from './CausationType';

/**
* Represents a causation instance.
*/
export class Causation {
/**
* Creates an unknown causation instance.
* @returns A new {@link Causation} with the current time, type set to {@link CausationType.unknown}, and empty properties.
*/
static unknown(): Causation {
return new Causation(new Date(), CausationType.unknown, {});
}

/**
* Initializes a new instance of the {@link Causation} class.
* @param occurred - When it occurred.
* @param type - Type of causation.
* @param properties - Any properties associated with the causation.
*/
constructor(
readonly occurred: Date,
readonly type: CausationType,
readonly properties: Readonly<Record<string, string>>
) {}
}
51 changes: 51 additions & 0 deletions Source/Auditing/CausationManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { AsyncLocalStorage } from 'async_hooks';
import { Causation } from './Causation';
import { CausationType } from './CausationType';
import { ICausationManager } from './ICausationManager';

/**
* Implements {@link ICausationManager} using {@link AsyncLocalStorage} to scope the causation chain to the active async call context.
*/
export class CausationManager implements ICausationManager {
private readonly _storage = new AsyncLocalStorage<Causation[]>();
private _root: Causation = new Causation(new Date(), CausationType.root, {});

/** @inheritdoc */
get root(): Causation {
return this._root;
}

/** @inheritdoc */
getCurrentChain(): ReadonlyArray<Causation> {
const chain = this._getOrInitChain();
return chain;
}

/** @inheritdoc */
add(type: CausationType, properties: Record<string, string>): void {
const chain = this._getOrInitChain();
chain.push(new Causation(new Date(), type, properties));
}

/**
* Defines the root causation for the current process.
* @param properties - Properties associated with the root causation.
*/
defineRoot(properties: Record<string, string>): void {
this._root = new Causation(new Date(), CausationType.root, properties);
}

private _getOrInitChain(): Causation[] {
let chain = this._storage.getStore();
if (chain === undefined) {
chain = [this._root];
this._storage.enterWith(chain);
} else if (chain.length === 0) {
chain.push(this._root);
}
return chain;
}
}
38 changes: 38 additions & 0 deletions Source/Auditing/CausationType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

/**
* Represents a type of causation.
*/
export class CausationType {
/**
* Represents the root causation type.
*/
static readonly root = new CausationType('Root');

/**
* Represents the unknown causation type.
*/
static readonly unknown = new CausationType('Unknown');

/**
* Represents the causation type for a single event append via the TypeScript client.
*/
static readonly appendEvent = new CausationType('TypeScriptClient.Append');

/**
* Represents the causation type for a batch event append via the TypeScript client.
*/
static readonly appendManyEvents = new CausationType('TypeScriptClient.AppendMany');

/**
* Initializes a new instance of the {@link CausationType} class.
* @param name - The name of the causation type.
*/
constructor(readonly name: string) {}

/** @inheritdoc */
toString(): string {
return this.name;
}
}
29 changes: 29 additions & 0 deletions Source/Auditing/ICausationManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { Causation } from './Causation';
import { CausationType } from './CausationType';

/**
* Defines a system that manages causation for the active call context.
*/
export interface ICausationManager {
/**
* Gets the root causation.
*/
readonly root: Causation;

/**
* Gets the full causation chain for the current call context.
* The chain always starts with {@link root} if no other causation has been added.
* @returns An array of {@link Causation} representing the current chain.
*/
getCurrentChain(): ReadonlyArray<Causation>;

/**
* Adds a causation entry to the current chain.
* @param type - The type of causation to add.
* @param properties - Properties associated with the causation.
*/
add(type: CausationType, properties: Record<string, string>): void;
}
15 changes: 15 additions & 0 deletions Source/Auditing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

export { Causation } from './Causation';
export { CausationType } from './CausationType';
export type { ICausationManager } from './ICausationManager';
export { CausationManager } from './CausationManager';

import { CausationManager } from './CausationManager';

/**
* The default singleton {@link CausationManager} for the process.
* Use this to manage the causation chain for the current async call context.
*/
export const causationManager = new CausationManager();
33 changes: 33 additions & 0 deletions Source/Correlation/CorrelationId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { Guid } from '@cratis/fundamentals';

/**
* Represents a correlation identifier used to track operations across call boundaries.
*/
export class CorrelationId {
/**
* A well-known {@link CorrelationId} representing an unset/empty value.
*/
static readonly notSet = new CorrelationId('00000000-0000-0000-0000-000000000000');

/**
* Creates a new unique {@link CorrelationId}.
* @returns A new {@link CorrelationId} backed by a freshly generated GUID.
*/
static create(): CorrelationId {
return new CorrelationId(Guid.create().toString());
}

/**
* Initializes a new instance of the {@link CorrelationId} class.
* @param value - The string value of the correlation identifier.
*/
constructor(readonly value: string) {}

/** @inheritdoc */
toString(): string {
return this.value;
}
}
30 changes: 30 additions & 0 deletions Source/Correlation/CorrelationIdManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { AsyncLocalStorage } from 'async_hooks';
import { CorrelationId } from './CorrelationId';
import { ICorrelationIdAccessor } from './ICorrelationIdAccessor';
import { ICorrelationIdSetter } from './ICorrelationIdSetter';

/**
* Implements both {@link ICorrelationIdAccessor} and {@link ICorrelationIdSetter},
* using {@link AsyncLocalStorage} to scope the correlation identifier to the active async call context.
*/
export class CorrelationIdManager implements ICorrelationIdAccessor, ICorrelationIdSetter {
private readonly _storage = new AsyncLocalStorage<CorrelationId>();

/** @inheritdoc */
get current(): CorrelationId {
return this._storage.getStore() ?? CorrelationId.create();
}

/** @inheritdoc */
setCurrent(correlationId: CorrelationId): void {
this._storage.enterWith(correlationId);
}

/** @inheritdoc */
clear(): void {
this._storage.enterWith(CorrelationId.create());
}
}
15 changes: 15 additions & 0 deletions Source/Correlation/ICorrelationIdAccessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { CorrelationId } from './CorrelationId';

/**
* Defines the read side of a correlation identifier provider scoped to the active call context.
*/
export interface ICorrelationIdAccessor {
/**
* Gets the current correlation identifier for the active call context.
* @returns The current {@link CorrelationId}.
*/
readonly current: CorrelationId;
}
21 changes: 21 additions & 0 deletions Source/Correlation/ICorrelationIdSetter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { CorrelationId } from './CorrelationId';

/**
* Defines the write side of a correlation identifier provider scoped to the active call context.
*/
export interface ICorrelationIdSetter {
/**
* Sets the current correlation identifier for the active call context.
* @param correlationId - The {@link CorrelationId} to set.
*/
setCurrent(correlationId: CorrelationId): void;

/**
* Clears the current correlation identifier for the active call context,
* causing the next access to generate a new unique identifier.
*/
clear(): void;
}
15 changes: 15 additions & 0 deletions Source/Correlation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

export { CorrelationId } from './CorrelationId';
export type { ICorrelationIdAccessor } from './ICorrelationIdAccessor';
export type { ICorrelationIdSetter } from './ICorrelationIdSetter';
export { CorrelationIdManager } from './CorrelationIdManager';

import { CorrelationIdManager } from './CorrelationIdManager';

/**
* The default singleton {@link CorrelationIdManager} for the process.
* Use this to get and set the correlation identifier for the current async call context.
*/
export const correlationIdManager = new CorrelationIdManager();
Loading