dump.js 8.45 KB
import { ScriptLine } from './schemas/script-line.js'
import { Bundle } from './schemas/bundle.js'
import { Drama, Page } from './schemas/drama.js'
import { ApiError } from './schemas/error.js'
import { LoginRequest, LoginResponse } from './schemas/auth.js'
import { Account } from './schemas/account.js'
import { UnlockReq, UnlockRes, TopupCheckoutReq, LedgerEntry } from './schemas/credits.js'
import { TelemetryEvent } from './schemas/telemetry.js'
import { ShareMeta } from './schemas/share.js'
import {
  VisualGameAdminAnalyticsOverviewResponse,
  VisualGameAdminAnalyticsDrilldownResponse,
  VisualGameAdminPlayerInteractionsResponse,
  VisualGameAdminContentAnalyticsResponse,
  VisualGameAdminGa4BehaviorResponse,
} from './schemas/visual-game-admin-analytics.js'
import {
  WaitlistActionResponse,
  WaitlistActorType,
  WaitlistCreateManualEntryRequest,
  WaitlistEntry,
  WaitlistEntryDetailResponse,
  WaitlistEntryListResponse,
  WaitlistEvent,
  WaitlistEventType,
  WaitlistExportResponse,
  WaitlistInviteDelivery,
  WaitlistInviteDeliveryStatus,
  WaitlistInviteCode,
  WaitlistInviteRequest,
  WaitlistInviteValidateResponse,
  WaitlistSendRegisterCodeResponse,
  WaitlistJoinRequest,
  WaitlistJoinResponse,
  WaitlistNoteRequest,
  WaitlistPublicSource,
  WaitlistRejectRequest,
  WaitlistSource,
  WaitlistStatus,
} from './schemas/waitlist.js'
import {
  VisualGameAgentCall,
  VisualGameAgentCallList,
  VisualGameAgentCallModelSummary,
  VisualGameAgentCallRoleSummary,
  VisualGameAgentCallSummary,
  VisualGameAssetIndexItem,
  VisualGameAssetManifest,
  VisualGameTtsVoice,
  VisualGameTtsVoiceList,
  VisualGameCreateRunResponse,
  VisualGameEditAuditLog,
  VisualGameEditAuditLogList,
  VisualGameEndingText,
  VisualGamePack,
  VisualGamePackAssetUploadResponse,
  VisualGamePackCompileResponse,
  VisualGameContentTranslationLocaleStatus,
  VisualGameContentTranslationStatus,
  VisualGameContentTranslationResult,
  VisualGameContentTranslationInventoryItem,
  VisualGameContentTranslationInventory,
  VisualGameContentTranslationProgress,
  VisualGamePackDeleteResponse,
  VisualGamePackList,
  VisualGamePreviewTokenResponse,
  VisualGameNotification,
  VisualGameNotificationList,
  VisualGameRunDetail,
  VisualGameStoryVideoLog,
  VisualGameScenarioVersion,
  VisualGameScenarioListItem,
  VisualGameScenarioPage,
  VisualGameTurnEvent,
} from './schemas/visual-game.js'
import {
  VisualGameAdminUserActionResponse,
  VisualGameAdminUserCreditsAdjustRequest,
  VisualGameAdminUserDetailResponse,
  VisualGameAdminUserListResponse,
  VisualGameAdminUserProvisionRequest,
  VisualGameAdminUserProfileUpdateRequest,
  VisualGameAdminUserSubscriptionUpdateRequest,
  VisualGameAdminUserSummary,
} from './schemas/visual-game-admin-users.js'
import {
  VisualGameKpAgentOutput,
  VisualGameNpcActionKpOutput,
  VisualGameNpcActorOutput,
} from './schemas/visual-game-agent.js'
import {
  VisualGameScenarioReleaseApplyResponse,
  VisualGameScenarioReleaseBundle,
  VisualGameScenarioReleaseRequest,
  VisualGameScenarioReleaseRecord,
  VisualGameScenarioReleaseTargetState,
} from './schemas/visual-game-scenario-release.js'

/**
 * 把单个 Valibot schema 递归序列化为一个稳定、确定性的 JSON 描述。
 *
 * 这是 schema gate 的发动机:同一份 schema 必须逐字节产出同样的描述(键有序、
 * 无时间戳、无随机顺序),否则 gate 会因伪漂移误报。本函数只覆盖本包实际使用
 * 到的 Valibot schema 类型(object / array / record / optional / picklist /
 * 基础标量),遇到未覆盖类型显式抛错——宁可 gate 失败,也不静默产出歧义描述。
 *
 * @param {any} schema Valibot schema 对象
 * @returns {object} 该 schema 的稳定 JSON 描述
 */
