72 lines
3.0 KiB
TypeScript
72 lines
3.0 KiB
TypeScript
|
|
// 五列内容身份锚点共享 helper(Plan 4 Task 1,ADR-006、Plan 3 前置事实 ④):
|
|||
|
|
// source_records 的内容身份(site + connection + source_type + source_id + locale,
|
|||
|
|
// 五列——快照列不在键上,跨快照复用同一 content_entry)→ 挂接 entry 的最新快照行
|
|||
|
|
// (latest first)。此前同一查询在 content/term/route 三写入器各持一份拷贝——
|
|||
|
|
// publish 域(第 4 个消费方触发 Plan 3 审查既定条件)起抽此单源共享,零行为变更
|
|||
|
|
// (WHERE/ORDER BY/LIMIT 逐字保真,由既有集成测试守护)。
|
|||
|
|
//
|
|||
|
|
// 消费方契约:
|
|||
|
|
// - content-writer(findPriorLinkedRecord):id/checksum/entryId 全消费(幂等三态);
|
|||
|
|
// - term-writer(resolveContentEntryId)/ route-writer(resolveEntryAnchor):
|
|||
|
|
// 只消费 contentEntryId(entry 锚点解析)。
|
|||
|
|
// 六列(快照内)查询**不在**此处——那是 content/media 写入器各自的快照行查找
|
|||
|
|
// (snapshot_id 进键),与五列身份语义不同,勿合并。
|
|||
|
|
import { and, desc, eq, isNotNull } from 'drizzle-orm'
|
|||
|
|
import { sourceRecords } from '../../db/schema.js'
|
|||
|
|
import type { ImportDb } from './import.service.js'
|
|||
|
|
|
|||
|
|
/** 五列身份的固定侧(site + connection)——各写入器的 WriterContext 均结构性满足 */
|
|||
|
|
export interface AnchorIdentity {
|
|||
|
|
siteId: string
|
|||
|
|
connectionId: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 五列身份的行侧(UniversalRow 的身份三列投影形状) */
|
|||
|
|
export interface AnchorRowKey {
|
|||
|
|
sourceType: string
|
|||
|
|
sourceId: string
|
|||
|
|
locale: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 命中行(contentEntryId 非 null——谓词保证;normalizedChecksum 可空:record-only 足迹无) */
|
|||
|
|
export interface LinkedSourceRecord {
|
|||
|
|
id: string
|
|||
|
|
contentEntryId: string
|
|||
|
|
normalizedChecksum: string | null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 五列内容身份 → 挂接 entry 的最新 source_records 行(latest first——createdAt 降序,
|
|||
|
|
* 与既有三写入器同口径);无挂接行 → null。谓词含 content_entry_id IS NOT NULL
|
|||
|
|
* (record-only / 占位足迹不参与锚点解析)。
|
|||
|
|
*/
|
|||
|
|
export async function findLatestLinkedSourceRecord(
|
|||
|
|
db: ImportDb,
|
|||
|
|
identity: AnchorIdentity,
|
|||
|
|
row: AnchorRowKey,
|
|||
|
|
): Promise<LinkedSourceRecord | null> {
|
|||
|
|
const rows = await db
|
|||
|
|
.select({
|
|||
|
|
id: sourceRecords.id,
|
|||
|
|
contentEntryId: sourceRecords.contentEntryId,
|
|||
|
|
normalizedChecksum: sourceRecords.normalizedChecksum,
|
|||
|
|
})
|
|||
|
|
.from(sourceRecords)
|
|||
|
|
.where(
|
|||
|
|
and(
|
|||
|
|
eq(sourceRecords.siteId, identity.siteId),
|
|||
|
|
eq(sourceRecords.sourceConnectionId, identity.connectionId),
|
|||
|
|
eq(sourceRecords.sourceType, row.sourceType),
|
|||
|
|
eq(sourceRecords.sourceId, row.sourceId),
|
|||
|
|
eq(sourceRecords.locale, row.locale),
|
|||
|
|
isNotNull(sourceRecords.contentEntryId),
|
|||
|
|
),
|
|||
|
|
)
|
|||
|
|
.orderBy(desc(sourceRecords.createdAt))
|
|||
|
|
.limit(1)
|
|||
|
|
const r = rows[0]
|
|||
|
|
if (r === undefined) return null
|
|||
|
|
// contentEntryId 非 null:查询谓词保证(drizzle 谓词不细化 select 类型)
|
|||
|
|
return { id: r.id, contentEntryId: r.contentEntryId!, normalizedChecksum: r.normalizedChecksum }
|
|||
|
|
}
|