Add StateEvent implementation

This commit is contained in:
Garrett Mills 2022-08-06 01:42:15 -05:00
parent d00e6a02e2
commit fc85c9d2c8
4 changed files with 137 additions and 4 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@extollo/lib", "name": "@extollo/lib",
"version": "0.11.0", "version": "0.12.0",
"description": "The framework library that lifts up your code.", "description": "The framework library that lifts up your code.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@ -0,0 +1,122 @@
import {Container, Inject, Injectable, Instantiable} from '../../di'
import {Awaitable, ErrorWithContext, JSONState, uuid4} from '../../util'
import {BaseSerializer} from './serial/BaseSerializer'
import {Event} from './types'
/**
* An event base class that provides its own validator and serializer implementation.
* This is meant to streamline event creation for app-space by forcing the payload of
* the event to be a JSONState.
*/
@Injectable()
export abstract class StateEvent<T extends JSONState> implements Event {
@Inject()
protected readonly injector!: Container
/** The payload of the event. */
private state?: T
eventUuid = uuid4()
public get eventName(): string {
return this.getName()
}
/** Validate the incoming data. */
public abstract validate(state: JSONState): state is T
/** Get the event's name for the bus. */
public getName(): string {
const ctor = this.constructor as typeof StateEvent
return `state:${ctor.name}`
}
/** Get the Serializer implementation for this event. */
public getSerializer(): StateEventSerializer {
const ctor = this.constructor as typeof StateEvent
return this.injector.makeNew<StateEventSerializer>(StateEventSerializer, ctor)
}
/** Validate & set the internal state of this event instance. */
public setState(state: JSONState): this {
if ( !this.validate(state) ) {
throw new ErrorWithContext('Invalid state', {
state,
})
}
this.state = state
return this
}
/** Get the internal state of this event, validated. */
public getState(): T {
if ( typeof this.state === 'undefined' ) {
throw new ErrorWithContext('State has not been set on event')
}
return this.state
}
}
/**
* Generic serializer implementation for StateEvents.
* Note that this is NOT registered with @ObjectSerializer() because we need to
* create an instance of this class for EACH implementation of StateEvent.
* Still working that one out. For now, you'll need to manually register the
* serializer. You can do this with the `getSerializer` method.
*
* @example
* ```ts
* type MyState = {
* id: number
* }
*
* class MyStateEvent extends StateEvent<MyState> {
* public validate(state: JSONState): state is MyState {
* return is<MyState>(state)
* }
* }
* ```
*/
@Injectable()
export class StateEventSerializer extends BaseSerializer<StateEvent<JSONState>, JSONState> {
@Inject()
protected readonly injector!: Container
/** The StateEvent implementation. */
public readonly eventClass: Instantiable<StateEvent<JSONState>>;
constructor(
eventClass: Instantiable<StateEvent<JSONState>>,
) {
super()
this.eventClass = eventClass
}
matchActual(some: StateEvent<JSONState>): boolean {
return some instanceof this.eventClass
}
protected encodeActual(actual: StateEvent<JSONState>): Awaitable<JSONState> {
return actual.getState()
}
protected decodeSerial(serial: JSONState): Awaitable<StateEvent<JSONState>> {
const inst = this.injector.makeNew<StateEvent<JSONState>>(this.eventClass)
if ( !inst.validate(serial) ) {
throw new ErrorWithContext('Invalid serial state', {
serial,
eventClass: inst.getName(),
})
}
inst.setState(serial)
return inst
}
protected getName(): string {
const inst = this.injector.makeNew<StateEvent<JSONState>>(this.eventClass)
return `${inst.getName()}Serializer`
}
}

View File

@ -1,4 +1,4 @@
import {AwareOfContainerLifecycle, Container, Inject, Injectable, StaticInstantiable} from '../../di' import {AwareOfContainerLifecycle, Inject, Injectable, StaticInstantiable} from '../../di'
import { import {
EventBus, EventBus,
Event, Event,

View File

@ -64,10 +64,21 @@ export class Serialization {
protected serializers: Collection<RegisteredSerializer<Serializer<unknown, JSONState>>> = new Collection() protected serializers: Collection<RegisteredSerializer<Serializer<unknown, JSONState>>> = new Collection()
/** Register a new serializer with the service. */ /** Register a new serializer with the service. */
public register(serializer: Instantiable<Serializer<unknown, JSONState>>): this { public register(key: Instantiable<Serializer<unknown, JSONState>>): this {
// Prepend instead of push so that later-registered serializers are prioritized when matching // Prepend instead of push so that later-registered serializers are prioritized when matching
this.serializers.prepend({ this.serializers.prepend({
key: serializer, key,
})
return this
}
/** Register an already-realized serializer instance with this service. */
public registerInstance(key: Instantiable<Serializer<unknown, JSONState>>, instance: Serializer<unknown, JSONState>): this {
// Prepend instead of push so that later-registered serializers are prioritized when matching
this.serializers.prepend({
key,
instance,
}) })
return this return this