Consolidate server utilities into shared module
- Add lib/server/utils/normalize.ts with normalizeTicker, normalizeTagsOrNull, nowIso, todayIso - Add lib/server/utils/validation.ts with asRecord, asBoolean, asStringArray, asEnum - Add lib/server/utils/index.ts re-exporting all utilities - Remove duplicate lib/server/utils.ts (old file) - Update all repos and files to use shared utilities - Remove redundant ?? '' from normalizeTicker calls - Update watchlist.ts to use normalizeTagsOrNull for null-return tags
This commit is contained in:
@@ -27,6 +27,14 @@ import {
|
||||
} from '@/lib/server/db/schema';
|
||||
import { getFilingByAccession, listFilingsRecords } from '@/lib/server/repos/filings';
|
||||
import { getWatchlistItemByTicker } from '@/lib/server/repos/watchlist';
|
||||
import {
|
||||
normalizeTicker,
|
||||
normalizeTags,
|
||||
normalizeOptionalString,
|
||||
normalizeRecord,
|
||||
normalizePositiveInteger,
|
||||
nowIso
|
||||
} from '@/lib/server/utils';
|
||||
|
||||
type ResearchArtifactRow = typeof researchArtifact.$inferSelect;
|
||||
type ResearchMemoRow = typeof researchMemo.$inferSelect;
|
||||
@@ -51,55 +59,6 @@ const RESEARCH_PACKET_SECTION_TITLES: Record<ResearchMemoSection, string> = {
|
||||
next_actions: 'Next Actions'
|
||||
};
|
||||
|
||||
function normalizeTicker(ticker: string) {
|
||||
return ticker.trim().toUpperCase();
|
||||
}
|
||||
|
||||
function normalizeOptionalString(value?: string | null) {
|
||||
const normalized = value?.trim();
|
||||
return normalized ? normalized : null;
|
||||
}
|
||||
|
||||
function normalizePositiveInteger(value?: number | null) {
|
||||
if (value === null || value === undefined || !Number.isFinite(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = Math.trunc(value);
|
||||
return normalized > 0 ? normalized : null;
|
||||
}
|
||||
|
||||
function normalizeRecord(value?: Record<string, unknown> | null) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeTags(tags?: string[] | null) {
|
||||
if (!Array.isArray(tags)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const unique = new Set<string>();
|
||||
|
||||
for (const entry of tags) {
|
||||
if (typeof entry !== 'string') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const normalized = entry.trim();
|
||||
if (!normalized) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unique.add(normalized);
|
||||
}
|
||||
|
||||
return [...unique];
|
||||
}
|
||||
|
||||
function buildArtifactSearchText(input: {
|
||||
title?: string | null;
|
||||
summary?: string | null;
|
||||
@@ -409,7 +368,7 @@ export async function createResearchArtifactRecord(input: {
|
||||
throw new Error('ticker is required');
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const now = nowIso();
|
||||
const title = normalizeOptionalString(input.title);
|
||||
const summary = normalizeOptionalString(input.summary);
|
||||
const bodyMarkdown = normalizeOptionalString(input.bodyMarkdown);
|
||||
@@ -520,7 +479,7 @@ export async function upsertSystemResearchArtifact(input: {
|
||||
search_text: searchText,
|
||||
tags: normalizeTags(input.tags),
|
||||
metadata: normalizeRecord(input.metadata),
|
||||
updated_at: new Date().toISOString()
|
||||
updated_at: nowIso()
|
||||
})
|
||||
.where(eq(researchArtifact.id, existing.id))
|
||||
.returning();
|
||||
@@ -566,7 +525,7 @@ export async function updateResearchArtifactRecord(input: {
|
||||
metadata,
|
||||
tags,
|
||||
search_text: searchText,
|
||||
updated_at: new Date().toISOString()
|
||||
updated_at: nowIso()
|
||||
})
|
||||
.where(eq(researchArtifact.id, input.id))
|
||||
.returning();
|
||||
@@ -702,7 +661,7 @@ export async function upsertResearchMemoRecord(input: {
|
||||
throw new Error('ticker is required');
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const now = nowIso();
|
||||
const existing = await getResearchMemoByTicker(input.userId, ticker);
|
||||
|
||||
if (!existing) {
|
||||
@@ -796,7 +755,7 @@ export async function addResearchMemoEvidenceLink(input: {
|
||||
.then((rows) => (rows[0]?.maxOrder ?? 0) + 1)
|
||||
: Math.max(0, Math.trunc(input.sortOrder));
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const now = nowIso();
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(researchMemoEvidence)
|
||||
@@ -894,7 +853,7 @@ export async function getResearchPacket(userId: string, ticker: string): Promise
|
||||
return {
|
||||
ticker: normalizedTicker,
|
||||
companyName: coverage?.company_name ?? latestFiling?.company_name ?? null,
|
||||
generated_at: new Date().toISOString(),
|
||||
generated_at: nowIso(),
|
||||
memo,
|
||||
sections: toPacketSections(memo, evidenceBySection)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user