src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.ts
import { ActionReducer } from '@ngrx/store';
import { IRequestEntityTypeState } from '../../app-state';
import {
EntitiesFetchHandler,
EntitiesInfoHandler,
EntityFetchHandler,
EntityInfoHandler,
} from '../../entity-request-pipeline/entity-request-pipeline.types';
import {
PaginationPageIteratorConfig,
} from '../../entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe';
import { EntityPipelineEntity, stratosEndpointGuidKey } from '../../entity-request-pipeline/pipeline.types';
import { EndpointAuthTypeConfig } from '../../extension-types';
import { EntitySchema } from '../../helpers/entity-schema';
import { endpointEntityType, STRATOS_ENDPOINT_TYPE, stratosEntityFactory } from '../../helpers/stratos-entity-factory';
import { EndpointModel } from '../../types/endpoint.types';
import { APISuccessOrFailedAction, EntityRequestAction } from '../../types/request.types';
import { IEndpointFavMetadata, UserFavorite } from '../../types/user-favorites.types';
import {
ActionBuilderAction,
ActionOrchestrator,
OrchestratedActionBuilderConfig,
OrchestratedActionBuilders,
} from '../action-orchestrator/action-orchestrator';
import { EntityCatalogHelpers } from '../entity-catalog.helper';
import {
EntityCatalogSchemas,
IEntityMetadata,
IStratosBaseEntityDefinition,
IStratosEndpointDefinition,
IStratosEntityBuilder,
IStratosEntityDefinition,
StratosEndpointExtensionDefinition,
} from '../entity-catalog.types';
import { ActionBuilderConfigMapper } from './action-builder-config.mapper';
import { ActionDispatchers, EntityCatalogEntityStoreHelpers } from './entity-catalog-entity-store-helpers';
import { EntityCatalogEntityStore } from './entity-catalog-entity.types';
import { KnownKeys, NonOptionalKeys } from './type.helpers';
export type KnownActionBuilders<ABC extends OrchestratedActionBuilders> = Pick<ABC, NonOptionalKeys<Pick<ABC, KnownKeys<ABC>>>>;
export interface EntityCatalogBuilders<
T extends IEntityMetadata = IEntityMetadata,
Y = any,
AB extends OrchestratedActionBuilderConfig = OrchestratedActionBuilders,
> {
entityBuilder?: IStratosEntityBuilder<T, Y>;
// Allows extensions to modify entities data in the store via none API Effect or unrelated actions.
dataReducers?: ActionReducer<IRequestEntityTypeState<Y>>[];
actionBuilders?: AB;
}
type DefinitionTypes = IStratosEntityDefinition<EntityCatalogSchemas> |
IStratosEndpointDefinition<EntityCatalogSchemas> |
IStratosBaseEntityDefinition<EntityCatalogSchemas>;
export class StratosBaseCatalogEntity<
T extends IEntityMetadata = IEntityMetadata,
Y = any,
AB extends OrchestratedActionBuilderConfig = OrchestratedActionBuilderConfig,
// This typing may cause an issue down the line.
ABC extends OrchestratedActionBuilders = AB extends OrchestratedActionBuilders ? AB : OrchestratedActionBuilders,
> {
constructor(
definition: IStratosEntityDefinition | IStratosEndpointDefinition | IStratosBaseEntityDefinition,
public readonly builders: EntityCatalogBuilders<T, Y, AB> = {}
) {
this.definition = this.populateEntity(definition);
this.type = this.definition.type || this.definition.schema.default.entityType;
const baseEntity = definition as IStratosEntityDefinition;
this.isEndpoint = !baseEntity.endpoint;
this.endpointType = this.getEndpointType(baseEntity);
// Note - Replacing `buildEntityKey` with `entityCatalog.getEntityKey` will cause circular dependency
this.entityKey = this.isEndpoint ?
EntityCatalogHelpers.buildEntityKey(EntityCatalogHelpers.endpointType, baseEntity.type) :
EntityCatalogHelpers.buildEntityKey(baseEntity.type, baseEntity.endpoint.type);
const actionBuilders = ActionBuilderConfigMapper.getActionBuilders(
this.builders.actionBuilders,
this.endpointType,
this.type,
(schemaKey: string) => this.getSchema(schemaKey)
);
this.actions = actionBuilders as KnownActionBuilders<ABC>;
this.actionOrchestrator = new ActionOrchestrator<ABC>(this.entityKey, actionBuilders as ABC);
this.store = {
...EntityCatalogEntityStoreHelpers.createCoreStore<Y, ABC>(
this.actionOrchestrator,
this.entityKey,
(schemaKey: string) => this.getSchema(schemaKey)
),
...EntityCatalogEntityStoreHelpers.getPaginationStore<Y, ABC>(
this.actions,
this.entityKey,
(schemaKey: string) => this.getSchema(schemaKey)
)
};
this.api = EntityCatalogEntityStoreHelpers.getActionDispatchers<Y, ABC>(
this.store,
actionBuilders as ABC
);
}
/**
* Create actions specific to the entity type
*/
public readonly actions: KnownActionBuilders<ABC>;
/**
* Create and dispatch actions specific to the entity type. Response will provide an observable reporting entity or pagination state
*/
public readonly api: ActionDispatchers<KnownActionBuilders<ABC>>;
/**
* Monitor an entity or collection of entities. Services will fetch the entity/entities if missing, monitors will not
*/
public readonly store: EntityCatalogEntityStore<Y, ABC>;
public readonly entityKey: string;
public readonly type: string;
public readonly definition: DefinitionTypes;
public readonly isEndpoint: boolean;
public readonly actionOrchestrator: ActionOrchestrator<ABC>;
public readonly endpointType: string;
private populateEntitySchemaKey(entitySchemas: EntityCatalogSchemas): EntityCatalogSchemas {
return Object.keys(entitySchemas).reduce((newSchema, schemaKey) => {
if (schemaKey !== 'default') {
// New schema must be instance of `schema.Entity` (and not a spread of one) else normalize will ignore
newSchema[schemaKey] = entitySchemas[schemaKey].clone();
newSchema[schemaKey].schemaKey = schemaKey;
}
return newSchema;
}, {
default: entitySchemas.default
});
}
private getEndpointType(definition: IStratosBaseEntityDefinition) {
const entityDef = definition as IStratosEntityDefinition;
return entityDef.endpoint ? entityDef.endpoint.type : STRATOS_ENDPOINT_TYPE;
}
private populateEntity(entity: IStratosEntityDefinition | IStratosEndpointDefinition | IStratosBaseEntityDefinition)
: DefinitionTypes {
// For cases where `entity.schema` is a EntityCatalogSchemas just pass original object through (with it's default)
const entitySchemas = entity.schema instanceof EntitySchema ? {
default: entity.schema
} : this.populateEntitySchemaKey(entity.schema);
return {
...entity,
type: entity.type || entitySchemas.default.entityType,
label: entity.label || 'Unknown',
labelPlural: entity.labelPlural || entity.label || 'Unknown',
schema: entitySchemas
};
}
/**
* Gets the schema associated with the entity type.
* If no schemaKey is provided then the default schema will be returned
*/
public getSchema(schemaKey?: string) {
const catalogSchema = this.definition.schema as EntityCatalogSchemas;
if (!schemaKey || this.isEndpoint) {
return catalogSchema.default;
}
const entityDefinition = this.definition as IStratosEntityDefinition;
// Note - Replacing `buildEntityKey` with `entityCatalog.getEntityKey` will cause circular dependency
const tempId = EntityCatalogHelpers.buildEntityKey(schemaKey, entityDefinition.endpoint.type);
if (!catalogSchema[schemaKey] && tempId === this.entityKey) {
// We've requested the default by passing the schema key that matches the entity type
return catalogSchema.default;
}
return catalogSchema[schemaKey];
}
public getGuidFromEntity(entity: Y) {
if (this.builders.entityBuilder && this.builders.entityBuilder.getGuid) {
return this.builders.entityBuilder.getGuid(entity);
}
return null;
}
public getEndpointGuidFromEntity(entity: Y & EntityPipelineEntity) {
return entity[stratosEndpointGuidKey];
}
public getTypeAndSubtype() {
const type = this.definition.parentType || this.definition.type;
const subType = this.definition.parentType ? this.definition.type : null;
return {
type,
subType
};
}
// Backward compatibility with the old actions.
// This should be removed after everything is based on the new flow
private getLegacyTypeFromAction(
action: EntityRequestAction,
actionString: 'start' | 'success' | 'failure' | 'complete'
) {
if (action && action.actions) {
switch (actionString) {
case 'success':
return action.actions[1];
case 'failure':
return action.actions[2];
case 'start':
return action.actions[0];
}
}
return null;
}
private getTypeFromAction(action?: EntityRequestAction) {
if (action) {
const actionBuilderAction = action as ActionBuilderAction;
return actionBuilderAction.actionBuilderActionType || null;
}
return null;
}
public getRequestType(
actionString: 'start' | 'success' | 'failure' | 'complete',
actionOrActionBuilderKey?: EntityRequestAction | string,
requestType: string = 'request'
) {
const requestTypeLabel = typeof actionOrActionBuilderKey === 'string' ?
actionOrActionBuilderKey :
this.getTypeFromAction(actionOrActionBuilderKey) || requestType;
return `@stratos/${this.entityKey}/${requestTypeLabel}/${actionString}`;
}
public getRequestAction(
actionString: 'start' | 'success' | 'failure' | 'complete',
actionOrActionBuilderKey?: EntityRequestAction | string,
requestType?: string,
response?: any
): APISuccessOrFailedAction {
if (typeof actionOrActionBuilderKey === 'string') {
return new APISuccessOrFailedAction(this.getRequestType(actionString, actionOrActionBuilderKey), null, response);
}
const type =
this.getLegacyTypeFromAction(actionOrActionBuilderKey, actionString) ||
this.getRequestType(actionString, actionOrActionBuilderKey, requestType);
return new APISuccessOrFailedAction(type, actionOrActionBuilderKey, response);
}
public getPaginationConfig(): PaginationPageIteratorConfig {
return this.definition.paginationConfig ?
this.definition.paginationConfig :
null;
}
public getEntityEmitHandler(): EntityInfoHandler {
return this.definition.entityEmitHandler;
}
public getEntitiesEmitHandler(): EntitiesInfoHandler {
return this.definition.entitiesEmitHandler;
}
public getEntityFetchHandler(): EntityFetchHandler {
return this.definition.entityFetchHandler;
}
public getEntitiesFetchHandler(): EntitiesFetchHandler {
return this.definition.entitiesFetchHandler;
}
}
export class StratosCatalogEntity<
T extends IEntityMetadata = IEntityMetadata,
Y = any,
AB extends OrchestratedActionBuilderConfig = OrchestratedActionBuilders,
ABC extends OrchestratedActionBuilders = AB extends OrchestratedActionBuilders ? AB : OrchestratedActionBuilders,
> extends StratosBaseCatalogEntity<T, Y, AB, ABC> {
public definition: IStratosEntityDefinition<EntityCatalogSchemas, Y, ABC>;
constructor(
entity: IStratosEntityDefinition,
config?: EntityCatalogBuilders<T, Y, AB>
) {
super(entity, config);
}
public getPaginationConfig(): PaginationPageIteratorConfig {
return this.definition.paginationConfig ?
this.definition.paginationConfig :
this.definition.endpoint ? this.definition.endpoint.paginationConfig : null;
}
public getEntityEmitHandler(): EntityInfoHandler {
return this.definition.entityEmitHandler ||
this.definition.endpoint ? this.definition.endpoint.entityEmitHandler : null;
}
public getEntitiesEmitHandler(): EntitiesInfoHandler {
return this.definition.entitiesEmitHandler ||
this.definition.endpoint ? this.definition.endpoint.entitiesEmitHandler : null;
}
public getEntityFetchHandler(): EntityFetchHandler {
return this.definition.entityFetchHandler ||
this.definition.endpoint ? this.definition.endpoint.entityFetchHandler : null;
}
public getEntitiesFetchHandler(): EntitiesFetchHandler {
return this.definition.entitiesFetchHandler ||
this.definition.endpoint ? this.definition.endpoint.entitiesFetchHandler : null;
}
}
export class StratosCatalogEndpointEntity extends StratosBaseCatalogEntity<IEndpointFavMetadata, EndpointModel> {
static readonly baseEndpointRender: IStratosEntityBuilder<IEndpointFavMetadata, EndpointModel> = {
getMetadata: endpoint => ({
name: endpoint.name,
subType: endpoint.sub_type,
}),
getLink: () => null,
getGuid: metadata => metadata.guid,
};
// This is needed here for typing
public definition: IStratosEndpointDefinition<EntityCatalogSchemas>;
constructor(
entity: StratosEndpointExtensionDefinition | IStratosEndpointDefinition,
getLink?: (favorite: UserFavorite<IEndpointFavMetadata>) => string
) {
const fullEntity: IStratosEndpointDefinition = {
...entity,
schema: {
default: stratosEntityFactory(endpointEntityType)
}
};
super(fullEntity, {
entityBuilder: {
...StratosCatalogEndpointEntity.baseEndpointRender,
getLink: getLink || StratosCatalogEndpointEntity.baseEndpointRender.getLink
}
});
}
public setListComponent(component: any) {
// Can only be set once
if (!this.definition.listDetailsComponent) {
(this.definition as any).listDetailsComponent = component;
}
}
public setAuthTypes(authTypes: EndpointAuthTypeConfig[]) {
// Can only be set once
if (!this.definition.authTypes || this.definition.authTypes.length === 0) {
(this.definition as any).authTypes = authTypes;
}
}
}