# Conflicts: # docs/technical/README.md # docs/technical/RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md # docs/technical/SPACETIMEDB_TABLE_CATALOG.md # scripts/generate-spacetime-bindings.mjs # server-rs/crates/api-server/src/app.rs # server-rs/crates/api-server/src/assets.rs # server-rs/crates/api-server/src/big_fish.rs # server-rs/crates/api-server/src/custom_world_ai.rs # server-rs/crates/api-server/src/llm.rs # server-rs/crates/api-server/src/main.rs # server-rs/crates/api-server/src/puzzle.rs # server-rs/crates/api-server/src/runtime_profile.rs # server-rs/crates/api-server/src/runtime_story/compat/ai.rs # server-rs/crates/api-server/src/runtime_story/compat/npc_actions.rs # server-rs/crates/api-server/src/runtime_story/compat/presentation.rs # server-rs/crates/api-server/src/runtime_story/compat/tests.rs # server-rs/crates/api-server/src/state.rs # server-rs/crates/module-auth/src/lib.rs # server-rs/crates/module-big-fish/src/lib.rs # server-rs/crates/module-custom-world/src/lib.rs # server-rs/crates/module-puzzle/src/lib.rs # server-rs/crates/module-runtime/src/lib.rs # server-rs/crates/spacetime-client/src/big_fish.rs # server-rs/crates/spacetime-client/src/lib.rs # server-rs/crates/spacetime-client/src/mapper.rs # server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_redeem_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_redeem_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_next_level_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/append_ai_text_chunk_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/attach_ai_result_reference_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/authorize_database_migration_operator_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/big_fish_runtime_run_type.rs # server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/cancel_ai_task_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/clear_platform_browse_history_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/compile_big_fish_draft_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/compile_custom_world_published_profile_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_agent_draft_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/complete_ai_stage_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/complete_ai_task_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/consume_profile_wallet_points_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/continue_story_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_big_fish_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_custom_world_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_profile_recharge_order_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_big_fish_work_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_profile_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_puzzle_work_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/delete_runtime_snapshot_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/drag_puzzle_piece_or_group_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/execute_custom_world_agent_action_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/export_auth_store_snapshot_from_tables_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/export_database_migration_to_file_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/fail_ai_task_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/finalize_big_fish_agent_message_turn_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/finalize_custom_world_agent_message_turn_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/finalize_puzzle_agent_message_turn_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/generate_big_fish_asset_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_auth_store_snapshot_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_battle_state_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_chapter_progression_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_card_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_operation_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_by_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_library_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_player_progression_or_default_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_profile_dashboard_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_profile_play_stats_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_center_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_profile_referral_invite_center_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_agent_session_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_gallery_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_run_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_work_detail_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_runtime_inventory_state_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_runtime_setting_or_default_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_runtime_snapshot_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/get_story_session_state_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/import_auth_store_snapshot_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_file_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_file_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_asset_history_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_big_fish_works_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_gallery_entries_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_profiles_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_works_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_platform_browse_history_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_profile_save_archives_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_profile_wallet_ledger_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_gallery_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_works_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/mod.rs # server-rs/crates/spacetime-client/src/module_bindings/publish_big_fish_game_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_world_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_work_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_play_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_referral_invite_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_reward_code_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/refund_profile_wallet_points_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_battle_interaction_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/resume_profile_save_archive_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/revoke_database_migration_operator_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_generated_images_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/select_puzzle_cover_image_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_run_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_message_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/submit_custom_world_agent_message_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_agent_message_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_leaderboard_entry_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_pieces_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_work_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_auth_store_snapshot_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_agent_operation_progress_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_platform_browse_history_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_setting_and_return_procedure.rs # server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_snapshot_and_return_procedure.rs # server-rs/crates/spacetime-module/src/auth/procedures.rs # server-rs/crates/spacetime-module/src/custom_world/mod.rs # server-rs/crates/spacetime-module/src/lib.rs # server-rs/crates/spacetime-module/src/migration.rs # server-rs/crates/spacetime-module/src/puzzle.rs # server-rs/crates/spacetime-module/src/runtime/profile.rs # src/components/platform-entry/PlatformEntryFlowShellImpl.tsx # src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx # src/services/aiService.ts # src/services/puzzle-runtime/puzzleRuntimeClient.ts
277 lines
7.6 KiB
JavaScript
277 lines
7.6 KiB
JavaScript
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
||
import { basename, join, relative } from 'node:path';
|
||
|
||
const repoRoot = process.cwd();
|
||
const cratesDir = join(repoRoot, 'server-rs', 'crates');
|
||
const spacetimeModuleSrcDir = join(cratesDir, 'spacetime-module', 'src');
|
||
const spacetimeMigrationPath = join(spacetimeModuleSrcDir, 'migration.rs');
|
||
const spacetimeTableCatalogPath = join(
|
||
repoRoot,
|
||
'docs',
|
||
'technical',
|
||
'SPACETIMEDB_TABLE_CATALOG.md',
|
||
);
|
||
const migrationExcludedTables = new Set([
|
||
'database_migration_operator',
|
||
'database_migration_import_chunk',
|
||
]);
|
||
const requiredModuleFiles = [
|
||
'domain.rs',
|
||
'commands.rs',
|
||
'application.rs',
|
||
'events.rs',
|
||
'errors.rs',
|
||
];
|
||
const requiredLibModules = ['domain', 'commands', 'application', 'events', 'errors'];
|
||
const forbiddenModuleWidePatterns = [
|
||
{
|
||
pattern: /\baxum::/u,
|
||
message: 'module-* 不允许直接依赖 Axum',
|
||
},
|
||
{
|
||
pattern: /\bspacetimedb::(?:table|reducer|procedure|ReducerContext|ProcedureContext|Table)\b/u,
|
||
message: 'module-* 不允许声明 SpacetimeDB table/reducer/procedure 或直接操作表',
|
||
},
|
||
];
|
||
const forbiddenCorePatterns = [
|
||
{
|
||
pattern: /\breqwest::/u,
|
||
message: 'DDD 核心文件不允许直接依赖 reqwest',
|
||
},
|
||
{
|
||
pattern: /\bplatform_oss::/u,
|
||
message: 'DDD 核心文件不允许直接依赖 OSS adapter',
|
||
},
|
||
{
|
||
pattern: /\bplatform_llm::/u,
|
||
message: 'DDD 核心文件不允许直接依赖 LLM adapter',
|
||
},
|
||
{
|
||
pattern: /\bspacetime_client::/u,
|
||
message: 'DDD 核心文件不允许直接依赖 SpacetimeDB client adapter',
|
||
},
|
||
{
|
||
pattern: /\bstd::fs\b/u,
|
||
message: 'DDD 核心文件不允许直接访问文件系统',
|
||
},
|
||
{
|
||
pattern: /\btokio::/u,
|
||
message: 'DDD 核心文件不允许绑定异步运行时',
|
||
},
|
||
];
|
||
|
||
function normalizePath(path) {
|
||
return path.replace(/\\/gu, '/');
|
||
}
|
||
|
||
function readText(path) {
|
||
return readFileSync(path, 'utf8');
|
||
}
|
||
|
||
function listRustFiles(dir) {
|
||
const files = [];
|
||
|
||
function walk(currentDir) {
|
||
for (const name of readdirSync(currentDir)) {
|
||
const fullPath = join(currentDir, name);
|
||
const stat = statSync(fullPath);
|
||
|
||
if (stat.isDirectory()) {
|
||
walk(fullPath);
|
||
continue;
|
||
}
|
||
|
||
if (name.endsWith('.rs')) {
|
||
files.push(fullPath);
|
||
}
|
||
}
|
||
}
|
||
|
||
walk(dir);
|
||
return files;
|
||
}
|
||
|
||
function collectSpacetimeTables() {
|
||
if (!existsSync(spacetimeModuleSrcDir)) {
|
||
return [];
|
||
}
|
||
|
||
const tableByAccessor = new Map();
|
||
const tablePattern =
|
||
/#\[spacetimedb::table\(([\s\S]*?)\)\]\s*(?:#\[[^\]]+\]\s*)*(?:pub\s+)?struct\s+([A-Za-z0-9_]+)/gu;
|
||
|
||
for (const rustFile of listRustFiles(spacetimeModuleSrcDir)) {
|
||
const text = readText(rustFile);
|
||
let match;
|
||
while ((match = tablePattern.exec(text)) !== null) {
|
||
const accessorMatch = /accessor\s*=\s*([A-Za-z0-9_]+)/u.exec(match[1]);
|
||
if (!accessorMatch) {
|
||
continue;
|
||
}
|
||
|
||
const accessor = accessorMatch[1];
|
||
const relativePath = normalizePath(relative(repoRoot, rustFile));
|
||
const previous = tableByAccessor.get(accessor);
|
||
if (previous) {
|
||
failures.push(
|
||
`SpacetimeDB table accessor ${accessor} 重复定义于 ${previous.path} 与 ${relativePath}`,
|
||
);
|
||
continue;
|
||
}
|
||
|
||
tableByAccessor.set(accessor, {
|
||
accessor,
|
||
structName: match[2],
|
||
path: relativePath,
|
||
});
|
||
}
|
||
}
|
||
|
||
return [...tableByAccessor.values()].sort((left, right) =>
|
||
left.accessor.localeCompare(right.accessor),
|
||
);
|
||
}
|
||
|
||
function collectMigrationTables() {
|
||
if (!existsSync(spacetimeMigrationPath)) {
|
||
return new Set();
|
||
}
|
||
|
||
const migrationText = readText(spacetimeMigrationPath);
|
||
const macroMatch =
|
||
/macro_rules!\s+migration_tables\s*\{[\s\S]*?\$macro_name!\s*\{([\s\S]*?)\n\s*\}\s*\n\s*\};\s*\n\}/u.exec(
|
||
migrationText,
|
||
);
|
||
if (!macroMatch) {
|
||
failures.push('migration.rs 无法解析 migration_tables! 白名单');
|
||
return new Set();
|
||
}
|
||
|
||
return new Set(
|
||
[...macroMatch[1].matchAll(/\b([a-z][a-z0-9_]*)\b/gu)]
|
||
.map((match) => match[1])
|
||
.filter((name) => !['arg'].includes(name)),
|
||
);
|
||
}
|
||
|
||
function collectCatalogTables() {
|
||
if (!existsSync(spacetimeTableCatalogPath)) {
|
||
return new Set();
|
||
}
|
||
|
||
const catalogText = readText(spacetimeTableCatalogPath);
|
||
return new Set(
|
||
[...catalogText.matchAll(/^### `([^`]+)`/gmu)].map((match) => match[1]),
|
||
);
|
||
}
|
||
|
||
function checkSpacetimeTableCatalogAndMigration() {
|
||
const tables = collectSpacetimeTables();
|
||
const tableNames = new Set(tables.map((table) => table.accessor));
|
||
const migrationTables = collectMigrationTables();
|
||
const catalogTables = collectCatalogTables();
|
||
|
||
for (const table of tables) {
|
||
if (!migrationExcludedTables.has(table.accessor) && !migrationTables.has(table.accessor)) {
|
||
failures.push(
|
||
`${table.path}: SpacetimeDB 表 ${table.accessor} 缺少 migration.rs 白名单`,
|
||
);
|
||
}
|
||
|
||
if (!catalogTables.has(table.accessor)) {
|
||
failures.push(
|
||
`${table.path}: SpacetimeDB 表 ${table.accessor} 缺少 SPACETIMEDB_TABLE_CATALOG.md 目录项`,
|
||
);
|
||
}
|
||
}
|
||
|
||
for (const tableName of migrationTables) {
|
||
if (!tableNames.has(tableName)) {
|
||
failures.push(`migration.rs 白名单包含不存在的 SpacetimeDB 表 ${tableName}`);
|
||
}
|
||
}
|
||
|
||
for (const tableName of catalogTables) {
|
||
if (!tableNames.has(tableName)) {
|
||
failures.push(`SPACETIMEDB_TABLE_CATALOG.md 包含不存在的 SpacetimeDB 表 ${tableName}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
function collectModuleCrates() {
|
||
return readdirSync(cratesDir)
|
||
.filter((name) => name.startsWith('module-'))
|
||
.filter((name) => existsSync(join(cratesDir, name, 'Cargo.toml')))
|
||
.sort();
|
||
}
|
||
|
||
const failures = [];
|
||
const moduleCrates = collectModuleCrates();
|
||
|
||
for (const crateName of moduleCrates) {
|
||
const crateDir = join(cratesDir, crateName);
|
||
const srcDir = join(crateDir, 'src');
|
||
const libPath = join(srcDir, 'lib.rs');
|
||
|
||
for (const fileName of requiredModuleFiles) {
|
||
const filePath = join(srcDir, fileName);
|
||
if (!existsSync(filePath)) {
|
||
failures.push(`${crateName} 缺少 DDD 落位文件 src/${fileName}`);
|
||
}
|
||
}
|
||
|
||
if (existsSync(libPath)) {
|
||
const libText = readText(libPath);
|
||
for (const moduleName of requiredLibModules) {
|
||
const moduleDeclaration = new RegExp(
|
||
`(?:^|\\n)\\s*(?:pub(?:\\([^)]*\\))?\\s+)?mod\\s+${moduleName}\\s*;`,
|
||
'u',
|
||
);
|
||
if (!moduleDeclaration.test(libText)) {
|
||
failures.push(`${crateName} 的 lib.rs 缺少模块声明 mod ${moduleName};`);
|
||
}
|
||
}
|
||
}
|
||
|
||
for (const rustFile of listRustFiles(srcDir)) {
|
||
const relativePath = normalizePath(relative(repoRoot, rustFile));
|
||
const fileName = basename(rustFile);
|
||
const text = readText(rustFile);
|
||
|
||
if (fileName === 'mapper.rs') {
|
||
failures.push(`${relativePath} 不能位于 module-*,mapper 只能放在 adapter crate`);
|
||
}
|
||
|
||
for (const rule of forbiddenModuleWidePatterns) {
|
||
if (rule.pattern.test(text)) {
|
||
failures.push(`${relativePath}: ${rule.message}`);
|
||
}
|
||
}
|
||
|
||
const isDddCoreFile = requiredModuleFiles.some((name) =>
|
||
relativePath.endsWith(`/src/${name}`),
|
||
);
|
||
if (!isDddCoreFile) {
|
||
continue;
|
||
}
|
||
|
||
for (const rule of forbiddenCorePatterns) {
|
||
if (rule.pattern.test(text)) {
|
||
failures.push(`${relativePath}: ${rule.message}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
checkSpacetimeTableCatalogAndMigration();
|
||
|
||
if (failures.length > 0) {
|
||
console.error('server-rs DDD boundary check failed:');
|
||
for (const failure of failures) {
|
||
console.error(`- ${failure}`);
|
||
}
|
||
process.exit(1);
|
||
}
|
||
|
||
console.log(`server-rs DDD boundary check passed for ${moduleCrates.length} module crate(s).`);
|