export function describeSchema(schema) {
  switch (schema.type) {
    case 'object':
    case 'strict_object': {
      /** @type {Record<string, object>} */
      const properties = {}
      for (const key of Object.keys(schema.entries).sort()) {
        properties[key] = describeSchema(schema.entries[key])
      }
      return { type: schema.type, properties }
    }
    case 'array':
      return { type: 'array', item: describeSchema(schema.item) }
    case 'record':
      return {
        type: 'record',
        key: describeSchema(schema.key),
        value: describeSchema(schema.value),
      }
    case 'optional':
      return { type: 'optional', wrapped: describeSchema(schema.wrapped) }
    case 'nullable':
      return { type: 'nullable', wrapped: describeSchema(schema.wrapped) }
    case 'picklist':
      return { type: 'picklist', options: [...schema.options].sort() }
    case 'literal':
      return { type: 'literal', value: schema.literal }
    case 'union':
      return { type: 'union', options: schema.options.map(describeSchema) }
    case 'intersect':
      return { type: 'intersect', options: schema.options.map(describeSchema) }
    case 'custom':
      return { type: 'custom' }
    case 'string':
    case 'number':
    case 'boolean':
    case 'unknown':
      return { type: schema.type }
    default:
      throw new Error(`describeSchema: 未支持的 Valibot schema 类型 "${schema.type}"`)
  }
}

/**
 * 导出全部公共 schema 的稳定 JSON 描述供 CI 比对。
 *
 * 返回对象键有序(按 schema 名排序),值为各 schema 的确定性描述;连续两次调用
 * 输出逐字节相同。`Page` 是高阶工厂,故快照里以一个代表性实例化(`Page(Drama)`)
 * 锁定其信封结构。
 *
 * @returns {Record<string, object>} `{ [schemaName]: jsonRepresentation }`,键有序
 */
export function dumpSchemas() {
  /** @type {Record<string, any>} */
  const registry = {
    Account,
    ApiError,
    Bundle,
    Drama,
    LedgerEntry,
    LoginRequest,
    LoginResponse,
    PageOfDrama: Page(Drama),
    ScriptLine,
    TopupCheckoutReq,
    UnlockReq,
    UnlockRes,
    ShareMeta,
    TelemetryEvent,
    VisualGameAgentCall,
    VisualGameAgentCallList,
    VisualGameAgentCallModelSummary,
    VisualGameAgentCallRoleSummary,
    VisualGameAgentCallSummary,
    VisualGameKpAgentOutput,
    VisualGameNpcActionKpOutput,
    VisualGameNpcActorOutput,
    VisualGameAssetIndexItem,
    VisualGameAssetManifest,
    VisualGameTtsVoice,
    VisualGameTtsVoiceList,
    VisualGameCreateRunResponse,
    VisualGameEditAuditLog,
    VisualGameEditAuditLogList,
    VisualGameEndingText,
    VisualGamePack,
    VisualGamePackAssetUploadResponse,
    VisualGamePackCompileResponse,
    VisualGameContentTranslationLocaleStatus,
    VisualGameContentTranslationStatus,
    VisualGameContentTranslationResult,
    VisualGameContentTranslationInventoryItem,
    VisualGameContentTranslationInventory,
    VisualGameContentTranslationProgress,
    VisualGamePackDeleteResponse,
    VisualGamePackList,
    VisualGamePreviewTokenResponse,
    VisualGameNotification,
    VisualGameNotificationList,
    VisualGameRunDetail,
    VisualGameStoryVideoLog,
    VisualGameScenarioVersion,
    VisualGameScenarioListItem,
    VisualGameScenarioPage,
    VisualGameScenarioReleaseApplyResponse,
    VisualGameScenarioReleaseBundle,
    VisualGameScenarioReleaseRequest,
    VisualGameScenarioReleaseRecord,
    VisualGameScenarioReleaseTargetState,
    VisualGameTurnEvent,
    VisualGameAdminUserDetailResponse,
    VisualGameAdminUserActionResponse,
    VisualGameAdminUserCreditsAdjustRequest,
    VisualGameAdminUserListResponse,
    VisualGameAdminUserProvisionRequest,
    VisualGameAdminUserProfileUpdateRequest,
    VisualGameAdminUserSubscriptionUpdateRequest,
    VisualGameAdminUserSummary,
    VisualGameAdminAnalyticsOverviewResponse,
    VisualGameAdminAnalyticsDrilldownResponse,
    VisualGameAdminPlayerInteractionsResponse,
    VisualGameAdminContentAnalyticsResponse,
    VisualGameAdminGa4BehaviorResponse,
    WaitlistActionResponse,
    WaitlistActorType,
    WaitlistCreateManualEntryRequest,
    WaitlistEntry,
    WaitlistEntryDetailResponse,
    WaitlistEntryListResponse,
    WaitlistEvent,
    WaitlistEventType,
    WaitlistExportResponse,
    WaitlistInviteDelivery,
    WaitlistInviteDeliveryStatus,
    WaitlistInviteCode,
    WaitlistInviteRequest,
    WaitlistInviteValidateResponse,
    WaitlistSendRegisterCodeResponse,
    WaitlistJoinRequest,
    WaitlistJoinResponse,
    WaitlistNoteRequest,
    WaitlistPublicSource,
    WaitlistRejectRequest,
    WaitlistSource,
    WaitlistStatus,
  }

  /** @type {Record<string, object>} */
  const out = {}
  for (const name of Object.keys(registry).sort()) {
    out[name] = describeSchema(registry[name])
  }
  return out
}