diff --git a/apps/server/src/api-data/rundown/rundown.dao.ts b/apps/server/src/api-data/rundown/rundown.dao.ts index ef42edafe..b0f6828e4 100644 --- a/apps/server/src/api-data/rundown/rundown.dao.ts +++ b/apps/server/src/api-data/rundown/rundown.dao.ts @@ -27,6 +27,7 @@ import { isPlayableEvent, } from 'ontime-types'; import { addToRundown, createGroup, customFieldLabelToKey, getInsertAfterId, insertAtIndex } from 'ontime-utils'; +import type { DeepReadonly } from 'ts-essentials'; import { getDataProvider } from '../../classes/data-provider/DataProvider.js'; import { consoleError } from '../../utils/console.js'; @@ -80,9 +81,16 @@ export const getRundownMetadata = (): Readonly => rundownMetada export const getProjectCustomFields = (): Readonly => projectCustomFields; export const getEntryWithId = (entryId: EntryId): OntimeEntry | undefined => cachedRundown.entries[entryId]; -type Transaction = { +/** + * @param R the type callers see for `rundown` - a plain, mutable `Rundown` when the + * transaction was opened with `mutableRundown: true`, otherwise a `DeepReadonly` + * so that accidentally mutating an entry (or an order array) on a non-mutable transaction + * - which would silently corrupt the live cache without going through commit() - is a + * compile-time error instead of a runtime bug. + */ +type Transaction = { customFields: CustomFields; - rundown: Rundown; + rundown: R; commit: (shouldProcess?: boolean) => Promise<{ rundown: Readonly; @@ -103,7 +111,13 @@ type TransactionOptions = { rundownId?: string; }; -export function createTransaction(options: TransactionOptions): Transaction { +export function createTransaction(options: TransactionOptions & { mutableRundown: true }): Transaction; +export function createTransaction( + options: TransactionOptions & { mutableRundown?: false }, +): Transaction>; +export function createTransaction( + options: TransactionOptions, +): Transaction | Transaction> { const targetId = options.rundownId ?? cachedRundown.id; const isLoaded = targetId === cachedRundown.id; const sourceRundown: Rundown = isLoaded ? cachedRundown : (getDataProvider().getRundown(targetId) as Rundown);