Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/modules/indexer/price-snapshot-writer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// src/modules/indexer/price-snapshot-writer.ts
// Price snapshot writer that skips the write when the price is unchanged
// from the most recent stored snapshot for that creator (#763).
//
// Recording a snapshot whose price equals the previous one wastes storage
// and adds flat, redundant points to the price history chart. This writer
// compares the incoming price against the latest CreatorPriceHistory row
// and only inserts when the price actually moved. The first snapshot for a
// creator is always written because there is nothing to compare against.
//
// Note: the per-trade indexer path (upsertPriceSnapshot) intentionally keeps
// recording every trade, including unchanged prices, because the full-window
// trade TWAP depends on it (#880). Use this writer for periodic/sampled
// snapshots, not for the trade log.

import { prisma } from '../../utils/prisma.utils';
import { logger } from '../../utils/logger.utils';

export type PriceSnapshotWriteResult =
| { written: true; reason: 'first' | 'changed' }
| { written: false; reason: 'unchanged' };

export type PriceSnapshotWriterDeps = {
/** Returns the most recent stored snapshot for the creator, or null. */
findLatest: (creatorId: string) => Promise<{ price: bigint } | null>;
/** Persists a new snapshot row for the creator. */
create: (creatorId: string, price: bigint) => Promise<unknown>;
};

const defaultDeps: PriceSnapshotWriterDeps = {
findLatest: (creatorId) =>
prisma.creatorPriceHistory.findFirst({
where: { creatorId },
orderBy: { recordedAt: 'desc' },
select: { price: true },
}),
create: (creatorId, price) =>
prisma.creatorPriceHistory.create({
data: { creatorId, price },
}),
};

/**
* Writes a price snapshot for a creator unless the price is unchanged from
* the most recent stored snapshot.
*
* - No previous snapshot: always written (`reason: 'first'`).
* - Price differs from the previous snapshot: written (`reason: 'changed'`).
* - Price equals the previous snapshot: skipped, no database write
* (`reason: 'unchanged'`).
*/
export async function writePriceSnapshot(
creatorId: string,
price: bigint,
deps?: Partial<PriceSnapshotWriterDeps>
): Promise<PriceSnapshotWriteResult> {
const findLatest = deps?.findLatest ?? defaultDeps.findLatest;
const create = deps?.create ?? defaultDeps.create;

const latest = await findLatest(creatorId);

if (latest && latest.price === price) {
logger.debug(
{ creator_id: creatorId, price: price.toString() },
'price-snapshot-writer: skipped (price unchanged)'
);
return { written: false, reason: 'unchanged' };
}

await create(creatorId, price);

const reason = latest ? 'changed' : 'first';
logger.debug(
{
creator_id: creatorId,
new_price: price.toString(),
previous_price: latest ? latest.price.toString() : null,
},
`price-snapshot-writer: written (${reason})`
);
return { written: true, reason };
}
115 changes: 115 additions & 0 deletions src/modules/indexer/price-snapshot-writer.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// src/modules/indexer/price-snapshot-writer.unit.test.ts
// Unit tests for #763: the price snapshot writer skips the write when the
// new price equals the most recent stored snapshot for that creator.
//
// Uses jest mocks; no database required.

jest.mock('../../utils/prisma.utils', () => ({
prisma: {
creatorPriceHistory: {
findFirst: jest.fn(),
create: jest.fn(),
},
},
}));

jest.mock('../../utils/logger.utils', () => ({
logger: {
debug: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
},
}));

import { prisma } from '../../utils/prisma.utils';
import { writePriceSnapshot } from './price-snapshot-writer';

const mockPrisma = prisma as unknown as {
creatorPriceHistory: {
findFirst: jest.Mock;
create: jest.Mock;
};
};

const CREATOR_ID = 'creator-763';

describe('#763 price snapshot writer', () => {
beforeEach(() => {
jest.clearAllMocks();
mockPrisma.creatorPriceHistory.findFirst.mockReset();
mockPrisma.creatorPriceHistory.create.mockReset();
mockPrisma.creatorPriceHistory.create.mockResolvedValue({ id: 'row' });
});

it('reads the most recent snapshot for the creator', async () => {
mockPrisma.creatorPriceHistory.findFirst.mockResolvedValue(null);

await writePriceSnapshot(CREATOR_ID, BigInt(1_000_000));

expect(mockPrisma.creatorPriceHistory.findFirst).toHaveBeenCalledWith({
where: { creatorId: CREATOR_ID },
orderBy: { recordedAt: 'desc' },
select: { price: true },
});
});

it('skips the database write when the price is unchanged', async () => {
mockPrisma.creatorPriceHistory.findFirst.mockResolvedValue({
price: BigInt(1_000_000),
});

const result = await writePriceSnapshot(CREATOR_ID, BigInt(1_000_000));

expect(mockPrisma.creatorPriceHistory.create).not.toHaveBeenCalled();
expect(result).toEqual({ written: false, reason: 'unchanged' });
});

it('writes when the price differs from the previous snapshot', async () => {
mockPrisma.creatorPriceHistory.findFirst.mockResolvedValue({
price: BigInt(1_000_000),
});

const result = await writePriceSnapshot(CREATOR_ID, BigInt(1_100_000));

expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledTimes(1);
expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledWith({
data: { creatorId: CREATOR_ID, price: BigInt(1_100_000) },
});
expect(result).toEqual({ written: true, reason: 'changed' });
});

it('always writes the first snapshot for a creator', async () => {
mockPrisma.creatorPriceHistory.findFirst.mockResolvedValue(null);

const result = await writePriceSnapshot(CREATOR_ID, BigInt(1_000_000));

expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledTimes(1);
expect(mockPrisma.creatorPriceHistory.create).toHaveBeenCalledWith({
data: { creatorId: CREATOR_ID, price: BigInt(1_000_000) },
});
expect(result).toEqual({ written: true, reason: 'first' });
});

it('uses injected dependencies when provided', async () => {
const findLatest = jest.fn().mockResolvedValue({ price: BigInt(5) });
const create = jest.fn().mockResolvedValue(undefined);

const unchanged = await writePriceSnapshot(CREATOR_ID, BigInt(5), {
findLatest,
create,
});
const changed = await writePriceSnapshot(CREATOR_ID, BigInt(6), {
findLatest,
create,
});

expect(findLatest).toHaveBeenCalledWith(CREATOR_ID);
expect(create).toHaveBeenCalledTimes(1);
expect(create).toHaveBeenCalledWith(CREATOR_ID, BigInt(6));
expect(unchanged).toEqual({ written: false, reason: 'unchanged' });
expect(changed).toEqual({ written: true, reason: 'changed' });
expect(mockPrisma.creatorPriceHistory.findFirst).not.toHaveBeenCalled();
expect(mockPrisma.creatorPriceHistory.create).not.toHaveBeenCalled();
});
});
Loading