Skip to content
Open
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
58 changes: 58 additions & 0 deletions .github/workflows/client-snippets.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Client Snippet Verification

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
workflow_dispatch:

pull_request:
branches:
- "**"
paths:
- "Documentation/client-snippets/**"
- "Documentation/validate-client-snippets.py"
- "Source/**"
- ".github/workflows/client-snippets.yml"

push:
branches: ["main"]
paths:
- "Documentation/client-snippets/**"
- "Documentation/validate-client-snippets.py"
- "Source/**"
- ".github/workflows/client-snippets.yml"

permissions:
contents: read

jobs:
verify:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 23.x
registry-url: "https://registry.npmjs.org"

- uses: actions/cache@v4
id: yarn-cache
with:
path: |
.yarn/cache
**/node_modules
**/.eslintcache
**/yarn.lock
key: ${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }}

- name: Yarn install
run: yarn

- name: Compile client snippets
run: python3 Documentation/validate-client-snippets.py
27 changes: 27 additions & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Trigger documentation build

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
workflow_dispatch:

push:
branches: ["main"]
paths:
- "Documentation/**"

permissions:
contents: read

jobs:
trigger:
runs-on: ubuntu-latest
steps:
- name: Trigger Documentation Build
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.PAT_DOCUMENTATION }}
repository: Cratis/Documentation
event-type: build-docs
Binary file not shown.
2 changes: 1 addition & 1 deletion Documentation/auditing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Auditing — Causation

Causation records *why* an event was appended — a chain of reasons from the root of a business operation down to the individual command or handler that triggered the append. This chain is stored with every event and enables full audit-trail tracing.
See [Correlation, identity, and causation](/chronicle/concepts/correlation-identity-causation/) for what causation means and why Chronicle tracks it. This page covers the TypeScript-specific API in depth. The causation chain is stored with every event and enables full audit-trail tracing.

## Concepts

Expand Down
13 changes: 13 additions & 0 deletions Documentation/client-snippets/coming-from-crud/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
```typescript
import { eventType } from '@cratis/chronicle';

@eventType()
class CrudComparisonCustomerRegistered {
constructor(readonly name: string, readonly address: string) {}
}

@eventType()
class CrudComparisonAddressChanged {
constructor(readonly address: string) {}
}
```
15 changes: 15 additions & 0 deletions Documentation/client-snippets/coming-from-crud/read-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```typescript
import { count, fromEvent, Guid, readModel } from '@cratis/chronicle';

@readModel()
@fromEvent(CrudComparisonCustomerRegistered)
@fromEvent(CrudComparisonAddressChanged)
class CrudComparisonCustomerCard {
id: Guid = Guid.empty;
name = '';
address = '';

@count(CrudComparisonAddressChanged)
timesRelocated = 0;
}
```
12 changes: 12 additions & 0 deletions Documentation/client-snippets/coming-from-crud/write-and-read.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
```typescript
import { IEventStore } from '@cratis/chronicle';

class CrudComparisonCustomerAddressUpdater {
constructor(private readonly store: IEventStore) {}

async changeAddress(customerId: string, newAddress: string): Promise<CrudComparisonCustomerCard> {
await this.store.eventLog.append(customerId, new CrudComparisonAddressChanged(newAddress));
return this.store.readModels.getInstanceById(CrudComparisonCustomerCard, customerId);
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```typescript
import { pii } from '@cratis/chronicle';
import { ConceptAs } from '@cratis/fundamentals';

