docs: expand session persistence event catalog

This commit is contained in:
Tianyi Cui
2026-07-18 15:11:51 +08:00
parent 7a29676c01
commit 2b4641799e
4 changed files with 434 additions and 83 deletions

View File

@@ -9,6 +9,7 @@ import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import {
annotateSurface,
collectEventEnvelopeTypes,
collectLogEvents,
collectSurfaceEventTypes,
render,
@@ -56,6 +57,7 @@ describe('gen-persistence-catalog collectLogEvents', () => {
scope: 'fix',
doc: 'A thing was recorded.',
payload: '{ turn: number }',
declaration: '/** A thing was recorded. */\n\'fix/happened\': { turn: number }',
source: 'packages/core/fix/src/types.ts:3',
})
})
@@ -102,10 +104,13 @@ describe('gen-persistence-catalog collectLogEvents', () => {
it('collapses a newline-separated multi-line payload to a valid one-line fragment', () => {
const events = collectLogEvents(make({
'packages/group/fix/src/types.ts': merge(
' /** Wide payload. */\n \'fix/wide\': {\n alpha: string[]\n range: { start: number; end: number }\n count: number\n }',
' /** Wide payload. */\n \'fix/wide\': {\n /** Alpha values. */\n alpha: string[]\n range: { start: number; end: number }\n count: number\n }',
),
}))
expect(events[0]?.payload).toBe('{ alpha: string[]; range: { start: number; end: number }; count: number }')
expect(events[0]?.declaration).toBe(
'/** Wide payload. */\n\'fix/wide\': {\n /** Alpha values. */\n alpha: string[]\n range: { start: number; end: number }\n count: number\n}',
)
})
it('hard-errors on a member with no description prose', () => {
@@ -158,6 +163,59 @@ describe('gen-persistence-catalog collectLogEvents', () => {
})
})
describe('gen-persistence-catalog collectEventEnvelopeTypes', () => {
const declarations = `/** Event keys. */
export type SessionEventType = keyof SessionEventMap
/** Surface-producing event keys. */
export type SurfaceEventType = 'fix/message'
/** Surface placement. */
export type SurfaceOp = 'append'
/** One persisted event. */
export type SessionEvent<T extends SessionEventType = SessionEventType> = { type: T }
`
it('extracts the envelope declarations with their complete JSDoc in canonical order', () => {
const entries = collectEventEnvelopeTypes(make({
'packages/core/fix/package.json': OWNER_MANIFEST,
'packages/core/fix/src/types.ts': declarations,
}))
expect(entries.map(entry => entry.name)).toEqual([
'SessionEventType',
'SurfaceEventType',
'SurfaceOp',
'SessionEvent',
])
expect(entries[3]).toMatchObject({
declaration: '/** One persisted event. */\nexport type SessionEvent<T extends SessionEventType = SessionEventType> = { type: T }',
source: 'packages/core/fix/src/types.ts:8',
})
})
it('hard-errors when an envelope declaration is missing', () => {
expect(() => collectEventEnvelopeTypes(make({
'packages/core/fix/package.json': OWNER_MANIFEST,
'packages/core/fix/src/types.ts': declarations.replace('/** Surface placement. */\nexport type SurfaceOp = \'append\'\n', ''),
}))).toThrow(/missing event-envelope declaration\(s\): SurfaceOp/)
})
it('hard-errors on duplicate, unexported, undocumented, or mistagged envelope declarations', () => {
const violations = new RegExp([
'4 JSDoc completeness violation\\(s\\)',
'[\\s\\S]*not exported',
'[\\s\\S]*@mode tag',
'[\\s\\S]*SurfaceOp.*no description prose',
'[\\s\\S]*SessionEvent.*already declared',
].join(''))
expect(() => collectEventEnvelopeTypes(make({
'packages/core/fix/package.json': OWNER_MANIFEST,
'packages/core/fix/src/types.ts': declarations
.replace('/** Event keys. */\nexport type SessionEventType', '/** Event keys.\n * @mode emit\n */\ntype SessionEventType')
.replace('/** Surface placement. */\n', '')
+ '/** Duplicate event. */\nexport type SessionEvent = { type: never }\n',
}))).toThrow(violations)
})
})
describe('gen-persistence-catalog collectSurfaceEventTypes', () => {
it('parses the literal union', () => {
const types = collectSurfaceEventTypes(make({
@@ -192,9 +250,21 @@ describe('gen-persistence-catalog annotateSurface + render', () => {
scope: name.split('/')[0] ?? name,
payload: '{ turn: number }',
doc: `Records ${name}.`,
declaration: `/** Records ${name}. */\n'${name}': { turn: number }`,
source: 'packages/core/fix/src/types.ts:3',
})
const envelopeTypes = [
'SessionEventType',
'SurfaceEventType',
'SurfaceOp',
'SessionEvent',
].map(name => ({
name: name as 'SessionEventType' | 'SurfaceEventType' | 'SurfaceOp' | 'SessionEvent',
declaration: `/** ${name}. */\nexport type ${name} = never`,
source: 'packages/core/fix/src/types.ts:1',
}))
it('badges union members surface and everything else log-only', () => {
const annotated = annotateSurface([entry('fix/message'), entry('fix/marker')], ['fix/message'])
expect(annotated.map(e => [e.name, e.surface])).toEqual([['fix/message', true], ['fix/marker', false]])
@@ -205,11 +275,13 @@ describe('gen-persistence-catalog annotateSurface + render', () => {
.toThrow(/'fix\/ghost' name no declared log event/)
})
it('renders badges, payload fences, and the generated-file header', () => {
const out = render(annotateSurface([entry('fix/message'), entry('fix/marker')], ['fix/message']))
it('renders badges, declaration fences, and the generated-file header', () => {
const out = render(annotateSurface([entry('fix/message'), entry('fix/marker')], ['fix/message']), envelopeTypes)
expect(out).toContain('Generated by scripts/gen-persistence-catalog.ts')
expect(out).toContain('# Session Persistence Event Catalog')
expect(out).toContain('```ts persistence-catalog\n/** SessionEventType. */\nexport type SessionEventType = never')
expect(out).toContain('#### `fix/message` — surface')
expect(out).toContain('#### `fix/marker` — log-only')
expect(out).toContain('```ts persistence-catalog\n\'fix/marker\': { turn: number }\n```')
expect(out).toContain('```ts persistence-catalog\n/** Records fix/marker. */\n\'fix/marker\': { turn: number }\n```')
})
})