diff --git a/docs/technical/README.md b/docs/technical/README.md index 7a98fac9..2b0ccd2d 100644 --- a/docs/technical/README.md +++ b/docs/technical/README.md @@ -14,6 +14,7 @@ - [RUNTIME_STORY_TO_STDB_PHASE1_TRANSPORT_ABSTRACTION_2026-04-20.md](./RUNTIME_STORY_TO_STDB_PHASE1_TRANSPORT_ABSTRACTION_2026-04-20.md):把 `runtimeStoryService` 改成可替换 transport,为后续 STDB provider 接入预留稳定边界。 - [RUNTIME_STORY_TO_STDB_PHASE2_CONTRACT_DESIGN_2026-04-20.md](./RUNTIME_STORY_TO_STDB_PHASE2_CONTRACT_DESIGN_2026-04-20.md):梳理 runtime story 从 Express 迁到 STDB 所需的聚合 view、procedure、mapper 与前端 provider 设计。 - [RUNTIME_STORY_TO_STDB_PHASE2A_COMPAT_BRIDGE_2026-04-20.md](./RUNTIME_STORY_TO_STDB_PHASE2A_COMPAT_BRIDGE_2026-04-20.md):确认 runtime story 当前 STDB/Express 快照真源分裂,并补一层只改 story 边界的 STDB 兼容桥。 +- [STORY_WORLD_TO_STDB_CAPABILITY_FIRST_PLAN_2026-04-20.md](./STORY_WORLD_TO_STDB_CAPABILITY_FIRST_PLAN_2026-04-20.md):明确 story / custom world 后续迁移顺序应先拆 capability/provider,再逐段迁移纯逻辑,避免整块硬搬。 - [TASK_AUTO_COMMIT_WORKFLOW_2026-04-20.md](./TASK_AUTO_COMMIT_WORKFLOW_2026-04-20.md):任务完成后按文件边界自动提交的脚本与协作约定。 - [NODE_DEV_STARTUP_HOTFIX_2026-04-20.md](./NODE_DEV_STARTUP_HOTFIX_2026-04-20.md):`npm run dev` 启动失败的热修记录、根因与验证结果。 - [NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md](./NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md):当前 Node 运行时后端的技术栈、入口、鉴权、存储与接口知识图谱。 diff --git a/docs/technical/STORY_WORLD_TO_STDB_CAPABILITY_FIRST_PLAN_2026-04-20.md b/docs/technical/STORY_WORLD_TO_STDB_CAPABILITY_FIRST_PLAN_2026-04-20.md new file mode 100644 index 00000000..dd03d3d5 --- /dev/null +++ b/docs/technical/STORY_WORLD_TO_STDB_CAPABILITY_FIRST_PLAN_2026-04-20.md @@ -0,0 +1,103 @@ +# Story / World 向 STDB 迁移:能力先迁、逻辑后搬实施方案(2026-04-20) + +更新时间:`2026-04-20` + +## 1. 为什么先迁能力,不先整块搬逻辑 + +当前 `runtime story` 和 `custom world` 都不是“只差把逻辑换个地方跑”。 + +真实问题有两类: + +1. 依赖面过大,很多 service 直接吃 `RuntimeRepositoryPort` 这类大接口 +2. `LLM / SSE / HTTP request / 账号鉴权 / snapshot/session 存取` 仍然缠在一起 + +如果在这个阶段直接把整块逻辑搬去 STDB,会出现两种风险: + +1. 把 Express 耦合一并搬过去,结果不是迁移而是复制 +2. 在 STDB 里同时承接“确定性规则”和“非确定性外部能力”,边界会继续变脏 + +因此后续迁移顺序固定为: + +1. 先拆能力口子 +2. 再给能力口子补 STDB provider +3. 最后再搬只依赖这些能力口子的纯逻辑 + +## 2. 迁移分层 + +### 2.1 可以先收进 STDB 的能力 + +这类能力适合先做成 capability/provider: + +1. runtime snapshot 读取与写回 +2. custom world session 读取与写回 +3. custom world profile / works 聚合读取 +4. runtime story state 聚合读取 +5. runtime story action 聚合写入结果 + +它们的共同特点: + +1. 以数据真源为主 +2. 可以明确建模为 table / view / procedure +3. 前后端 contract 可以稳定冻结 + +### 2.2 不能直接塞进 reducer 的能力 + +以下能力不应和纯规则一起直接塞进 STDB reducer: + +1. LLM 调用 +2. SSE 流式输出 +3. 图片 / 资产生成 +4. 长任务轮询与异步编排 +5. 旧 HTTP 鉴权链路 + +这些能力应该保留在 Express 或 procedure / orchestrator 边界,直到它们被进一步拆清。 + +## 3. 当前代码收口原则 + +这一轮先不扩大兼容桥,而是先把依赖面收紧: + +1. `storyActionService` 只依赖 `RuntimeStoryCapability` +2. `CustomWorldSessionStore` 只依赖 `CustomWorldSessionCapability` +3. `CustomWorldAgentSessionStore` 只依赖 `CustomWorldSessionCapability` +4. `listCustomWorldWorkSummaries` 只依赖 `CustomWorldWorkSummaryCapability` + +这一步的意义不是“已经迁完 STDB”,而是: + +1. 后续替换 provider 时,不再需要动整块 `RuntimeRepositoryPort` +2. 测试 stub 会被迫只实现真实需要的能力 +3. 可以更准确地区分“真源能力迁移”和“业务逻辑迁移” + +## 4. 后续实施顺序 + +### 4.1 Story + +1. 继续收紧 `runtime story` 所需 capability +2. 为 `get state / resolve action` 设计正式 STDB provider contract +3. 让 `storyActionService` 改为吃 provider,而不是直接认具体仓储 +4. 再把可确定性的结算逻辑逐段迁入 STDB + +### 4.2 Custom World + +1. 先把 `session / works / profile` 各自 capability 固化 +2. 为 `custom world session` 提供 STDB provider +3. 为 `agent session` 的会话存取提供 STDB provider +4. 保留 `LLM / asset / stream` 在 orchestrator 边界 +5. 等会话和作品真源稳定后,再搬纯状态推导逻辑 + +## 5. 本轮落地点 + +本轮只做一件事: + +1. 把 `story/custom world` 的 service 依赖从大仓储接口改成最小 capability + +本轮不做: + +1. 新增更大范围兼容桥 +2. 直接把 `custom world agent` 整块搬进 STDB +3. 把 LLM / stream / asset orchestration 一起迁移 + +## 6. 验收标准 + +1. `server-node` 编译通过 +2. 相关测试 stub 不再依赖完整 `RuntimeRepositoryPort` +3. 文档明确固定“能力先迁、逻辑后搬”的顺序 diff --git a/server-node/src/modules/story/storyActionService.ts b/server-node/src/modules/story/storyActionService.ts index 74a6ee76..9dad0989 100644 --- a/server-node/src/modules/story/storyActionService.ts +++ b/server-node/src/modules/story/storyActionService.ts @@ -6,11 +6,9 @@ import type { RuntimeStoryPatch, } from '../../../../packages/shared/src/contracts/story.js'; import { conflict, invalidRequest } from '../../errors.js'; -import type { - RuntimeRepositoryPort, - SavedSnapshot, -} from '../../repositories/runtimeRepository.js'; +import type { SavedSnapshot } from '../../repositories/runtimeRepository.js'; import type { UpstreamLlmClient } from '../../services/llmClient.js'; +import type { RuntimeStoryCapability } from '../../services/runtimeCapabilities.js'; import { buildStrictNpcChatDialoguePrompt, NPC_CHAT_DIALOGUE_STRICT_SYSTEM_PROMPT, @@ -80,10 +78,7 @@ type GeneratedStoryPayload = { savedCurrentStory: JsonRecord; }; -type RuntimeSnapshotRepositoryPort = Pick< - RuntimeRepositoryPort, - 'getSnapshot' | 'putSnapshot' ->; +type RuntimeSnapshotRepositoryPort = RuntimeStoryCapability; const CONTINUE_ADVENTURE_OPTION = { functionId: 'story_continue_adventure', diff --git a/server-node/src/modules/story/storySpacetimeBridge.ts b/server-node/src/modules/story/storySpacetimeBridge.ts index 0af14fb9..c8452bf1 100644 --- a/server-node/src/modules/story/storySpacetimeBridge.ts +++ b/server-node/src/modules/story/storySpacetimeBridge.ts @@ -6,18 +6,15 @@ import type { } from '../../../../src/spacetime/generated/types.ts'; import { conflict, unauthorized } from '../../errors.js'; import type { - RuntimeRepositoryPort, SavedSnapshot, } from '../../repositories/runtimeRepository.js'; import type { AppConfig } from '../../config.js'; import { hydrateSavedSnapshot } from '../runtime/runtimeSnapshotHydration.js'; +import type { RuntimeStoryCapability } from '../../services/runtimeCapabilities.js'; const STORY_STDB_AUTH_HEADER = 'x-genarrative-runtime-story-auth'; -type RuntimeSnapshotRepositoryPort = Pick< - RuntimeRepositoryPort, - 'getSnapshot' | 'putSnapshot' ->; +type RuntimeSnapshotRepositoryPort = RuntimeStoryCapability; type StoryStdbBridgeContext = { config: AppConfig; @@ -160,7 +157,7 @@ export async function authenticateRuntimeStoryViaSpacetime( export function createRuntimeStorySnapshotRepository(params: { request: Request; - runtimeRepository: RuntimeRepositoryPort; + runtimeRepository: RuntimeStoryCapability; config: AppConfig; }): RuntimeSnapshotRepositoryPort { if (params.request.runtimeStoryAuthMode !== 'spacetime') { diff --git a/server-node/src/services/customWorldAgentPhase2.test.ts b/server-node/src/services/customWorldAgentPhase2.test.ts index a083ceb4..62f70d10 100644 --- a/server-node/src/services/customWorldAgentPhase2.test.ts +++ b/server-node/src/services/customWorldAgentPhase2.test.ts @@ -2,7 +2,6 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js'; -import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js'; import { buildPendingClarifications, evaluateCreatorIntentReadiness, @@ -15,8 +14,13 @@ import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js' import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js'; import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js'; import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js'; +import type { + CustomWorldSessionCapability, + CustomWorldWorkSummaryCapability, +} from './runtimeCapabilities.js'; -function createRuntimeRepositoryStub(): RuntimeRepositoryPort { +function createRuntimeRepositoryStub(): CustomWorldSessionCapability & + CustomWorldWorkSummaryCapability { const sessionsByUser = new Map< string, Map @@ -35,51 +39,9 @@ function createRuntimeRepositoryStub(): RuntimeRepositoryPort { }; return { - async getSnapshot(_userId) { - return null; - }, - async putSnapshot(_userId, _payload) { - throw new Error('not implemented'); - }, - async deleteSnapshot(_userId) { - return undefined; - }, - async getSettings() { - return { - musicVolume: 0.42, - platformTheme: 'light', - }; - }, - async putSettings(_userId, settings) { - return settings; - }, async listCustomWorldProfiles(userId) { return [...(profilesByUser.get(userId) ?? [])]; }, - async upsertCustomWorldProfile(userId, profileId, profile) { - const current = [...(profilesByUser.get(userId) ?? [])].filter( - (item) => String(item.id ?? '') !== profileId, - ); - current.unshift({ - ...profile, - id: profileId, - }); - profilesByUser.set(userId, current); - return current; - }, - async deleteCustomWorldProfile(userId, profileId) { - const current = [...(profilesByUser.get(userId) ?? [])].filter( - (item) => String(item.id ?? '') !== profileId, - ); - profilesByUser.set(userId, current); - return current; - }, - async listProfileSaveArchives() { - return []; - }, - async resumeProfileSaveArchive() { - return null; - }, async listCustomWorldSessions(userId) { return [...getSessionBucket(userId).values()]; }, diff --git a/server-node/src/services/customWorldAgentPhase3.test.ts b/server-node/src/services/customWorldAgentPhase3.test.ts index 7e350f12..601b3615 100644 --- a/server-node/src/services/customWorldAgentPhase3.test.ts +++ b/server-node/src/services/customWorldAgentPhase3.test.ts @@ -2,13 +2,17 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js'; -import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js'; import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js'; import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js'; import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js'; import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js'; +import type { + CustomWorldSessionCapability, + CustomWorldWorkSummaryCapability, +} from './runtimeCapabilities.js'; -function createRuntimeRepositoryStub(): RuntimeRepositoryPort { +function createRuntimeRepositoryStub(): CustomWorldSessionCapability & + CustomWorldWorkSummaryCapability { const sessionsByUser = new Map< string, Map @@ -27,51 +31,9 @@ function createRuntimeRepositoryStub(): RuntimeRepositoryPort { }; return { - async getSnapshot(_userId) { - return null; - }, - async putSnapshot(_userId, _payload) { - throw new Error('not implemented'); - }, - async deleteSnapshot(_userId) { - return undefined; - }, - async getSettings() { - return { - musicVolume: 0.42, - platformTheme: 'light', - }; - }, - async putSettings(_userId, settings) { - return settings; - }, async listCustomWorldProfiles(userId) { return [...(profilesByUser.get(userId) ?? [])]; }, - async upsertCustomWorldProfile(userId, profileId, profile) { - const current = [...(profilesByUser.get(userId) ?? [])].filter( - (item) => String(item.id ?? '') !== profileId, - ); - current.unshift({ - ...profile, - id: profileId, - }); - profilesByUser.set(userId, current); - return current; - }, - async deleteCustomWorldProfile(userId, profileId) { - const current = [...(profilesByUser.get(userId) ?? [])].filter( - (item) => String(item.id ?? '') !== profileId, - ); - profilesByUser.set(userId, current); - return current; - }, - async listProfileSaveArchives() { - return []; - }, - async resumeProfileSaveArchive() { - return null; - }, async listCustomWorldSessions(userId) { return [...getSessionBucket(userId).values()]; }, diff --git a/server-node/src/services/customWorldAgentPhase4.test.ts b/server-node/src/services/customWorldAgentPhase4.test.ts index b5e347f0..0e3af305 100644 --- a/server-node/src/services/customWorldAgentPhase4.test.ts +++ b/server-node/src/services/customWorldAgentPhase4.test.ts @@ -2,14 +2,18 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js'; -import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js'; import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js'; import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js'; import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js'; import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js'; import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js'; +import type { + CustomWorldSessionCapability, + CustomWorldWorkSummaryCapability, +} from './runtimeCapabilities.js'; -function createRuntimeRepositoryStub(): RuntimeRepositoryPort { +function createRuntimeRepositoryStub(): CustomWorldSessionCapability & + CustomWorldWorkSummaryCapability { const sessionsByUser = new Map< string, Map @@ -28,51 +32,9 @@ function createRuntimeRepositoryStub(): RuntimeRepositoryPort { }; return { - async getSnapshot(_userId) { - return null; - }, - async putSnapshot(_userId, _payload) { - throw new Error('not implemented'); - }, - async deleteSnapshot(_userId) { - return undefined; - }, - async getSettings() { - return { - musicVolume: 0.42, - platformTheme: 'light', - }; - }, - async putSettings(_userId, settings) { - return settings; - }, async listCustomWorldProfiles(userId) { return [...(profilesByUser.get(userId) ?? [])]; }, - async upsertCustomWorldProfile(userId, profileId, profile) { - const current = [...(profilesByUser.get(userId) ?? [])].filter( - (item) => String(item.id ?? '') !== profileId, - ); - current.unshift({ - ...profile, - id: profileId, - }); - profilesByUser.set(userId, current); - return current; - }, - async deleteCustomWorldProfile(userId, profileId) { - const current = [...(profilesByUser.get(userId) ?? [])].filter( - (item) => String(item.id ?? '') !== profileId, - ); - profilesByUser.set(userId, current); - return current; - }, - async listProfileSaveArchives() { - return []; - }, - async resumeProfileSaveArchive() { - return null; - }, async listCustomWorldSessions(userId) { return [...getSessionBucket(userId).values()]; }, diff --git a/server-node/src/services/customWorldAgentPhase5.test.ts b/server-node/src/services/customWorldAgentPhase5.test.ts index 5e3559f6..ebc6a86d 100644 --- a/server-node/src/services/customWorldAgentPhase5.test.ts +++ b/server-node/src/services/customWorldAgentPhase5.test.ts @@ -2,18 +2,17 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js'; -import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js'; import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js'; import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js'; import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js'; import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js'; +import type { CustomWorldSessionCapability } from './runtimeCapabilities.js'; -function createRuntimeRepositoryStub(): RuntimeRepositoryPort { +function createRuntimeRepositoryStub(): CustomWorldSessionCapability { const sessionsByUser = new Map< string, Map >(); - const profilesByUser = new Map[]>(); const getSessionBucket = (userId: string) => { const existing = sessionsByUser.get(userId); @@ -27,51 +26,6 @@ function createRuntimeRepositoryStub(): RuntimeRepositoryPort { }; return { - async getSnapshot() { - return null; - }, - async putSnapshot(_userId, payload) { - return payload; - }, - async deleteSnapshot() { - return undefined; - }, - async getSettings() { - return { - musicVolume: 0.42, - platformTheme: 'light', - }; - }, - async putSettings(_userId, settings) { - return settings; - }, - async listCustomWorldProfiles(userId) { - return [...(profilesByUser.get(userId) ?? [])]; - }, - async upsertCustomWorldProfile(userId, profileId, profile) { - const current = [...(profilesByUser.get(userId) ?? [])].filter( - (item) => String(item.id ?? '') !== profileId, - ); - current.unshift({ - ...profile, - id: profileId, - }); - profilesByUser.set(userId, current); - return current; - }, - async deleteCustomWorldProfile(userId, profileId) { - const current = [...(profilesByUser.get(userId) ?? [])].filter( - (item) => String(item.id ?? '') !== profileId, - ); - profilesByUser.set(userId, current); - return current; - }, - async listProfileSaveArchives() { - return []; - }, - async resumeProfileSaveArchive() { - return null; - }, async listCustomWorldSessions(userId) { return [...getSessionBucket(userId).values()]; }, diff --git a/server-node/src/services/customWorldAgentSessionStore.ts b/server-node/src/services/customWorldAgentSessionStore.ts index 0310a321..663548d6 100644 --- a/server-node/src/services/customWorldAgentSessionStore.ts +++ b/server-node/src/services/customWorldAgentSessionStore.ts @@ -13,7 +13,6 @@ import type { EightAnchorContent, } from '../../../packages/shared/src/contracts/customWorldAgent.js'; import type { CustomWorldSessionRecord as LegacyCustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js'; -import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js'; import { buildPendingClarifications, evaluateCreatorIntentReadiness, @@ -33,6 +32,7 @@ import { estimateProgressPercentFromAnchorContent, normalizeEightAnchorContent, } from './eightAnchorCompatibilityService.js'; +import type { CustomWorldSessionCapability } from './runtimeCapabilities.js'; export const CUSTOM_WORLD_AGENT_SESSION_ID_PREFIX = 'custom-world-agent-session-'; @@ -565,7 +565,7 @@ function toSnapshot( } export class CustomWorldAgentSessionStore { - constructor(private readonly runtimeRepository: RuntimeRepositoryPort) {} + constructor(private readonly runtimeRepository: CustomWorldSessionCapability) {} private async persist(record: CustomWorldAgentSessionRecord) { await this.runtimeRepository.upsertCustomWorldSession( diff --git a/server-node/src/services/customWorldSessionStore.ts b/server-node/src/services/customWorldSessionStore.ts index 6fb99c6a..a856605a 100644 --- a/server-node/src/services/customWorldSessionStore.ts +++ b/server-node/src/services/customWorldSessionStore.ts @@ -7,7 +7,7 @@ import type { CustomWorldSessionRecord, CustomWorldSessionStatus, } from '../../../packages/shared/src/contracts/runtime.js'; -import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js'; +import type { CustomWorldSessionCapability } from './runtimeCapabilities.js'; export type CustomWorldSession = { sessionId: string; @@ -111,7 +111,7 @@ function buildClarificationQuestions( export class CustomWorldSessionStore { constructor( - private readonly runtimeRepository: RuntimeRepositoryPort, + private readonly runtimeRepository: CustomWorldSessionCapability, ) {} async create( diff --git a/server-node/src/services/customWorldWorkSummaryService.ts b/server-node/src/services/customWorldWorkSummaryService.ts index 4af0db2e..ec8b62d8 100644 --- a/server-node/src/services/customWorldWorkSummaryService.ts +++ b/server-node/src/services/customWorldWorkSummaryService.ts @@ -6,7 +6,6 @@ import type { CustomWorldLibraryEntry, CustomWorldProfileRecord, } from '../../../packages/shared/src/contracts/runtime.js'; -import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js'; import { resolveCustomWorldCoverPresentation } from '../repositories/customWorldLibraryMetadata.js'; import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js'; import { @@ -26,6 +25,7 @@ import { buildDraftSummaryFromEightAnchorContent, buildDraftTitleFromEightAnchorContent, } from './eightAnchorCompatibilityService.js'; +import type { CustomWorldWorkSummaryCapability } from './runtimeCapabilities.js'; function toText(value: unknown) { return typeof value === 'string' ? value.trim() : ''; @@ -171,7 +171,7 @@ function isLibraryEntry( export async function listCustomWorldWorkSummaries( userId: string, dependencies: { - runtimeRepository: RuntimeRepositoryPort; + runtimeRepository: CustomWorldWorkSummaryCapability; customWorldAgentSessions: CustomWorldAgentSessionStore; }, ) { diff --git a/server-node/src/services/runtimeCapabilities.ts b/server-node/src/services/runtimeCapabilities.ts new file mode 100644 index 00000000..6adf64b3 --- /dev/null +++ b/server-node/src/services/runtimeCapabilities.ts @@ -0,0 +1,53 @@ +import type { CustomWorldProfileRecord, CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js'; +import type { + RuntimeRepositoryPort, + SavedSnapshot, +} from '../repositories/runtimeRepository.js'; + +export type RuntimeSnapshotCapability = Pick< + RuntimeRepositoryPort, + 'getSnapshot' | 'putSnapshot' +>; + +export type CustomWorldProfileCapability = Pick< + RuntimeRepositoryPort, + 'listCustomWorldProfiles' +>; + +export type CustomWorldSessionCapability = { + listCustomWorldSessions(userId: string): Promise; + getCustomWorldSession( + userId: string, + sessionId: string, + ): Promise; + upsertCustomWorldSession( + userId: string, + sessionId: string, + session: CustomWorldSessionRecord, + ): Promise; +}; + +export type RuntimeStoryCapability = RuntimeSnapshotCapability; + +export type CustomWorldWorkSummaryCapability = CustomWorldProfileCapability; + +export function createRuntimeSnapshotCapability( + runtimeRepository: RuntimeRepositoryPort, +): RuntimeSnapshotCapability { + return runtimeRepository; +} + +export function createCustomWorldSessionCapability( + runtimeRepository: RuntimeRepositoryPort, +): CustomWorldSessionCapability { + return runtimeRepository; +} + +export function createCustomWorldProfileCapability( + runtimeRepository: RuntimeRepositoryPort, +): CustomWorldProfileCapability { + return runtimeRepository; +} + +export type RuntimeStorySnapshot = SavedSnapshot; +export type RuntimeCustomWorldProfile = CustomWorldProfileRecord;