first commit

This commit is contained in:
2026-10-03 13:40:26 +02:00
parent 732a5b5a16
commit 566f415fa7
555 changed files with 62704 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
# api-shared-application
Contrato del event store y reconstrucción de agregados.
- **Import**: `@task-stream/api/shared/application`
- **Capa**: `application` · **Tags**: `scope:api-shared`, `type:application`
## Contenido
- `EventStore` (puerto): `persist` y `getEventsByStreamId`.
- `AggregateRehydrator`: carga los eventos de un stream y devuelve el agregado reconstruido, ya enlazado con el `EventPublisher`.
- `SharedModule.withInfrastructure(...)`: módulo global que expone el event store y el rehydrator a todos los contextos.
## Comandos
```sh
npx nx lint api-shared-application
npx nx test api-shared-application # Jest
```
Reglas de dependencia entre capas: [Arquitectura](../../../../docs/02-arquitectura.md#21-capas-hexagonales).

View File

@@ -0,0 +1,3 @@
import baseConfig from '../../../../eslint.config.mjs';
export default [...baseConfig];

View File

@@ -0,0 +1,11 @@
module.exports = {
displayName: 'api-shared-application',
preset: '../../../../jest.preset.js',
testEnvironment: 'node',
passWithNoTests: true,
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../../../coverage/libs/api/shared/application',
};

View File

@@ -0,0 +1,12 @@
{
"name": "api-shared-application",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/api/shared/application/src",
"projectType": "library",
"tags": [
"scope:api-shared",
"type:application"
],
"// targets": "to see all targets run: nx show project api-shared-application --web",
"targets": {}
}

View File

@@ -0,0 +1,3 @@
export * from './lib/ports/event-store';
export * from './lib/aggregate-rehydrator';
export * from './lib/shared.module';

View File

@@ -0,0 +1,32 @@
import { Injectable, Type } from '@nestjs/common';
import { EventPublisher } from '@nestjs/cqrs';
import {
NotFoundError,
VersionedAggregateRoot,
} from '@task-stream/api/shared/domain';
import { EventStore } from './ports/event-store';
@Injectable()
export class AggregateRehydrator {
constructor(
private readonly eventStore: EventStore,
private readonly eventPublisher: EventPublisher,
) {}
async rehydrate<T extends VersionedAggregateRoot>(
aggregateId: string,
AggregateCls: Type<T>,
): Promise<T> {
const events = await this.eventStore.getEventsByStreamId(aggregateId);
if (events.length === 0) {
throw new NotFoundError(AggregateCls.name, aggregateId);
}
const AggregateClsWithDispatcher =
this.eventPublisher.mergeClassContext(AggregateCls);
const aggregate = new AggregateClsWithDispatcher(aggregateId);
aggregate.loadFromHistory(events);
return aggregate;
}
}

View File

@@ -0,0 +1,10 @@
import { SerializableEvent } from '@task-stream/api/shared/domain';
/** Append-only store of domain events (the write model). */
export abstract class EventStore {
/** Fails with ConcurrencyError if another process appended to the stream first. */
abstract persist(
eventOrEvents: SerializableEvent | SerializableEvent[],
): Promise<void>;
abstract getEventsByStreamId(streamId: string): Promise<SerializableEvent[]>;
}

View File

@@ -0,0 +1,23 @@
import { DynamicModule, Global, Module, Type } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { AggregateRehydrator } from './aggregate-rehydrator';
/**
* Shared building blocks for every bounded context: the event store (from
* the infrastructure module plugged in by the composition root) and the
* AggregateRehydrator.
*/
@Global()
@Module({})
export class SharedModule {
static withInfrastructure(
infrastructureModule: Type | DynamicModule,
): DynamicModule {
return {
module: SharedModule,
imports: [CqrsModule, infrastructureModule],
providers: [AggregateRehydrator],
exports: [infrastructureModule, AggregateRehydrator],
};
}
}

View File

@@ -0,0 +1,23 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"importHelpers": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@@ -0,0 +1,23 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"],
"target": "es2021",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strictNullChecks": true,
"noImplicitAny": true,
"strictBindCallApply": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts"],
"exclude": [
"jest.config.ts",
"jest.config.cts",
"src/**/*.spec.ts",
"src/**/*.test.ts"
]
}

View File

@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"module": "commonjs",
"moduleResolution": "bundler",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"jest.config.cts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}