Skip to content

Commit ed20a0e

Browse files
committed
feat(command-flow): add package
1 parent e10ad5d commit ed20a0e

45 files changed

Lines changed: 1031 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package-lock.json

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/command-flow/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# CommandFlow
2+
3+
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
4+
5+
## Code scaffolding
6+
7+
Run `ng generate component component-name --project command-flow` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project command-flow`.
8+
9+
> Note: Don't forget to add `--project command-flow` or else it will be added to the default project in your `angular.json` file.
10+
11+
## Build
12+
13+
Run `ng build command-flow` to build the project. The build artifacts will be stored in the `dist/` directory.
14+
15+
## Publishing
16+
17+
After building your library with `ng build command-flow`, go to the dist folder `cd dist/command-flow` and run `npm publish`.
18+
19+
## Running unit tests
20+
21+
Run `ng test command-flow` to execute the unit tests via [Karma](https://karma-runner.github.io).
22+
23+
## Further help
24+
25+
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3+
"dest": "../../dist/command-flow",
4+
"lib": {
5+
"entryFile": "src/public-api.ts"
6+
}
7+
}

packages/command-flow/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@angularity/command-flow",
3+
"version": "0.1.0",
4+
"peerDependencies": {
5+
"@angular/common": "17.x.x",
6+
"@angular/core": "17.x.x",
7+
"@angularity/core": "*",
8+
"rxjs": "7.x.x"
9+
},
10+
"dependencies": {
11+
"ts-pattern": "^5.1.1",
12+
"tslib": "^2.3.0"
13+
},
14+
"sideEffects": false
15+
}

packages/command-flow/project.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
3+
"name": "command-flow",
4+
"projectType": "library",
5+
"sourceRoot": "packages/command-flow/src",
6+
"prefix": "agl",
7+
"targets": {
8+
"build": {
9+
"executor": "@angular-devkit/build-angular:ng-packagr",
10+
"options": {
11+
"project": "packages/command-flow/ng-package.json"
12+
},
13+
"configurations": {
14+
"production": {
15+
"tsConfig": "packages/command-flow/tsconfig.lib.prod.json"
16+
},
17+
"development": {
18+
"tsConfig": "packages/command-flow/tsconfig.lib.json"
19+
}
20+
},
21+
"defaultConfiguration": "production"
22+
},
23+
"test": {
24+
"executor": "@angular-devkit/build-angular:karma",
25+
"options": {
26+
"tsConfig": "packages/command-flow/tsconfig.spec.json",
27+
"polyfills": ["zone.js", "zone.js/testing"]
28+
}
29+
}
30+
}
31+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { forwardRef, Injectable } from '@angular/core';
2+
import { Observable, Subject } from 'rxjs';
3+
4+
import { Command, CommandEvent } from './core';
5+
6+
@Injectable({
7+
providedIn: 'root',
8+
useExisting: forwardRef(() => SubjectBasedCommandBus),
9+
})
10+
export abstract class CommandBus extends Observable<Command> {
11+
abstract dispatch(command: Command): void;
12+
}
13+
14+
@Injectable({
15+
providedIn: 'root',
16+
useExisting: forwardRef(() => SubjectBasedCommandEventBus),
17+
})
18+
export abstract class CommandEventBus extends Observable<CommandEvent> {
19+
abstract publish(event: CommandEvent): void;
20+
}
21+
22+
@Injectable({
23+
providedIn: 'root',
24+
})
25+
export class SubjectBasedCommandBus
26+
extends Subject<Command>
27+
implements CommandBus
28+
{
29+
dispatch(command: Command): void {
30+
this.next(command);
31+
}
32+
}
33+
34+
@Injectable({
35+
providedIn: 'root',
36+
})
37+
export class SubjectBasedCommandEventBus
38+
extends Subject<CommandEvent>
39+
implements CommandEventBus
40+
{
41+
publish(event: CommandEvent): void {
42+
this.next(event);
43+
}
44+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export const COMMAND_META = Symbol('COMMAND_META');
2+
3+
export interface CommandMetadata {}
4+
5+
export abstract class Command {
6+
abstract [COMMAND_META]: CommandMetadata;
7+
private constructor() {}
8+
static [Symbol.hasInstance](input: object): boolean {
9+
return input && COMMAND_META in input;
10+
}
11+
}
12+
13+
export const COMMAND_EVENT_META = Symbol('COMMAND_EVENT_META');
14+
15+
export interface CommandEventMetadata<C extends Command = Command> {
16+
source: C;
17+
}
18+
19+
export abstract class CommandEvent<C extends Command = Command> {
20+
abstract [COMMAND_EVENT_META]: CommandEventMetadata<C>;
21+
private constructor() {}
22+
static [Symbol.hasInstance](input: object): boolean {
23+
return input && COMMAND_EVENT_META in input;
24+
}
25+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { inject, Type } from '@angular/core';
2+
import { filter, Observable } from 'rxjs';
3+
4+
import { CommandBus, CommandEventBus } from './buses';
5+
import { Command, COMMAND_EVENT_META as META, CommandEvent } from './core';
6+
import { CommandFlowScheduler } from './scheduler';
7+
8+
export const useCommand =
9+
<T extends Type<Command>>(
10+
type: T,
11+
[scheduler, commands$, events$] = [
12+
inject(CommandFlowScheduler),
13+
inject(CommandBus),
14+
inject(CommandEventBus),
15+
],
16+
) =>
17+
(
18+
...args: ConstructorParameters<T>
19+
): Observable<CommandEvent<InstanceType<T>>> => {
20+
const command = new type(...args);
21+
scheduler.next(() => commands$.dispatch(command));
22+
return events$.pipe(
23+
filter(
24+
(e): e is CommandEvent<InstanceType<T>> => e[META].source === command,
25+
),
26+
);
27+
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { inject, Type } from '@angular/core';
2+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3+
import { Observable, tap } from 'rxjs';
4+
5+
import { pickType } from '../utilities/reactive-x';
6+
import { CommandBus, CommandEventBus } from './buses';
7+
import { Command, CommandEvent } from './core';
8+
import { CommandFlowScheduler } from './scheduler';
9+
10+
export interface CommandHandler<C extends Command> {
11+
(stream: Observable<C>): Observable<void | CommandEvent<C>>;
12+
}
13+
14+
export interface CommandEventHandler<E extends CommandEvent> {
15+
(stream: Observable<E>): Observable<void | Command>;
16+
}
17+
18+
export function registerCommandHandler<Types extends Type<Command>[]>(
19+
types: Types,
20+
handler: CommandHandler<InstanceType<Types[number]>>,
21+
): void {
22+
const commands$ = inject(CommandBus);
23+
const events$ = inject(CommandEventBus);
24+
const scheduler = inject(CommandFlowScheduler);
25+
commands$
26+
.pipe(
27+
takeUntilDestroyed(),
28+
pickType(...types),
29+
handler,
30+
pickType(CommandEvent),
31+
tap((event) => scheduler.next(() => events$.publish(event))),
32+
)
33+
.subscribe();
34+
}
35+
36+
export function registerCommandEventHandler<Types extends Type<CommandEvent>[]>(
37+
types: Types,
38+
handler: CommandEventHandler<InstanceType<Types[number]>>,
39+
): void {
40+
const events$ = inject(CommandEventBus);
41+
const commands$ = inject(CommandBus);
42+
const scheduler = inject(CommandFlowScheduler);
43+
events$
44+
.pipe(
45+
takeUntilDestroyed(),
46+
pickType(...types),
47+
handler,
48+
pickType(Command),
49+
tap((command) => scheduler.next(() => commands$.dispatch(command))),
50+
)
51+
.subscribe();
52+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './buses';
2+
export * from './core';
3+
export * from './facade';
4+
export * from './handlers';

0 commit comments

Comments
 (0)