@pii()
class ComplianceClientPersonName extends ConceptAs<string> {
constructor(value: string) {
super(value);
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```typescript
import { eventType, pii } from '@cratis/chronicle';

@eventType()
class ComplianceClientEmployeeRegistered {
@pii() firstName = '';
@pii() lastName = '';
department = '';
startDate = new Date();
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```typescript
import { pii } from '@cratis/chronicle';
import { ConceptAs } from '@cratis/fundamentals';

@pii()
class PiiConceptsPersonName extends ConceptAs<string> {
constructor(value: string) {
super(value);
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```typescript
import { pii } from '@cratis/chronicle';
import { ConceptAs } from '@cratis/fundamentals';

@pii('National ID number — sensitive personal identifier')
class PiiConceptsNationalIdNumber extends ConceptAs<string> {
constructor(value: string) {
super(value);
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```typescript
import { pii } from '@cratis/chronicle';
import { ConceptAs } from '@cratis/fundamentals';

@pii('Collected under GDPR Art. 6(1)(b) — necessary for contract performance. Retention: contract duration + 7 years.')
class PiiConceptsLegalName extends ConceptAs<string> {
constructor(value: string) {
super(value);
}
}
```
11 changes: 11 additions & 0 deletions Documentation/client-snippets/compliance/pii/concept-level.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```typescript
import { pii } from '@cratis/chronicle';
import { ConceptAs } from '@cratis/fundamentals';

@pii()
class PiiAttrConceptPersonName extends ConceptAs<string> {
constructor(value: string) {
super(value);
}
}
```
11 changes: 11 additions & 0 deletions Documentation/client-snippets/compliance/pii/legal-name-details.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```typescript
import { pii } from '@cratis/chronicle';
import { ConceptAs } from '@cratis/fundamentals';

@pii('Full legal name — required for contract identification')
class PiiAttrLegalName extends ConceptAs<string> {
constructor(value: string) {
super(value);
}
}
```
10 changes: 10 additions & 0 deletions Documentation/client-snippets/compliance/pii/property-level.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
```typescript
import { eventType, pii } from '@cratis/chronicle';

@eventType()
class PiiAttrEmployeeRegistered {
@pii() firstName = '';
@pii() lastName = '';
department = '';
}
```
11 changes: 11 additions & 0 deletions Documentation/client-snippets/compliance/pii/with-details.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```typescript
import { pii } from '@cratis/chronicle';
import { ConceptAs } from '@cratis/fundamentals';

@pii('Collected under GDPR Art. 6(1)(b) — necessary for contract performance')
class PiiAttrPersonName extends ConceptAs<string> {
constructor(value: string) {
super(value);
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
```typescript
import { causationManager, CausationType } from '@cratis/chronicle';

class CorrelationIdentityCausationCausation {
recordPlaceOrder(orderId: string): void {
causationManager.add(new CausationType('MyApp.Commands.PlaceOrder'), { orderId });
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
```typescript
import { correlationIdManager, CorrelationId } from '@cratis/chronicle';

class CorrelationIdentityCausationCorrelation {
getCurrent(): CorrelationId {
return correlationIdManager.current;
}

setForRequest(): void {
correlationIdManager.setCurrent(CorrelationId.create());
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
```typescript
import { identityProvider, Identity } from '@cratis/chronicle';

class CorrelationIdentityCausationIdentity {
setForRequest(subject: string, name: string, userName: string): void {
identityProvider.setCurrentIdentity(new Identity(subject, name, userName));
}

getCurrent(): Identity {
return identityProvider.getCurrent();
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
```typescript
import { IEventStore } from '@cratis/chronicle';

class DesigningReadModelsCustomerListItem {
constructor(
readonly id: string,
readonly name: string
) {}
}

class DesigningReadModelsCustomerListService {
constructor(private readonly store: IEventStore) {}

// Strongly consistent — Chronicle replays the read model's events on demand
getAllStronglyConsistent(): Promise<DesigningReadModelsCustomerListItem[]> {
return this.store.readModels.getInstances(DesigningReadModelsCustomerListItem);
}

// Eventually consistent — a page of materialized instances straight from storage
getPageEventuallyConsistent(): Promise<DesigningReadModelsCustomerListItem[]> {
return this.store.readModels.materialized.getInstances(DesigningReadModelsCustomerListItem, 0, 20);
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
```typescript
import { IEventStore } from '@cratis/chronicle';

class DesigningReadModelsCustomerDetail {
constructor(
readonly id: string,
readonly name: string
) {}
}

class DesigningReadModelsCustomerDetailService {
constructor(private readonly store: IEventStore) {}

getDetail(customerId: string): Promise<DesigningReadModelsCustomerDetail> {
return this.store.readModels.getInstanceById(DesigningReadModelsCustomerDetail, customerId);
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
```typescript
import { eventType, eventTypeMigration, IEventTypeMigration, IEventMigrationBuilder } from '@cratis/chronicle';

@eventType()
class MigrationsCombineShippingAddressRecordedV1 {
constructor(
readonly street: string,
readonly city: string
) {}
}

@eventType('shipping-address-recorded', 2)
class MigrationsCombineShippingAddressRecorded {
constructor(readonly formattedAddress: string) {}
}

@eventTypeMigration(MigrationsCombineShippingAddressRecorded, MigrationsCombineShippingAddressRecordedV1)
class MigrationsCombineShippingAddressRecordedMigration implements IEventTypeMigration<MigrationsCombineShippingAddressRecorded, MigrationsCombineShippingAddressRecordedV1> {
upcast(builder: IEventMigrationBuilder<MigrationsCombineShippingAddressRecorded, MigrationsCombineShippingAddressRecordedV1>): void {
builder.properties(pb => pb
.combine('formattedAddress', ' ', 'street', 'city')); // Joins with space separator
}

downcast(builder: IEventMigrationBuilder<MigrationsCombineShippingAddressRecordedV1, MigrationsCombineShippingAddressRecorded>): void {
builder.properties(pb => pb
.split('street', 'formattedAddress', ' ', 0)
.split('city', 'formattedAddress', ' ', 1));
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
```typescript
import { eventType, eventTypeMigration, IEventTypeMigration, IEventMigrationBuilder } from '@cratis/chronicle';

@eventType()
class MigrationsDefaultValueOrderShippedV1 {
constructor(readonly trackingNumber: string) {}
}

@eventType('order-shipped', 2)
class MigrationsDefaultValueOrderShipped {
constructor(
readonly trackingNumber: string,
readonly retryCount: number,
readonly description: string
) {}
}

@eventTypeMigration(MigrationsDefaultValueOrderShipped, MigrationsDefaultValueOrderShippedV1)
class MigrationsDefaultValueOrderShippedMigration implements IEventTypeMigration<MigrationsDefaultValueOrderShipped, MigrationsDefaultValueOrderShippedV1> {
upcast(builder: IEventMigrationBuilder<MigrationsDefaultValueOrderShipped, MigrationsDefaultValueOrderShippedV1>): void {
builder.properties(pb => pb
.defaultValue('retryCount', 42)
.defaultValue('description', 'default string'));
}

downcast(_builder: IEventMigrationBuilder<MigrationsDefaultValueOrderShippedV1, MigrationsDefaultValueOrderShipped>): void {
// retryCount and description did not exist in generation 1 — nothing to map back
}
}
```
Loading
Loading