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
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,14 @@
"@nestjs/testing": "11.1.6",
"@swc-node/register": "^1.11.1",
"@swc/core": "1.13.5",
"@swc/jest": "0.2.39",
"@types/jest": "30.0.0",
"@types/node": "22.13.4",
"jest": "30.1.3",
"jest-mock": "30.0.5",
"@vitest/coverage-v8": "^4.0.0",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.2",
"turbo": "^2.5.6",
"typescript": "5.9.2"
"typescript": "5.9.2",
"unplugin-swc": "^1.5.7",
"vitest": "^4.0.0"
},
"repository": {
"type": "git",
Expand Down
44 changes: 0 additions & 44 deletions packages/config/jest/base.js

This file was deleted.

6 changes: 5 additions & 1 deletion packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
"name": "@ocoda/event-sourcing-config",
"version": "1.0.0",
"private": true,
"license": "MIT"
"license": "MIT",
"exports": {
"./vitest/base": "./vitest/base.mjs",
"./typescript/*": "./typescript/*"
}
}
40 changes: 40 additions & 0 deletions packages/config/vitest/base.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';

export default defineConfig({
plugins: [
swc.vite({
jsc: {
parser: { syntax: 'typescript', decorators: true },
transform: { legacyDecorator: true, decoratorMetadata: true },
target: 'es2016',
keepClassNames: true,
},
module: { type: 'es6' },
}),
],
test: {
globals: true,
environment: 'node',
include: ['tests/**/*.spec.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov', 'json-summary'],
include: ['lib/**/*.ts'],
exclude: [
'lib/**/*.d.ts',
'lib/**/index.ts',
'lib/**/*.interface.ts',
'lib/**/*.type.ts',
'lib/**/*.enum.ts',
'lib/**/*.constants.ts',
],
thresholds: {
lines: 90,
functions: 90,
branches: 80,
statements: 90,
},
},
},
});
2 changes: 1 addition & 1 deletion packages/core/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ tests
.turbo
biome.json
docker-compose.yml
jest.config.js
vitest.config.mts
pnpm-lock.yaml
renovate.json
tsconfig.build.json
Expand Down
8 changes: 0 additions & 8 deletions packages/core/jest.config.js

This file was deleted.

7 changes: 3 additions & 4 deletions packages/core/lib/command-bus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'reflect-metadata';
import { Injectable, type Type } from '@nestjs/common';
import type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';

import {
CommandHandlerNotFoundException,
Expand All @@ -9,7 +8,7 @@ import {
MissingCommandMetadataException,
} from './exceptions';
import { DefaultCommandPubSub, ObservableBus, getCommandHandlerMetadata, getCommandMetadata } from './helpers';
import type { ICommand, ICommandBus, ICommandHandler, ICommandPublisher } from './interfaces';
import type { ICommand, ICommandBus, ICommandHandler, ICommandPublisher, ProviderWrapper } from './interfaces';

@Injectable()
export class CommandBus<CommandBase extends ICommand = ICommand>
Expand Down Expand Up @@ -50,12 +49,12 @@ export class CommandBus<CommandBase extends ICommand = ICommand>
return id;
}

register(handlers: InstanceWrapper<ICommandHandler>[] = []) {
register(handlers: ProviderWrapper<ICommandHandler>[] = []) {
for (const handler of handlers) {
this.registerHandler(handler);
}
}
protected registerHandler(handler: InstanceWrapper<ICommandHandler>) {
protected registerHandler(handler: ProviderWrapper<ICommandHandler>) {
// get the metadata from the handler
const { metatype, instance } = handler;

Expand Down
11 changes: 5 additions & 6 deletions packages/core/lib/event-bus.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Injectable, type OnModuleDestroy, type Type } from '@nestjs/common';
import type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
import { type Observable, type Subscription, from } from 'rxjs';
import { filter, mergeMap } from 'rxjs/operators';

import { MissingEventMetadataException, MissingEventSubscriberMetadataException } from './exceptions';
import { ObservableBus, getEventMetadata, getEventSubscriberMetadata } from './helpers';
import { DefaultEventPubSub } from './helpers/default-event-publisher';
import type { IEventBus, IEventPublisher, IEventSubscriber } from './interfaces';
import type { IEventBus, IEventPublisher, IEventSubscriber, ProviderWrapper } from './interfaces';
import type { EventEnvelope } from './models';

@Injectable()
Expand Down Expand Up @@ -46,24 +45,24 @@ export class EventBus extends ObservableBus<EventEnvelope> implements IEventBus,
return this.subject$.pipe(filter(({ event }) => event === eventName));
}

registerPublishers(publishers: InstanceWrapper<IEventPublisher>[] = []) {
registerPublishers(publishers: ProviderWrapper<IEventPublisher>[] = []) {
for (const publisher of publishers) {
this.registerPublisher(publisher);
}
}
registerSubscribers(subscribers: InstanceWrapper<IEventSubscriber>[] = []) {
registerSubscribers(subscribers: ProviderWrapper<IEventSubscriber>[] = []) {
for (const subscriber of subscribers) {
this.registerSubscriber(subscriber);
}
}

protected registerPublisher(handler: InstanceWrapper<IEventPublisher>) {
protected registerPublisher(handler: ProviderWrapper<IEventPublisher>) {
const { instance } = handler;
if (!instance) return;

this.addPublisher(instance as IEventPublisher);
}
protected registerSubscriber(handler: InstanceWrapper<IEventSubscriber>) {
protected registerSubscriber(handler: ProviderWrapper<IEventSubscriber>) {
const { metatype, instance } = handler;
if (!metatype || !instance) {
throw new MissingEventSubscriberMetadataException(metatype as Type<IEventSubscriber>);
Expand Down
5 changes: 2 additions & 3 deletions packages/core/lib/event-map.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Injectable, type Type } from '@nestjs/common';
import type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
import {
MissingEventMetadataException,
UnregisteredEventException,
UnregisteredSerializerException,
} from './exceptions';
import { DefaultEventSerializer, getEventMetadata, getEventSerializerMetadata } from './helpers';
import type { IEvent, IEventPayload, IEventSerializer } from './interfaces';
import type { IEvent, IEventPayload, IEventSerializer, ProviderWrapper } from './interfaces';

export type EventSerializerType = Type<IEventSerializer<IEvent>>;

Expand Down Expand Up @@ -95,7 +94,7 @@ export class EventMap {
return name;
}

registerSerializers(events: Type<IEvent>[] = [], serializers: InstanceWrapper<IEventSerializer>[] = []) {
registerSerializers(events: Type<IEvent>[] = [], serializers: ProviderWrapper<IEventSerializer>[] = []) {
for (const event of events) {
// get the handler
const handler = serializers.find(({ metatype }) => {
Expand Down
1 change: 1 addition & 0 deletions packages/core/lib/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export type * from './aggregate';
export type * from './commands';
export type * from './events';
export type * from './module';
export type * from './provider-wrapper';
export type * from './queries';
5 changes: 5 additions & 0 deletions packages/core/lib/interfaces/provider-wrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { DiscoveryService } from '@nestjs/core';

export type ProviderWrapper<T = unknown> = ReturnType<DiscoveryService['getProviders']>[number] & {
instance?: T;
};
7 changes: 3 additions & 4 deletions packages/core/lib/query-bus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'reflect-metadata';
import { Injectable, type Type } from '@nestjs/common';
import type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';

import {
InvalidQueryHandlerException,
Expand All @@ -9,7 +8,7 @@ import {
QueryHandlerNotFoundException,
} from './exceptions';
import { DefaultQueryPubSub, ObservableBus, getQueryHandlerMetadata, getQueryMetadata } from './helpers';
import type { IQuery, IQueryBus, IQueryHandler, IQueryPublisher } from './interfaces';
import type { IQuery, IQueryBus, IQueryHandler, IQueryPublisher, ProviderWrapper } from './interfaces';

@Injectable()
export class QueryBus<QueryBase extends IQuery = IQuery>
Expand Down Expand Up @@ -53,12 +52,12 @@ export class QueryBus<QueryBase extends IQuery = IQuery>
return id;
}

register(handlers: InstanceWrapper<IQueryHandler>[] = []) {
register(handlers: ProviderWrapper<IQueryHandler>[] = []) {
for (const handler of handlers) {
this.registerHandler(handler);
}
}
protected registerHandler(handler: InstanceWrapper<IQueryHandler>) {
protected registerHandler(handler: ProviderWrapper<IQueryHandler>) {
const { metatype, instance } = handler;

// check
Expand Down
76 changes: 26 additions & 50 deletions packages/core/lib/services/explorer.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Injectable, type Type } from '@nestjs/common';
import type { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';

import type { Module } from '@nestjs/core/injector/module';
// biome-ignore lint/style/useImportType: DI
import { ModulesContainer } from '@nestjs/core/injector/modules-container';
import { DiscoveryService } from '@nestjs/core';

import {
COMMAND_HANDLER_METADATA,
Expand All @@ -21,6 +18,7 @@ import type {
IEventSerializer,
IEventSubscriber,
IQueryHandler,
ProviderWrapper,
} from '../interfaces';
import { EventRegistry } from '../registries';

Expand All @@ -29,73 +27,51 @@ export type ProvidersIntrospectionResult = {
* For future saga support, currently unused.
* @ignore
*/
sagas?: InstanceWrapper[];
sagas?: ProviderWrapper[];
events?: Type<IEvent>[];
queries?: InstanceWrapper<IQueryHandler>[];
commands?: InstanceWrapper<ICommandHandler>[];
eventPublishers?: InstanceWrapper<IEventPublisher>[];
eventSubscribers?: InstanceWrapper<IEventSubscriber>[];
eventSerializers?: InstanceWrapper<IEventSerializer>[];
queries?: ProviderWrapper<IQueryHandler>[];
commands?: ProviderWrapper<ICommandHandler>[];
eventPublishers?: ProviderWrapper<IEventPublisher>[];
eventSubscribers?: ProviderWrapper<IEventSubscriber>[];
eventSerializers?: ProviderWrapper<IEventSerializer>[];
};

@Injectable()
export class ExplorerService {
constructor(
@InjectEventSourcingOptions()
private readonly options: EventSourcingModuleOptions,
private readonly modulesContainer: ModulesContainer,
private readonly discoveryService: DiscoveryService,
) {}

get events(): Type<IEvent>[] {
return [...(this.options.events ?? []), ...EventRegistry.getEvents()];
}

explore(): ProvidersIntrospectionResult {
const modules = [...this.modulesContainer.values()];
const providers = this.discoveryService.getProviders();

return {
sagas: [],
events: this.events,
queries: this.flatMap<IQueryHandler>(modules, (instance) =>
this.filterByMetadataKey(instance, QUERY_HANDLER_METADATA),
),
commands: this.flatMap<ICommandHandler>(modules, (instance) =>
this.filterByMetadataKey(instance, COMMAND_HANDLER_METADATA),
),
eventPublishers: this.flatMap<IEventPublisher>(modules, (instance) =>
this.filterByMetadataKey(instance, EVENT_PUBLISHER_METADATA),
),
eventSubscribers: this.flatMap<IEventSubscriber>(modules, (instance) =>
this.filterByMetadataKey(instance, EVENT_SUBSCRIBER_METADATA),
),
eventSerializers: this.flatMap<IEventSerializer>(modules, (instance) =>
this.filterByMetadataKey(instance, EVENT_SERIALIZER_METADATA),
),
queries: this.filterByMetadataKey<IQueryHandler>(providers, QUERY_HANDLER_METADATA),
commands: this.filterByMetadataKey<ICommandHandler>(providers, COMMAND_HANDLER_METADATA),
eventPublishers: this.filterByMetadataKey<IEventPublisher>(providers, EVENT_PUBLISHER_METADATA),
eventSubscribers: this.filterByMetadataKey<IEventSubscriber>(providers, EVENT_SUBSCRIBER_METADATA),
eventSerializers: this.filterByMetadataKey<IEventSerializer>(providers, EVENT_SERIALIZER_METADATA),
};
}

flatMap<T extends object>(
modules: Module[],
callback: (instance: InstanceWrapper) => InstanceWrapper | undefined,
): InstanceWrapper<T>[] {
const items = modules
.map((moduleRef) => [...moduleRef.providers.values()].map(callback))
.reduce((a, b) => a.concat(b), []);
return items.filter((item) => !!item) as InstanceWrapper<T>[];
}

filterByMetadataKey(wrapper: InstanceWrapper, metadataKey: string) {
const { instance } = wrapper;
if (!instance) {
return;
}
if (!instance.constructor) {
return;
}
const metadata = Reflect.getMetadata(metadataKey, instance.constructor);
if (!metadata) {
return;
}
return wrapper;
private filterByMetadataKey<T extends object>(
providers: ProviderWrapper[],
metadataKey: string,
): ProviderWrapper<T>[] {
return providers.filter((wrapper) => {
const instance = wrapper.instance;
if (!instance || !instance.constructor) {
return false;
}
return !!Reflect.getMetadata(metadataKey, instance.constructor);
}) as ProviderWrapper<T>[];
}
}
Loading
Loading