import { Prisma } from '../../../generated/prisma'; import type { AutomationTriggerType } from '../../../generated/prisma'; import { assertTransactionClient, type DbClient } from '../../prisma/db-client'; import { lockRowForUpdate } from '../../prisma/row-lock'; import { DEFAULT_RULES } from './default-rules'; import { ruleConfigOf } from './rule-config.registry'; import type { RuleConfigMap } from './rule-config.types'; export interface ActiveRule { /** null while no row exists yet: the built-in setting applies. */ id: string | null; active: boolean; config: RuleConfigMap[K]; actionTemplate: string | null; } interface RuleRow { id: string; active: boolean; config: Prisma.JsonValue; actionTemplate: string | null; } const RULE_COLUMNS = { id: true, active: true, config: true, actionTemplate: true, } as const; function toActiveRule( trigger: K, row: RuleRow | null, ): ActiveRule { if (row === null) { const defaults = DEFAULT_RULES[trigger]; return { id: null, active: defaults.active ?? true, config: ruleConfigOf(trigger, defaults.config), actionTemplate: defaults.actionTemplate, }; } return { id: row.id, active: row.active, config: ruleConfigOf(trigger, row.config), actionTemplate: row.actionTemplate, }; } /** * The rule as it is in force: its row, or the built-in setting while there is * none. The config is parsed, so every key is valid. */ export async function readRule( db: DbClient, trigger: K, ): Promise> { const row = await db.automationRule.findUnique({ where: { triggerType: trigger }, select: RULE_COLUMNS, }); return toActiveRule(trigger, row); } /** * Creates the rule row if it is missing (without touching an existing one), * locks it until the transaction ends and reads it. Serialises everything * that decides "is there already an open task/suggestion" under the rule. */ export async function lockRuleIn( tx: Prisma.TransactionClient, trigger: K, ): Promise & { id: string }> { assertTransactionClient(tx, 'lockRuleIn'); const defaults = DEFAULT_RULES[trigger]; await tx.automationRule.createMany({ data: [ { triggerType: trigger, active: defaults.active ?? true, config: defaults.config as Prisma.InputJsonValue, actionTemplate: defaults.actionTemplate, }, ], skipDuplicates: true, }); const { id } = await tx.automationRule.findUniqueOrThrow({ where: { triggerType: trigger }, select: { id: true }, }); await lockRowForUpdate(tx, 'AutomationRule', id); // Read after the lock: a concurrent edit of the rule is visible now. const row = await tx.automationRule.findUniqueOrThrow({ where: { id }, select: RULE_COLUMNS, }); return { ...toActiveRule(trigger, row), id }; }