chore(import): unsupported/failed 分项计数与键契约 JSDoc;NULL-entry 缝注释;lineage 语义注记
审查 cheap riders 四项:handleUnmappable 分项 unsupportedRecords/ failedRecords(按事件计——拆分只在分派点可知,Task 6 无从重derive,测试 对拍两键);ImportPipelineOutcome.counts 键契约注记(跨写入器扁平全局 命名空间、同名 CLOBBER 不求和、各键归属声明、Task 4 勿复用 'skipped'); 六列 NULL-entry 已知死角缝注释(23505 → Task 6 收编);态 1 checksum-diff 的 import_job_id lineage 语义注记(source_records 留建行 job、新 job 记 content_revisions)。296 全绿;typecheck/lint 通过。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,23 @@ export interface ContentEventHandler {
|
||||
finish(): ImportPipelineOutcome
|
||||
}
|
||||
|
||||
/**
|
||||
* 本写入器的 counts 键与归属(**counts 键是跨写入器的扁平全局命名空间**——import.service
|
||||
* 按"返回值覆盖累加器同名键"合并,同名键 CLOBBER 不求和,各写入器必须各持独占键名,见
|
||||
* ImportPipelineOutcome.counts 的键契约注记)。本写入器独占:contentEntries /
|
||||
* revisionsCreated / sourceRecords / skipped / unsupportedRecords / failedRecords——
|
||||
* **Task 4 media-writer 起新键名,勿复用 'skipped'**(语义不同会互相覆盖)。
|
||||
* 前四键恒出现在报告;unsupportedRecords/failedRecords 只在 >0 时出现(分项按事件计)。
|
||||
*/
|
||||
interface ContentWriterCounts {
|
||||
contentEntries: number
|
||||
revisionsCreated: number
|
||||
sourceRecords: number
|
||||
skipped: number
|
||||
unsupportedRecords?: number
|
||||
failedRecords?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 内容写入器的事件分派句柄。产出 counts:
|
||||
* - contentEntries:新建 content_entries 数(幂等重跑/变化路径为 0);
|
||||
@@ -134,7 +151,9 @@ export interface ContentEventHandler {
|
||||
* - sourceRecords:新建 source_records 数(内容/attachment/unsupported/error 全计;
|
||||
* 跨快照重导按快照补行故可为正);
|
||||
* - skipped:内容行未产修订的跳过数(同快照 checksum 命中、跨快照无业务变化、locale
|
||||
* 拒收三类;attachment 命中不计——媒体域是 Task 4 主场)。
|
||||
* 拒收三类;attachment 命中不计——媒体域是 Task 4 主场);
|
||||
* - unsupportedRecords/failedRecords:unsupported/error 事件分项数(**按事件计**非按
|
||||
* 插入行——重跑行幂等而计数如实;拆分只在分派点可知,Task 6 报告层无从重derive)。
|
||||
* issues:locale 不匹配、未知 sourceType 兜底——显式报告(§7.6 禁静默)。
|
||||
*/
|
||||
export function createContentEventHandler(identity: {
|
||||
@@ -152,7 +171,14 @@ export function createContentEventHandler(identity: {
|
||||
versions: ImportVersionBlock
|
||||
}): ContentEventHandler {
|
||||
const { db, siteId } = identity
|
||||
const counts = { contentEntries: 0, revisionsCreated: 0, sourceRecords: 0, skipped: 0 }
|
||||
// 交叉 Record:finish() 要把 counts 交还给 ImportPipelineOutcome(Record<string, number>)
|
||||
// ——接口无索引签名不可赋值;交叉后已知键类型不变、动态赋值兼容
|
||||
const counts: ContentWriterCounts & Record<string, number> = {
|
||||
contentEntries: 0,
|
||||
revisionsCreated: 0,
|
||||
sourceRecords: 0,
|
||||
skipped: 0,
|
||||
}
|
||||
const issues: string[] = []
|
||||
|
||||
/** 修订插入(纯 INSERT——不可变表;data 与 checksum 同构于 mapped_content) */
|
||||
@@ -259,14 +285,18 @@ export function createContentEventHandler(identity: {
|
||||
const data = mappedContentOf(row)
|
||||
|
||||
// 态 1:本快照行已存在(挂 entry——本写入器是 content 行 source_records 的唯一写入方,
|
||||
// 挂接不变量由写入路径自身保证)
|
||||
// 挂接不变量由写入路径自身保证)。
|
||||
// 已知死角:NULL-entry 占位行(外源 seed/未来写入方)+ 后续映射扩充重导 → 全新建分支
|
||||
// 插入撞六列唯一约束 23505;Task 6 收编(UPDATE 该行挂 entry)——见审查记录
|
||||
const snapRow = await findSnapshotRecord(row)
|
||||
if (snapRow !== null && snapRow.contentEntryId !== null) {
|
||||
if (snapRow.normalizedChecksum === checksum) {
|
||||
counts.skipped++
|
||||
return
|
||||
}
|
||||
// checksum 异:快照行前移(checksum/source_url)+ 新修订 + current 前移(事务)
|
||||
// checksum 异:快照行前移(checksum/source_url)+ 新修订 + current 前移(事务)。
|
||||
// lineage 语义(有意):source_records.import_job_id 留在建行 job(行的溯源=创建它的
|
||||
// 导入),本次 job 记在 content_revisions.import_job_id(谁产了新修订)
|
||||
const entryId = snapRow.contentEntryId // 事务回调外的 const 收窄(属性收窄不进闭包)
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
@@ -396,6 +426,10 @@ export function createContentEventHandler(identity: {
|
||||
): Promise<void> {
|
||||
// postId 缺失不硬造身份——source_id 以 rawLocation 回退(行可审计、身份不虚构)
|
||||
const sourceId = ev.postId ?? ev.rawLocation
|
||||
// 分项计数按事件计(非按插入行):unsupported/failed 的拆分只在分派点可知,
|
||||
// Task 6 报告层无法从 sourceRecords 总数重derive;重跑时行幂等但计数如实。
|
||||
if (parseStatus === 'unsupported') counts.unsupportedRecords = (counts.unsupportedRecords ?? 0) + 1
|
||||
else counts.failedRecords = (counts.failedRecords ?? 0) + 1
|
||||
const inserted = await db
|
||||
.insert(sourceRecords)
|
||||
.values({
|
||||
|
||||
@@ -127,6 +127,14 @@ export interface ImportJobContext {
|
||||
* 无返回值,累加器现值原样进报告(部分产出可审计)。
|
||||
*/
|
||||
export interface ImportPipelineOutcome {
|
||||
/**
|
||||
* 计数块(原样进报告)。**键契约:counts 键是跨写入器的扁平全局命名空间**——
|
||||
* 汇合规则按"返回值覆盖累加器同名键"合并(ctx.report.counts ⊕ outcome.counts),
|
||||
* 同名键 **CLOBBER 不求和**:各写入器必须各持独占键名并在自身 JSDoc 声明归属
|
||||
* (Task 2 term-writer:termsProcessed/autoCreatedTerms/contentTermsCreated;
|
||||
* Task 3 content-writer:contentEntries/revisionsCreated/sourceRecords/skipped/
|
||||
* unsupportedRecords/failedRecords——Task 4 media-writer 起新键名,勿复用 'skipped')。
|
||||
*/
|
||||
counts: Record<string, number>
|
||||
issues: string[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user