test: define schema contract for topic fact graph
This commit is contained in:
34
src/core/schema.ts
Normal file
34
src/core/schema.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export const TOPICS_TABLE = 'topics';
|
||||
export const FACTS_TABLE = 'facts';
|
||||
export const FACT_TOPICS_TABLE = 'fact_topics';
|
||||
|
||||
export const TOPIC_COLUMNS = [
|
||||
'id',
|
||||
'name',
|
||||
'normalized_name',
|
||||
'category',
|
||||
'granularity',
|
||||
'description',
|
||||
'metadata',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
] as const;
|
||||
|
||||
export const FACT_COLUMNS = [
|
||||
'id',
|
||||
'statement',
|
||||
'summary',
|
||||
'source',
|
||||
'confidence',
|
||||
'metadata',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
] as const;
|
||||
|
||||
export const FACT_TOPIC_COLUMNS = [
|
||||
'fact_id',
|
||||
'topic_id',
|
||||
'role',
|
||||
'position',
|
||||
'created_at',
|
||||
] as const;
|
||||
22
src/types/api.ts
Normal file
22
src/types/api.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { JsonValue, TopicCategory, TopicGranularity } from './domain';
|
||||
|
||||
export interface UpsertTopicInput {
|
||||
name: string;
|
||||
category?: TopicCategory;
|
||||
granularity?: TopicGranularity;
|
||||
description?: string | null;
|
||||
metadata?: JsonValue | null;
|
||||
}
|
||||
|
||||
export interface TopicLinkInput extends UpsertTopicInput {
|
||||
role?: string | null;
|
||||
}
|
||||
|
||||
export interface AddFactInput {
|
||||
statement: string;
|
||||
summary?: string | null;
|
||||
source?: string | null;
|
||||
confidence?: number | null;
|
||||
metadata?: JsonValue | null;
|
||||
topics: TopicLinkInput[];
|
||||
}
|
||||
7
src/types/database.ts
Normal file
7
src/types/database.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { FactRecord, FactTopicRecord, TopicRecord } from './domain';
|
||||
|
||||
export interface IdentityDatabaseSchema {
|
||||
topics: TopicRecord;
|
||||
facts: FactRecord;
|
||||
fact_topics: FactTopicRecord;
|
||||
}
|
||||
37
src/types/domain.ts
Normal file
37
src/types/domain.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export type TopicCategory = 'entity' | 'concept' | 'temporal' | 'custom';
|
||||
|
||||
export type TopicGranularity = 'abstract' | 'concrete' | 'mixed';
|
||||
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
||||
|
||||
export interface TopicRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
normalized_name: string;
|
||||
category: TopicCategory;
|
||||
granularity: TopicGranularity;
|
||||
description: string | null;
|
||||
metadata: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface FactRecord {
|
||||
id: string;
|
||||
statement: string;
|
||||
summary: string | null;
|
||||
source: string | null;
|
||||
confidence: number | null;
|
||||
metadata: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface FactTopicRecord {
|
||||
fact_id: string;
|
||||
topic_id: string;
|
||||
role: string | null;
|
||||
position: number;
|
||||
created_at: string;
|
||||
}
|
||||
88
tests/migrations.test.ts
Normal file
88
tests/migrations.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { sql } from 'kysely';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { createDatabase } from '../src/adapters/dialect';
|
||||
import { initializeSchema } from '../src/core/migrations';
|
||||
|
||||
const openConnections: Array<() => Promise<void>> = [];
|
||||
|
||||
afterEach(async () => {
|
||||
while (openConnections.length > 0) {
|
||||
const close = openConnections.pop();
|
||||
if (close) {
|
||||
await close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('initializeSchema', () => {
|
||||
it('creates the topics, facts, and fact_topics tables', async () => {
|
||||
const connection = await createDatabase({ client: 'sqlite', filename: ':memory:' });
|
||||
openConnections.push(connection.destroy);
|
||||
|
||||
await initializeSchema(connection.db);
|
||||
|
||||
const tables = await sql<{ name: string }>`
|
||||
SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table'
|
||||
ORDER BY name
|
||||
`.execute(connection.db);
|
||||
|
||||
const tableNames = tables.rows.map((row) => row.name);
|
||||
|
||||
expect(tableNames).toContain('topics');
|
||||
expect(tableNames).toContain('facts');
|
||||
expect(tableNames).toContain('fact_topics');
|
||||
});
|
||||
|
||||
it('creates the expected columns for each table', async () => {
|
||||
const connection = await createDatabase({ client: 'sqlite', filename: ':memory:' });
|
||||
openConnections.push(connection.destroy);
|
||||
|
||||
await initializeSchema(connection.db);
|
||||
|
||||
const topicsColumns = await sql<{ name: string }>`PRAGMA table_info(topics)`.execute(connection.db);
|
||||
const factsColumns = await sql<{ name: string }>`PRAGMA table_info(facts)`.execute(connection.db);
|
||||
const factTopicsColumns = await sql<{ name: string }>`PRAGMA table_info(fact_topics)`.execute(connection.db);
|
||||
|
||||
expect(topicsColumns.rows.map((row) => row.name)).toEqual([
|
||||
'id',
|
||||
'name',
|
||||
'normalized_name',
|
||||
'category',
|
||||
'granularity',
|
||||
'description',
|
||||
'metadata',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]);
|
||||
|
||||
expect(factsColumns.rows.map((row) => row.name)).toEqual([
|
||||
'id',
|
||||
'statement',
|
||||
'summary',
|
||||
'source',
|
||||
'confidence',
|
||||
'metadata',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]);
|
||||
|
||||
expect(factTopicsColumns.rows.map((row) => row.name)).toEqual([
|
||||
'fact_id',
|
||||
'topic_id',
|
||||
'role',
|
||||
'position',
|
||||
'created_at',
|
||||
]);
|
||||
});
|
||||
|
||||
it('is idempotent when called more than once', async () => {
|
||||
const connection = await createDatabase({ client: 'sqlite', filename: ':memory:' });
|
||||
openConnections.push(connection.destroy);
|
||||
|
||||
await initializeSchema(connection.db);
|
||||
await expect(initializeSchema(connection.db)).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user