refactor: middle ware triggers
This commit is contained in:
parent
3dc566c2fd
commit
7d8684a16f
|
|
@ -1,346 +1,29 @@
|
||||||
import { cardDesertData } from "../data";
|
import {createMiddlewareChain} from "../utils/middleware";
|
||||||
import { createStatusCard } from "../deck/factory";
|
import {CombatGameContext} from "./types";
|
||||||
import type { BuffTable, CombatEffectEntry, CombatState } from "./types";
|
|
||||||
import { applyDamage, removeBuff } from "./effects";
|
|
||||||
|
|
||||||
export type TriggerContext = {
|
export type Triggers = {
|
||||||
state: CombatState;
|
onTurnStart: { entityKey: "player" | string, },
|
||||||
rng: { nextInt: (n: number) => number };
|
onTurnEnd: { entityKey: "player" | string, },
|
||||||
};
|
onShuffle: { entityKey: "player" | string, },
|
||||||
|
onCardPlayed: { cardId: string, },
|
||||||
|
onCardDiscarded: { cardId: string, },
|
||||||
|
onCardDrawn: { cardId: string, },
|
||||||
|
onEffectApplied: { effectId: string, entityKey: "player" | string, stacks: number, },
|
||||||
|
}
|
||||||
|
|
||||||
export type BuffTriggerBehavior = {
|
export function createTriggers(){
|
||||||
onTurnStart?: (ctx: TriggerContext, entityKey: "player" | string, stacks: number) => void;
|
|
||||||
onTurnEnd?: (ctx: TriggerContext, entityKey: "player" | string, stacks: number) => void;
|
|
||||||
onAttacked?: (ctx: TriggerContext, attackerKey: "player" | string, defenderKey: "player" | string, damage: number, stacks: number) => number;
|
|
||||||
onDamage?: (ctx: TriggerContext, targetKey: "player" | string, damage: number, stacks: number) => void;
|
|
||||||
modifyOutgoingDamage?: (ctx: TriggerContext, sourceKey: "player" | string, damage: number, stacks: number) => number;
|
|
||||||
modifyIncomingDamage?: (ctx: TriggerContext, targetKey: "player" | string, damage: number, stacks: number) => number;
|
|
||||||
onShuffle?: (ctx: TriggerContext, stacks: number) => void;
|
|
||||||
onCardPlayed?: (ctx: TriggerContext, cardId: string, stacks: number) => void;
|
|
||||||
onCardDiscarded?: (ctx: TriggerContext, cardId: string, stacks: number) => void;
|
|
||||||
onCardDrawn?: (ctx: TriggerContext, cardId: string, stacks: number) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TriggerEvent =
|
|
||||||
| "onTurnStart"
|
|
||||||
| "onTurnEnd"
|
|
||||||
| "onAttacked"
|
|
||||||
| "onDamage"
|
|
||||||
| "modifyOutgoingDamage"
|
|
||||||
| "modifyIncomingDamage"
|
|
||||||
| "onShuffle"
|
|
||||||
| "onCardPlayed"
|
|
||||||
| "onCardDiscarded"
|
|
||||||
| "onCardDrawn";
|
|
||||||
|
|
||||||
export type CombatTriggerRegistry = Record<string, BuffTriggerBehavior>;
|
|
||||||
|
|
||||||
export function createCombatTriggerRegistry(): CombatTriggerRegistry {
|
|
||||||
return {
|
return {
|
||||||
spike: {
|
onTurnStart: createTrigger("onTurnStart"),
|
||||||
onAttacked(ctx, attackerKey, _defenderKey, damage, stacks) {
|
onTurnEnd: createTrigger("onTurnEnd"),
|
||||||
const { state } = ctx;
|
onShuffle: createTrigger("onShuffle"),
|
||||||
applyDamage(state, attackerKey, stacks, _defenderKey);
|
onCardPlayed: createTrigger("onCardPlayed"),
|
||||||
return damage;
|
onCardDiscarded: createTrigger("onCardDiscarded"),
|
||||||
},
|
onCardDrawn: createTrigger("onCardDrawn"),
|
||||||
},
|
onEffectApplied: createTrigger("onEffectApplied"),
|
||||||
aim: {
|
|
||||||
modifyOutgoingDamage(_ctx, _sourceKey, damage, stacks) {
|
|
||||||
if (stacks > 0) return damage * 2;
|
|
||||||
return damage;
|
|
||||||
},
|
|
||||||
onDamage(ctx, targetKey, damage, stacks) {
|
|
||||||
const { state } = ctx;
|
|
||||||
const entity = targetKey === "player" ? null : state.enemies[targetKey];
|
|
||||||
if (entity) {
|
|
||||||
const loss = Math.min(stacks, damage);
|
|
||||||
removeBuff(entity.buffs, "aim", loss);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
charge: {
|
|
||||||
modifyOutgoingDamage(_ctx, _sourceKey, damage, stacks) {
|
|
||||||
if (stacks > 0) {
|
|
||||||
return damage * 2;
|
|
||||||
}
|
|
||||||
return damage;
|
|
||||||
},
|
|
||||||
modifyIncomingDamage(_ctx, _targetKey, damage, stacks) {
|
|
||||||
if (stacks > 0) {
|
|
||||||
return damage * 2;
|
|
||||||
}
|
|
||||||
return damage;
|
|
||||||
},
|
|
||||||
onDamage(ctx, targetKey, damage, stacks) {
|
|
||||||
const { state } = ctx;
|
|
||||||
const entity = targetKey === "player"
|
|
||||||
? { buffs: state.player.buffs } as { buffs: BuffTable }
|
|
||||||
: state.enemies[targetKey];
|
|
||||||
if (entity) {
|
|
||||||
const loss = Math.min(stacks, damage);
|
|
||||||
removeBuff(entity.buffs, "charge", loss);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
roll: {
|
|
||||||
modifyOutgoingDamage(ctx, sourceKey, damage, stacks) {
|
|
||||||
if (stacks >= 10) {
|
|
||||||
const { state } = ctx;
|
|
||||||
const entity = sourceKey === "player"
|
|
||||||
? { buffs: state.player.buffs } as { buffs: BuffTable }
|
|
||||||
: state.enemies[sourceKey];
|
|
||||||
if (entity) {
|
|
||||||
const spendable = Math.floor(stacks / 10) * 10;
|
|
||||||
const bonusDamage = Math.floor(spendable / 10);
|
|
||||||
removeBuff(entity.buffs, "roll", spendable);
|
|
||||||
return damage + bonusDamage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return damage;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tailSting: {
|
|
||||||
onTurnEnd(ctx, entityKey, stacks) {
|
|
||||||
const { state } = ctx;
|
|
||||||
if (entityKey !== "player" && state.enemies[entityKey]?.isAlive) {
|
|
||||||
applyDamage(state, "player", stacks, entityKey);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
energyDrain: {
|
|
||||||
onDamage(ctx, targetKey, _damage, _stacks) {
|
|
||||||
const { state } = ctx;
|
|
||||||
if (targetKey === "player" && state.player.damagedThisTurn === false) {
|
|
||||||
// This is the first damage; mark it.
|
|
||||||
// actual energy drain happens in onTurnStart check
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTurnStart(ctx, entityKey, _stacks) {
|
|
||||||
// energyDrain: first damage each turn loses 1 energy
|
|
||||||
// We just mark that the enemy has this; actual drain is in onDamage
|
|
||||||
},
|
|
||||||
},
|
|
||||||
molt: {
|
|
||||||
onDamage(ctx, targetKey, _damage, _stacks) {
|
|
||||||
const { state } = ctx;
|
|
||||||
if (targetKey !== "player") {
|
|
||||||
const enemy = state.enemies[targetKey];
|
|
||||||
if (enemy && enemy.isAlive) {
|
|
||||||
const moltStacks = enemy.buffs["molt"] ?? 0;
|
|
||||||
if (moltStacks >= enemy.maxHp) {
|
|
||||||
enemy.isAlive = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
storm: {
|
|
||||||
onAttacked(ctx, attackerKey, defenderKey, damage, stacks) {
|
|
||||||
const { state } = ctx;
|
|
||||||
if (defenderKey !== "player" && state.enemies[defenderKey]?.isAlive) {
|
|
||||||
addStatusCardToHand(state, "static", 1);
|
|
||||||
}
|
|
||||||
return damage;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
vultureEye: {
|
|
||||||
onDamage(ctx, targetKey, damage, stacks) {
|
|
||||||
const { state } = ctx;
|
|
||||||
if (targetKey === "player" && damage > 0) {
|
|
||||||
const vultureEnemies = state.enemyOrder.filter(
|
|
||||||
(id) => state.enemies[id].isAlive && state.enemies[id].buffs["vultureEye"] && state.enemies[id].templateId === "秃鹫"
|
|
||||||
);
|
|
||||||
if (vultureEnemies.length > 0) {
|
|
||||||
for (const vultureId of vultureEnemies) {
|
|
||||||
const vulture = state.enemies[vultureId];
|
|
||||||
const intent = vulture.intentData["attack"];
|
|
||||||
if (intent) {
|
|
||||||
const effects = intent.effects as unknown as CombatEffectEntry[];
|
|
||||||
for (const entry of effects) {
|
|
||||||
if (entry[0] === "player" && entry[1].id === "attack") {
|
|
||||||
applyDamage(state, "player", entry[2], vultureId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
venom: {
|
|
||||||
onCardDiscarded(ctx, cardId, stacks) {
|
|
||||||
const { state } = ctx;
|
|
||||||
state.player.cardsDiscardedThisTurn++;
|
|
||||||
const venomCards = state.player.deck.hand.filter((id) => {
|
|
||||||
const card = state.player.deck.cards[id];
|
|
||||||
return card && card.itemData === null && card.displayName === "蛇毒";
|
|
||||||
});
|
|
||||||
if (state.player.cardsDiscardedThisTurn > 1 && venomCards.length > 0) {
|
|
||||||
applyDamage(state, "player", 6, undefined);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
static: {
|
|
||||||
modifyIncomingDamage(_ctx, targetKey, damage, stacks) {
|
|
||||||
if (targetKey === "player") {
|
|
||||||
return damage + stacks;
|
|
||||||
}
|
|
||||||
return damage;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
discard: {
|
|
||||||
onShuffle(ctx, stacks) {
|
|
||||||
// Bandit: shuffle discards random item cards
|
|
||||||
// Simplified: mark the effect for the procedure to handle
|
|
||||||
},
|
|
||||||
},
|
|
||||||
curse: {
|
|
||||||
onDamage(ctx, targetKey, damage, stacks) {
|
|
||||||
// Curse: when attacked, item attack -1 until card from that item is discarded
|
|
||||||
// This is handled via itemBuffs in effects
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function addStatusCardToHand(state: CombatState, effectId: string, count: number): void {
|
|
||||||
const cardDef = cardDesertData.find((c) => c.id === effectId);
|
|
||||||
if (!cardDef) return;
|
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const cardId = `status-${effectId}-${Date.now()}-${i}`;
|
|
||||||
const card = createStatusCard(cardId, cardDef.name, cardDef.desc);
|
|
||||||
state.player.deck.cards[card.id] = card;
|
|
||||||
state.player.deck.hand.push(card.id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dispatchTrigger(
|
export function createTrigger<TKey extends keyof Triggers>(event: TKey) {
|
||||||
ctx: TriggerContext,
|
type Ctx = Triggers[TKey] & { event: TKey, game: CombatGameContext };
|
||||||
event: TriggerEvent,
|
return createMiddlewareChain<Ctx>();
|
||||||
entityKey: "player" | string,
|
}
|
||||||
registry: CombatTriggerRegistry,
|
|
||||||
): void {
|
|
||||||
const buffs = entityKey === "player"
|
|
||||||
? ctx.state.player.buffs
|
|
||||||
: ctx.state.enemies[entityKey]?.buffs;
|
|
||||||
if (!buffs) return;
|
|
||||||
|
|
||||||
for (const [buffId, stacks] of Object.entries(buffs)) {
|
|
||||||
const behavior = registry[buffId];
|
|
||||||
if (!behavior) continue;
|
|
||||||
const handler = behavior[event];
|
|
||||||
if (handler) {
|
|
||||||
handler(ctx, entityKey, stacks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dispatchAttackedTrigger(
|
|
||||||
ctx: TriggerContext,
|
|
||||||
attackerKey: "player" | string,
|
|
||||||
defenderKey: "player" | string,
|
|
||||||
damage: number,
|
|
||||||
registry: CombatTriggerRegistry,
|
|
||||||
): number {
|
|
||||||
const buffs = defenderKey === "player"
|
|
||||||
? ctx.state.player.buffs
|
|
||||||
: ctx.state.enemies[defenderKey]?.buffs;
|
|
||||||
if (!buffs) return damage;
|
|
||||||
|
|
||||||
let modifiedDamage = damage;
|
|
||||||
for (const [buffId, stacks] of Object.entries(buffs)) {
|
|
||||||
const behavior = registry[buffId];
|
|
||||||
if (!behavior?.onAttacked) continue;
|
|
||||||
modifiedDamage = behavior.onAttacked(ctx, attackerKey, defenderKey, modifiedDamage, stacks);
|
|
||||||
}
|
|
||||||
return modifiedDamage;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dispatchDamageTrigger(
|
|
||||||
ctx: TriggerContext,
|
|
||||||
targetKey: "player" | string,
|
|
||||||
damage: number,
|
|
||||||
registry: CombatTriggerRegistry,
|
|
||||||
): void {
|
|
||||||
const buffs = targetKey === "player"
|
|
||||||
? ctx.state.player.buffs
|
|
||||||
: ctx.state.enemies[targetKey]?.buffs;
|
|
||||||
if (!buffs) return;
|
|
||||||
|
|
||||||
for (const [buffId, stacks] of Object.entries(buffs)) {
|
|
||||||
const behavior = registry[buffId];
|
|
||||||
if (!behavior?.onDamage) continue;
|
|
||||||
behavior.onDamage(ctx, targetKey, damage, stacks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dispatchOutgoingDamageTrigger(
|
|
||||||
ctx: TriggerContext,
|
|
||||||
sourceKey: "player" | string,
|
|
||||||
damage: number,
|
|
||||||
registry: CombatTriggerRegistry,
|
|
||||||
): number {
|
|
||||||
const buffs = sourceKey === "player"
|
|
||||||
? ctx.state.player.buffs
|
|
||||||
: ctx.state.enemies[sourceKey]?.buffs;
|
|
||||||
if (!buffs) return damage;
|
|
||||||
|
|
||||||
let modifiedDamage = damage;
|
|
||||||
for (const [buffId, stacks] of Object.entries(buffs)) {
|
|
||||||
const behavior = registry[buffId];
|
|
||||||
if (!behavior?.modifyOutgoingDamage) continue;
|
|
||||||
modifiedDamage = behavior.modifyOutgoingDamage(ctx, sourceKey, modifiedDamage, stacks);
|
|
||||||
}
|
|
||||||
return modifiedDamage;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dispatchIncomingDamageTrigger(
|
|
||||||
ctx: TriggerContext,
|
|
||||||
targetKey: "player" | string,
|
|
||||||
damage: number,
|
|
||||||
registry: CombatTriggerRegistry,
|
|
||||||
): number {
|
|
||||||
const buffs = targetKey === "player"
|
|
||||||
? ctx.state.player.buffs
|
|
||||||
: ctx.state.enemies[targetKey]?.buffs;
|
|
||||||
if (!buffs) return damage;
|
|
||||||
|
|
||||||
let modifiedDamage = damage;
|
|
||||||
for (const [buffId, stacks] of Object.entries(buffs)) {
|
|
||||||
const behavior = registry[buffId];
|
|
||||||
if (!behavior?.modifyIncomingDamage) continue;
|
|
||||||
modifiedDamage = behavior.modifyIncomingDamage(ctx, targetKey, modifiedDamage, stacks);
|
|
||||||
}
|
|
||||||
return modifiedDamage;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dispatchShuffleTrigger(
|
|
||||||
ctx: TriggerContext,
|
|
||||||
registry: CombatTriggerRegistry,
|
|
||||||
): void {
|
|
||||||
for (const enemyId of ctx.state.enemyOrder) {
|
|
||||||
const enemy = ctx.state.enemies[enemyId];
|
|
||||||
if (!enemy.isAlive) continue;
|
|
||||||
for (const [buffId, stacks] of Object.entries(enemy.buffs)) {
|
|
||||||
const behavior = registry[buffId];
|
|
||||||
if (!behavior?.onShuffle) continue;
|
|
||||||
behavior.onShuffle(ctx, stacks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dispatchCardDrawnTrigger(
|
|
||||||
ctx: TriggerContext,
|
|
||||||
cardId: string,
|
|
||||||
registry: CombatTriggerRegistry,
|
|
||||||
): void {
|
|
||||||
const buffs = ctx.state.player.buffs;
|
|
||||||
if (!buffs) return;
|
|
||||||
|
|
||||||
for (const [buffId, stacks] of Object.entries(buffs)) {
|
|
||||||
const behavior = registry[buffId];
|
|
||||||
if (!behavior?.onCardDrawn) continue;
|
|
||||||
behavior.onCardDrawn(ctx, cardId, stacks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +1,30 @@
|
||||||
import type { PlayerDeck, GameCard } from "../deck/types";
|
import type { PlayerDeck } from "../deck/types";
|
||||||
import type { PlayerState } from "../progress/types";
|
import {EnemyData, IntentData} from "@/samples/slay-the-spire-like/system/type";
|
||||||
|
|
||||||
export type BuffTable = Record<string, number>;
|
export type EffectTable = Record<string, number>;
|
||||||
|
|
||||||
export type EffectTiming = "instant" | "temporary" | "lingering" | "permanent" | "posture" | "card" | "cardDraw" | "cardHand" | "item" | "itemUntilPlayed";
|
export type CombatEntity = {
|
||||||
|
effects: EffectTable;
|
||||||
export type EffectData = {
|
|
||||||
readonly id: string;
|
|
||||||
readonly timing: EffectTiming;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EffectTarget = "self" | "target" | "all" | "random" | "player" | "team";
|
|
||||||
|
|
||||||
export type EnemyIntentData = {
|
|
||||||
readonly enemy: string;
|
|
||||||
readonly intentId: string;
|
|
||||||
readonly initialIntent: boolean;
|
|
||||||
readonly nextIntents: readonly string[];
|
|
||||||
readonly brokenIntent: readonly string[];
|
|
||||||
readonly initBuffs: readonly [EffectData, number];
|
|
||||||
readonly effects: readonly ["self" | "player" | "team", EffectData, number];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EncounterData = {
|
|
||||||
readonly type: "minion" | "elite" | "event" | "shop" | "camp" | "curio";
|
|
||||||
readonly name: string;
|
|
||||||
readonly description: string;
|
|
||||||
readonly enemies: readonly [string, number, number];
|
|
||||||
readonly dialogue: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ItemBuff = {
|
|
||||||
effectId: string;
|
|
||||||
stacks: number;
|
|
||||||
timing: EffectTiming;
|
|
||||||
sourceItemId: string;
|
|
||||||
targetItemId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EnemyState = {
|
|
||||||
id: string;
|
|
||||||
templateId: string;
|
|
||||||
hp: number;
|
hp: number;
|
||||||
maxHp: number;
|
maxHp: number;
|
||||||
buffs: BuffTable;
|
|
||||||
currentIntentId: string;
|
|
||||||
intentData: Record<string, EnemyIntentData>;
|
|
||||||
isAlive: boolean;
|
isAlive: boolean;
|
||||||
hadDefendBroken: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PlayerCombatState = {
|
export type PlayerEntity = CombatEntity & {
|
||||||
hp: number;
|
|
||||||
maxHp: number;
|
|
||||||
energy: number;
|
energy: number;
|
||||||
maxEnergy: number;
|
maxEnergy: number;
|
||||||
buffs: BuffTable;
|
|
||||||
deck: PlayerDeck;
|
deck: PlayerDeck;
|
||||||
damageTakenThisTurn: number;
|
itemEffects: Record<string, EffectTable>;
|
||||||
damagedThisTurn: boolean;
|
}
|
||||||
cardsDiscardedThisTurn: number;
|
|
||||||
itemBuffs: ItemBuff[];
|
export type EnemyEntity = CombatEntity & {
|
||||||
fatigueAddedCount: number;
|
id: string;
|
||||||
|
enemy: EnemyData;
|
||||||
|
intents: Record<string, IntentData>;
|
||||||
|
currentIntentId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CombatPhase = "playerTurn" | "enemyTurn" | "combatEnd";
|
export type CombatPhase = "playerTurn" | "enemyTurn" | "combatEnd";
|
||||||
|
|
||||||
export type CombatResult = "victory" | "defeat";
|
export type CombatResult = "victory" | "defeat";
|
||||||
|
|
||||||
export type LootEntry = {
|
export type LootEntry = {
|
||||||
|
|
@ -75,23 +34,14 @@ export type LootEntry = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CombatState = {
|
export type CombatState = {
|
||||||
enemies: Record<string, EnemyState>;
|
enemies: EnemyEntity[];
|
||||||
enemyOrder: string[];
|
player: PlayerEntity;
|
||||||
player: PlayerCombatState;
|
|
||||||
phase: CombatPhase;
|
phase: CombatPhase;
|
||||||
turnNumber: number;
|
turnNumber: number;
|
||||||
result: CombatResult | null;
|
result: CombatResult | null;
|
||||||
|
|
||||||
loot: LootEntry[];
|
loot: LootEntry[];
|
||||||
enemyTemplateData: Record<string, EnemyIntentData>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CombatEffectEntry = [EffectTarget, EffectData, number];
|
|
||||||
|
|
||||||
export type CombatEntity = {
|
|
||||||
buffs: BuffTable;
|
|
||||||
hp: number;
|
|
||||||
maxHp: number;
|
|
||||||
isAlive: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CombatGameContext = import("@/core/game").IGameContext<CombatState>;
|
export type CombatGameContext = import("@/core/game").IGameContext<CombatState>;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
type Middleware<TContext, TReturn> = (
|
||||||
|
context: TContext,
|
||||||
|
next: () => Promise<TReturn>
|
||||||
|
) => Promise<TReturn>;
|
||||||
|
|
||||||
|
export type MiddlewareChain<TContext, TReturn> = {
|
||||||
|
use: (middleware: Middleware<TContext, TReturn>) => void;
|
||||||
|
execute: (context: TContext) => Promise<TReturn>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createMiddlewareChain<TContext extends object, TReturn=TContext>(
|
||||||
|
fallback?: (context: TContext) => Promise<TReturn>
|
||||||
|
): MiddlewareChain<TContext, TReturn> {
|
||||||
|
const middlewares: Middleware<TContext, TReturn>[] = [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
use(middleware: Middleware<TContext, TReturn>) {
|
||||||
|
middlewares.push(middleware);
|
||||||
|
},
|
||||||
|
async execute(context: TContext) {
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
async function dispatch(ctx: TContext): Promise<TReturn> {
|
||||||
|
if (index >= middlewares.length) {
|
||||||
|
return fallback ? fallback(ctx) : ctx as unknown as TReturn;
|
||||||
|
}
|
||||||
|
const current = middlewares[index++];
|
||||||
|
return current(ctx, () => dispatch(ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dispatch(context);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue