From 857389e68bd1b33bda4d46780780f473505cdf11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 20 Oct 2026 18:15:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(contracts):=20UniversalRow=20=E5=A5=91?= =?UTF-8?q?=E7=BA=A6=20v1=20=E5=86=BB=E7=BB=93=20+=20=E5=AE=88=E5=8D=AB=20?= =?UTF-8?q?+=20=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- packages/contracts/package.json | 18 ++++++++++ packages/contracts/src/index.ts | 1 + packages/contracts/src/universal-row.ts | 36 +++++++++++++++++++ packages/contracts/test/universal-row.test.ts | 27 ++++++++++++++ packages/contracts/tsconfig.json | 7 ++++ pnpm-lock.yaml | 2 ++ 6 files changed, 91 insertions(+) create mode 100644 packages/contracts/package.json create mode 100644 packages/contracts/src/index.ts create mode 100644 packages/contracts/src/universal-row.ts create mode 100644 packages/contracts/test/universal-row.test.ts create mode 100644 packages/contracts/tsconfig.json diff --git a/packages/contracts/package.json b/packages/contracts/package.json new file mode 100644 index 0000000..b8ec218 --- /dev/null +++ b/packages/contracts/package.json @@ -0,0 +1,18 @@ +{ + "name": "@sf/contracts", + "version": "0.1.0", + "description": "SiteForge 跨源契约:UniversalRow 统一中间格式(契约冻结,变更须升版)", + "type": "module", + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "default": "./dist/src/index.js" + } + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "test": "vitest run" + } +} diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts new file mode 100644 index 0000000..3d1b3fa --- /dev/null +++ b/packages/contracts/src/index.ts @@ -0,0 +1 @@ +export * from './universal-row.js' diff --git a/packages/contracts/src/universal-row.ts b/packages/contracts/src/universal-row.ts new file mode 100644 index 0000000..bbcbe16 --- /dev/null +++ b/packages/contracts/src/universal-row.ts @@ -0,0 +1,36 @@ +export const UNIVERSAL_ROW_VERSION = 1 + +export interface UniversalRow { + sourceType: string + sourceId: string + status: string + locale: string + sourceUrl?: string + slug?: string + title?: string + excerpt?: string + bodyHtml?: string + publishedAt?: string + modifiedAt?: string + taxonomyRefs: Array<{ taxonomy: string; sourceTermId: string }> + attachmentRefs: Array<{ role: string; sourceId?: string; sourceUrl?: string }> + seo: Record + knownFields: Record + extras: Record + rawLocation: string +} + +const REQUIRED: (keyof UniversalRow)[] = [ + 'sourceType', 'sourceId', 'status', 'locale', + 'taxonomyRefs', 'attachmentRefs', 'seo', 'knownFields', 'extras', 'rawLocation', +] + +export function isUniversalRow(v: unknown): v is UniversalRow { + if (typeof v !== 'object' || v === null) return false + const row = v as Record + return REQUIRED.every((k) => k in row) + && Array.isArray(row.taxonomyRefs) && Array.isArray(row.attachmentRefs) + && row.seo !== null && typeof row.seo === 'object' + && row.knownFields !== null && typeof row.knownFields === 'object' + && row.extras !== null && typeof row.extras === 'object' +} diff --git a/packages/contracts/test/universal-row.test.ts b/packages/contracts/test/universal-row.test.ts new file mode 100644 index 0000000..31bad0e --- /dev/null +++ b/packages/contracts/test/universal-row.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest' +import { UNIVERSAL_ROW_VERSION, isUniversalRow, type UniversalRow } from '../src/index.js' + +const valid: UniversalRow = { + sourceType: 'wp:post', sourceId: '101', status: 'publish', locale: 'en', + sourceUrl: 'https://fixture.example/news/fixture-news-a.html', + slug: 'fixture-news-a', title: 'Fixture News A', excerpt: 'Fixture excerpt A', + bodyHtml: '

Body

', publishedAt: '2026-10-05T09:00:00Z', modifiedAt: '2026-10-06T10:00:00Z', + taxonomyRefs: [{ taxonomy: 'category', sourceTermId: '31' }], + attachmentRefs: [{ role: 'featured', sourceId: '201' }], + seo: { title: 'Fixture SEO Title A' }, + knownFields: {}, extras: { custom_meta: 'x' }, + rawLocation: 'wxr://item/3', +} + +describe('UniversalRow 契约', () => { + it('版本号为 1(冻结,变更须升版并保留转换测试)', () => { + expect(UNIVERSAL_ROW_VERSION).toBe(1) + }) + it('合法对象通过守卫', () => { + expect(isUniversalRow(valid)).toBe(true) + }) + it('缺必填字段拒绝', () => { + const { sourceId: _omit, ...bad } = valid + expect(isUniversalRow(bad)).toBe(false) + }) +}) diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json new file mode 100644 index 0000000..ae5ba21 --- /dev/null +++ b/packages/contracts/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src", "test"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8691509..6c86373 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,8 @@ importers: specifier: ^2.1.0 version: 2.1.9 + packages/contracts: {} + packages: '@esbuild/aix-ppc64@0.21.5':