清理线上普通图片错误素材类型
新增受权迁移脚本分批清理 assetKind=image 并执行零残留复核。 仅新增 SpacetimeDB 迁移 procedure,不修改 API 和常规 storage 写入语义。 同步 CLI 返回契约、生成绑定、发布制品、运维门禁和数据契约。
This commit is contained in:
@@ -448,6 +448,7 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
|
||||
- `generation_inputs_json` 只保存用户生成 / 重放输入,例如 `fields`、`references`、`artSpec`;动作输出和内部背景决策色不得再写入该 JSON。背景决策审计继续走独立审计链路。
|
||||
- 存量 `generation_inputs_json.characterAnimation`、已确认动作行的 helper 顶层 `frames/previewVideoPath/frameCount/fps/durationSeconds`、`screenColorHex`、正式帧中的 `frameIndex` 和误标动作预览 MP4,统一由 migration operator procedure `normalize_editor_character_animation_metadata_and_return` 分 `asset → project-resource → showcase → canvas` 四个 scope 迁移。顶层旧字段只有在 `asset_kind=character-animation`、正式序列字段、嵌套 `characterAnimation` 或权威 `asset_object` 已证明该行属于角色动作时才解释,普通图片 / 视频任意 JSON 中同名字段保持原样。迁移在计划态先把同 task 的已验证预览 MP4 重分类为 `video`,只有能形成正式图片序列的候选才可用于后续 scope;project-resource 自身缺帧但能用同 owner、同 task、同首帧 `assetObjectId/objectKey` 精确定位唯一最终账号素材时,dry-run 可消费 asset 的计划态正式序列,apply 仍要求 asset scope 已物理完成。canvas 同样按 project-resource 的计划态类型分类:旧库误标为动作、但规划后为 `video` 且 layout 未声明动作的 preview 图层不参与动作副本清理;layout 明确声明动作却指向该视频,或原始动作资源的规划存在 blocker,继续失败关闭。每一帧必须按首帧 bucket 下的精确对象路径匹配同 owner、同 task、`editor_character_animation` 且为图片的 `asset_object`,并从登记对象补齐 `objectKey/assetObjectId`。canvas layout 复制的 `sourceResourceId` 不参与迁移血缘校验,补建资源只采用最终账号素材的 DB 血缘;新生成链路仍严格校验直接来源。正式与旧版帧或时长冲突、最终候选为零或多于一个、帧对象无法精确验证和缺失正式帧集均作为 blocker,整批拒绝 apply;procedure 同时返回不含生成输入正文的 blocker 完整诊断,包含 scope、ID、原因、owner、project、task、对象身份和来源资源。
|
||||
- 运维入口固定为 `node scripts/spacetime-normalize-editor-character-actions.mjs --database <database> --server-url <url>`;默认全量 dry-run,`--apply` 时每批先 dry-run,再用返回的 batch SHA-256 写入,四个 scope 完成后从头执行零匹配 / 零 blocker 复核。迁移完成后 api-server、后台、前端和外部 helper 只读取正式字段,不再包含 legacy fallback。新建动作资源 / 素材若在 `generationInputs` 提交旧运行字段,或正式帧包含 `frameIndex`,api-server 与 SpacetimeDB storage 均失败关闭;其它素材的任意生成输入不受动作专属门禁影响。
|
||||
- 2026-08-10 前短期错误版本写入的 `asset_kind = "image"` 不走永久兼容,统一由 migration operator procedure `clean_editor_image_asset_kind_and_return` 清理。scope 固定为 `asset → project-resource → showcase → canvas`:前三者只把精确旧值改为 `None`;canvas 同时清理 `editor_canvas` / `editor_project` 双份 legacy layout 顶层 `assetKind` / `assetKindOverride` 和结构化 `editor_canvas_layer.asset_kind_override`,但不递归修改 `generationInputs.references[*].mediaType`,也不修改 MIME、`CanvasMediaType` 或 `asset_object.asset_kind`。纯分类修复保留原 `updated_at`、画布 revision 和迁移状态。双份 layout 清理后仍不一致、缺工程、owner 不一致或命中字段的画布项缺少稳定 layer / resource 身份时形成 blocker,整批拒绝 apply;诊断只返回 ID SHA-256、scope 和原因。
|
||||
- 普通画布 layer 的持久化和前端响应不包含顶层 `mediaType`;渲染类型只由资源 / 素材 `assetKind` 在前端派生。`generationInputs.references[*].mediaType` 是生成参考输入契约,不属于画布 layer legacy 字段,迁移和响应清洗不得递归删除。
|
||||
|
||||
## 新增编辑器 `assetKind` 接入清单(2026-08-03,2026-08-08 增补)
|
||||
|
||||
@@ -94,6 +94,8 @@ BgFilter 对已经落入私有 OSS 的生成原图、动作抽取帧和手动去
|
||||
|
||||
角色动作正式字段收口使用 `node scripts/spacetime-normalize-editor-character-actions.mjs --database <database> --server-url <url>`,且同样只能由已授权 migration operator 执行。必须先发布包含 normalization cursor 索引和 `normalize_editor_character_animation_metadata_and_return` 的 SpacetimeDB 模块,在 API / worker 仍处于维护模式时先运行默认全量 dry-run;脚本固定按 `asset → project-resource → showcase → canvas` 扫描,普通 scope 每批最多 25 行,canvas 每批最多 5 行。全量 dry-run 会在不写库的情况下把 asset 计划结果投影给同 owner / task / 首帧对象精确匹配的 project-resource,再把前置 scope 的计划结果投影给 canvas 检查;因此同 task 的误标预览 MP4 会先按权威视频对象排除,最终图片序列会逐帧核对并补齐精确 `asset_object` 身份。canvas 中仍引用误标 preview resource 的普通 video layer 会按 project-resource 计划态 `video` 跳过,只有 layout 明确声明动作却指向视频,或资源规划本身失败时才形成 blocker。apply 时仍要求前置 scope 已按顺序物理完成,不能跳过 asset 直接让 project-resource 借未落库结果。历史 canvas 复制的 `sourceResourceId` 不是迁移证据,不要因它仍指向原角色而手工改库,补建资源会采用最终账号素材的 DB 血缘。出现 blocker 时脚本会打印 ID、原因、owner、project、task、对象身份和来源资源;先据此区分最终候选为零 / 多个、正式与旧版冲突、帧对象不匹配或缺失资源,不得跳过 scope。确认 dry-run 后追加 `--apply`,脚本会对每批重新 dry-run、携带该批 SHA-256 apply,并在最后从头要求四个 scope 均为零匹配、零 blocker。只有该复核通过后才发布移除 action fallback 的 API / Web。Stdb build artifact 和完整 release 包必须同时包含 `scripts/spacetime-normalize-editor-character-actions.mjs` 与 `scripts/spacetime-migration-common.mjs`。本地切换分支时若要避免 dev publish 因 schema 冲突使用 `-c=on-conflict` 清库,启动命令必须追加 `--preserve-database`,让冲突直接失败。
|
||||
|
||||
普通图片错误素材类型清理使用 `npm run spacetime:editor-image-asset-kind:clean -- --database <database> --server-url <url>`,只能由已授权 migration operator 执行。先进入维护模式并发布包含 `clean_editor_image_asset_kind_and_return` 的 SpacetimeDB module,并保持会写入旧值的在线 API / controller / worker 停止;随后运行默认全量 dry-run,核对 `asset → project-resource → showcase → canvas` 各 scope 的扫描数、命中行数、字段数和 blocker 均符合预期,再追加 `--apply`。脚本对每批重新 dry-run、绑定 SHA-256,最后自动从头复核零命中;任一画布数据异常都会只输出哈希化 ID、scope 与原因并停止,不能跳过。清理只处理精确业务旧值,不修改 `asset_object.asset_kind`、MIME、媒体类型、API 或常规 storage 写入语义;完成零残留复核后恢复已修正的应用版本,最后退出维护。Stdb build artifact 和完整 release 包必须同时包含 `scripts/spacetime-clean-editor-image-asset-kind.mjs` 与 `scripts/spacetime-migration-common.mjs`。
|
||||
|
||||
自 2026-07-11 起,`Genarrative-Full-Build-And-Deploy` 的每日 04:00 timer 默认以 `DEPLOY_TARGET=development`、`STDB_API_ROLLOUT_MODE=normal` 对仅供开发使用的 dev 服务器执行 Stdb → API → Web 完整发布,不进入人工 rollout gate。三个下游 Build 都由 Full Job 显式传 `PUBLISH_AFTER_BUILD=false`,不得依赖下游 Job 默认值或提前各自发布;统一 Build 完成后仍由 Full Job 按固定顺序发布。人工维护窗口才选择 `pause-after-stdb`,且必须配置 `STDB_API_ROLLOUT_APPROVERS`。上文“定时构建缺少审批人时失败”的旧口径不再作为当前 dev 定时发布行为。
|
||||
|
||||
Full Job 通过 `EXIT_MAINTENANCE_MODE_AFTER_COMPLETION` 明确选择完整发布成功后是否退出维护,默认勾选以保持历史行为。Full 对 Stdb Publish 和 API Deploy 两个下游阶段都固定传 `KEEP_MAINTENANCE_MODE=true`,让 maintenance marker 持续覆盖 Stdb → API → Web 整段发布;Web Deploy 成功后才进入独立 `Exit Maintenance` 阶段。该阶段只能通过 `agent none` 和显式 `node(...)` 分配目标机,直接执行 `/opt/genarrative/current/scripts/deploy/maintenance-off.sh`;目标机不得 checkout Git、挂载 Git SSH 凭据或依赖 Jenkins workspace 源码。取消勾选时跳过最终退出阶段,便于内网验收完成后人工恢复公网。`Genarrative-Api-Deploy` 也单独暴露 `KEEP_MAINTENANCE_MODE` 参数,并转换为随发布包脚本的 `--keep-maintenance-mode`;失败路径仍按既有 current 切换边界保留或退出维护,不受成功态选项覆盖。外部生成 queue 的 `warning` 由 API/worker 固化为可直接展示的完整文案,Web 不再补前缀,因此 API/worker 与 Web 必须在同一维护窗口按同一版本协调发布;分开运行 Job 时先保持维护态完成 API/worker,再发布 Web,二者完成后才能恢复公网,不得在公网可用期间只滚动其中一侧。
|
||||
@@ -597,7 +599,7 @@ Pingora current release 自审脚本 `scripts/ops/pingora-current-release-audit.
|
||||
|
||||
同一 API release 随包依赖还必须包含 `scripts/check-pingora-release-readiness.mjs` 与 `scripts/check-pingora-canary-live.mjs`。前者在 current release 上以 `--release-runtime-only` 汇总运行时复核,后者支撑目标 Nginx canary live smoke;缺少任一脚本时不能进入直连切换窗口。
|
||||
|
||||
`Genarrative-Stdb-Module-Build` 的 Jenkins 归档产物必须包含 `build/<version>/spacetime_module.wasm`、`spacetime_module.wasm.sha256`、`release-manifest.json`、`scripts/deploy/production-stdb-publish.sh`、`scripts/deploy/production-runtime-writer-identity-rotate.mjs`、`scripts/deploy/maintenance-on.sh`、`scripts/deploy/maintenance-off.sh`、`scripts/spacetime-migration-common.mjs`、`scripts/spacetime-maintain-external-generation-jobs.mjs`、`scripts/spacetime-normalize-editor-character-actions.mjs`、`scripts/spacetime-migrate-editor-canvas-layout.mjs` 和 `scripts/database-backup-to-oss.mjs`,不得包含 `migration-bootstrap-secret.txt` 或任何原始 bootstrap secret。`Genarrative-Stdb-Module-Build` 只接受 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 指向的受保护 Jenkins Secret File:构建 shell 从临时文件读取原始值,强制校验为 64 位十六进制,计算 SHA-256,随后只通过 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET_SHA256` 注入 Rust 编译;WASM 因而只包含摘要,不包含可下载的原文,Stdb `release-manifest.json` 以 `migration_bootstrap_secret_sha256` 记录该非敏感摘要。`Genarrative-Stdb-Module-Publish` 只通过 `copyArtifacts` 复制上述非敏感产物,不在目标机器 checkout Git,并在发布阶段用同一个凭据 ID 再次挂载 Secret File;publish 必须再次校验 64 位十六进制、重算 SHA-256,并与 manifest 的 `migration_bootstrap_secret_sha256` 强制匹配后才可发布。Full Build 必须保证 Stdb Build / Publish 的 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 完全相同并把同一个 ID 同时透传,不能从构建 artifact 传 secret;ID 不同、manifest 缺摘要或摘要不匹配都必须在发布前失败。
|
||||
`Genarrative-Stdb-Module-Build` 的 Jenkins 归档产物必须包含 `build/<version>/spacetime_module.wasm`、`spacetime_module.wasm.sha256`、`release-manifest.json`、`scripts/deploy/production-stdb-publish.sh`、`scripts/deploy/production-runtime-writer-identity-rotate.mjs`、`scripts/deploy/maintenance-on.sh`、`scripts/deploy/maintenance-off.sh`、`scripts/spacetime-migration-common.mjs`、`scripts/spacetime-maintain-external-generation-jobs.mjs`、`scripts/spacetime-clean-editor-image-asset-kind.mjs`、`scripts/spacetime-normalize-editor-character-actions.mjs`、`scripts/spacetime-migrate-editor-canvas-layout.mjs` 和 `scripts/database-backup-to-oss.mjs`,不得包含 `migration-bootstrap-secret.txt` 或任何原始 bootstrap secret。`Genarrative-Stdb-Module-Build` 只接受 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 指向的受保护 Jenkins Secret File:构建 shell 从临时文件读取原始值,强制校验为 64 位十六进制,计算 SHA-256,随后只通过 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET_SHA256` 注入 Rust 编译;WASM 因而只包含摘要,不包含可下载的原文,Stdb `release-manifest.json` 以 `migration_bootstrap_secret_sha256` 记录该非敏感摘要。`Genarrative-Stdb-Module-Publish` 只通过 `copyArtifacts` 复制上述非敏感产物,不在目标机器 checkout Git,并在发布阶段用同一个凭据 ID 再次挂载 Secret File;publish 必须再次校验 64 位十六进制、重算 SHA-256,并与 manifest 的 `migration_bootstrap_secret_sha256` 强制匹配后才可发布。Full Build 必须保证 Stdb Build / Publish 的 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 完全相同并把同一个 ID 同时透传,不能从构建 artifact 传 secret;ID 不同、manifest 缺摘要或摘要不匹配都必须在发布前失败。
|
||||
|
||||
三个 SCM Jenkinsfile 将 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 默认固定为 `genarrative-spacetime-bootstrap-secret-dev-file`。Secret File 的原文只存在于 Jenkins Credentials;credential ID、参数默认值和定时 / 发布行为以仓库 Jenkinsfile 为事实源,不能只改 Job UI,因为 Declarative Pipeline 下一次载入会重写参数定义。旧 Secret Text `genarrative-spacetime-bootstrap-secret-dev` 继续保留给 Database Import / Export,不得原地改类型或删除。
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ pipeline {
|
||||
|
||||
stage('Archive') {
|
||||
steps {
|
||||
archiveArtifacts artifacts: "build/${env.EFFECTIVE_BUILD_VERSION}/spacetime_module.wasm,build/${env.EFFECTIVE_BUILD_VERSION}/spacetime_module.wasm.sha256,build/${env.EFFECTIVE_BUILD_VERSION}/release-manifest.json,scripts/deploy/production-stdb-publish.sh,scripts/deploy/production-runtime-writer-identity-rotate.mjs,scripts/deploy/maintenance-on.sh,scripts/deploy/maintenance-off.sh,scripts/spacetime-migration-common.mjs,scripts/spacetime-maintain-external-generation-jobs.mjs,scripts/spacetime-normalize-editor-character-actions.mjs,scripts/spacetime-migrate-editor-canvas-layout.mjs,scripts/spacetime-repair-editor-canvas-resources.mjs,scripts/database-backup-to-oss.mjs", fingerprint: true
|
||||
archiveArtifacts artifacts: "build/${env.EFFECTIVE_BUILD_VERSION}/spacetime_module.wasm,build/${env.EFFECTIVE_BUILD_VERSION}/spacetime_module.wasm.sha256,build/${env.EFFECTIVE_BUILD_VERSION}/release-manifest.json,scripts/deploy/production-stdb-publish.sh,scripts/deploy/production-runtime-writer-identity-rotate.mjs,scripts/deploy/maintenance-on.sh,scripts/deploy/maintenance-off.sh,scripts/spacetime-migration-common.mjs,scripts/spacetime-maintain-external-generation-jobs.mjs,scripts/spacetime-clean-editor-image-asset-kind.mjs,scripts/spacetime-normalize-editor-character-actions.mjs,scripts/spacetime-migrate-editor-canvas-layout.mjs,scripts/spacetime-repair-editor-canvas-resources.mjs,scripts/database-backup-to-oss.mjs", fingerprint: true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ pipeline {
|
||||
copyArtifacts(
|
||||
projectName: params.BUILD_JOB_NAME,
|
||||
selector: specific(params.BUILD_NUMBER_TO_DEPLOY),
|
||||
filter: "build/${params.BUILD_VERSION}/spacetime_module.wasm,build/${params.BUILD_VERSION}/spacetime_module.wasm.sha256,build/${params.BUILD_VERSION}/release-manifest.json,scripts/deploy/production-stdb-publish.sh,scripts/deploy/production-runtime-writer-identity-rotate.mjs,scripts/deploy/maintenance-on.sh,scripts/deploy/maintenance-off.sh,scripts/spacetime-migration-common.mjs,scripts/spacetime-maintain-external-generation-jobs.mjs,scripts/spacetime-normalize-editor-character-actions.mjs,scripts/spacetime-migrate-editor-canvas-layout.mjs,scripts/spacetime-repair-editor-canvas-resources.mjs,scripts/database-backup-to-oss.mjs",
|
||||
filter: "build/${params.BUILD_VERSION}/spacetime_module.wasm,build/${params.BUILD_VERSION}/spacetime_module.wasm.sha256,build/${params.BUILD_VERSION}/release-manifest.json,scripts/deploy/production-stdb-publish.sh,scripts/deploy/production-runtime-writer-identity-rotate.mjs,scripts/deploy/maintenance-on.sh,scripts/deploy/maintenance-off.sh,scripts/spacetime-migration-common.mjs,scripts/spacetime-maintain-external-generation-jobs.mjs,scripts/spacetime-clean-editor-image-asset-kind.mjs,scripts/spacetime-normalize-editor-character-actions.mjs,scripts/spacetime-migrate-editor-canvas-layout.mjs,scripts/spacetime-repair-editor-canvas-resources.mjs,scripts/database-backup-to-oss.mjs",
|
||||
target: '.',
|
||||
fingerprintArtifacts: true
|
||||
)
|
||||
|
||||
@@ -558,6 +558,7 @@ copy_required_file "${SCRIPT_DIR}/spacetime-export-migration-json.mjs" "${TARGET
|
||||
copy_required_file "${SCRIPT_DIR}/spacetime-import-migration-json.mjs" "${TARGET_DIR}/scripts/database-import.mjs" "数据库导入脚本"
|
||||
copy_required_file "${SCRIPT_DIR}/spacetime-migration-common.mjs" "${TARGET_DIR}/scripts/spacetime-migration-common.mjs" "数据库迁移公共脚本"
|
||||
copy_required_file "${SCRIPT_DIR}/spacetime-maintain-external-generation-jobs.mjs" "${TARGET_DIR}/scripts/spacetime-maintain-external-generation-jobs.mjs" "外部生成任务维护脚本"
|
||||
copy_required_file "${SCRIPT_DIR}/spacetime-clean-editor-image-asset-kind.mjs" "${TARGET_DIR}/scripts/spacetime-clean-editor-image-asset-kind.mjs" "普通图片素材类型清理脚本"
|
||||
copy_required_file "${SCRIPT_DIR}/spacetime-normalize-editor-character-actions.mjs" "${TARGET_DIR}/scripts/spacetime-normalize-editor-character-actions.mjs" "角色动作元数据规范化脚本"
|
||||
copy_required_file "${SCRIPT_DIR}/spacetime-authorize-migration-operator.mjs" "${TARGET_DIR}/scripts/spacetime-authorize-migration-operator.mjs" "数据库迁移授权脚本"
|
||||
copy_required_file "${SCRIPT_DIR}/spacetime-revoke-migration-operator.mjs" "${TARGET_DIR}/scripts/spacetime-revoke-migration-operator.mjs" "数据库迁移撤权脚本"
|
||||
|
||||
@@ -651,6 +651,21 @@ const checks = [
|
||||
includes: "await scanScopes(options, { verifyZero: true })",
|
||||
reason: '角色动作规范化 apply 后必须执行全量零匹配复核。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/spacetime-clean-editor-image-asset-kind.mjs',
|
||||
includes: "const SCOPES = ['asset', 'project-resource', 'showcase', 'canvas']",
|
||||
reason: '普通图片错误素材类型必须覆盖三张业务表和持久化画布副本。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/spacetime-clean-editor-image-asset-kind.mjs',
|
||||
includes: 'expectedBatchSha256: dryRun.batch_sha256',
|
||||
reason: '普通图片素材类型 apply 必须绑定同批 dry-run 返回的摘要。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/spacetime-clean-editor-image-asset-kind.mjs',
|
||||
includes: "await scanScopes(options, { verifyZero: true })",
|
||||
reason: '普通图片素材类型清理 apply 后必须执行全量零匹配复核。',
|
||||
},
|
||||
{
|
||||
file: 'scripts/spacetime-repair-editor-canvas-resources.mjs',
|
||||
includes: 'buildProcedureInput(canvas, updatedAtMicros, !options.apply)',
|
||||
@@ -686,6 +701,16 @@ const checks = [
|
||||
includes: 'scripts/spacetime-normalize-editor-character-actions.mjs',
|
||||
reason: 'Stdb Publish 必须从同一上游制品复制角色动作元数据规范化脚本。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-stdb-module-build',
|
||||
includes: 'scripts/spacetime-clean-editor-image-asset-kind.mjs',
|
||||
reason: 'Stdb Build 必须归档普通图片素材类型清理脚本。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-stdb-module-publish',
|
||||
includes: 'scripts/spacetime-clean-editor-image-asset-kind.mjs',
|
||||
reason: 'Stdb Publish 必须从同一上游制品复制普通图片素材类型清理脚本。',
|
||||
},
|
||||
{
|
||||
file: 'jenkins/Jenkinsfile.production-stdb-module-build',
|
||||
includes: 'scripts/spacetime-repair-editor-canvas-resources.mjs',
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createHash } from 'node:crypto';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
import {
|
||||
callSpacetimeProcedure,
|
||||
callSpacetimeProcedureViaCli,
|
||||
encodeSpacetimeCliOption,
|
||||
ensureProcedureOk,
|
||||
parsePositiveInteger,
|
||||
} from './spacetime-migration-common.mjs';
|
||||
|
||||
const PROCEDURE_NAME = 'clean_editor_image_asset_kind_and_return';
|
||||
const SCOPES = ['asset', 'project-resource', 'showcase', 'canvas'];
|
||||
const DEFAULT_CHUNK_SIZE = 25;
|
||||
const CANVAS_MAX_CHUNK_SIZE = 5;
|
||||
const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
||||
|
||||
function sha256(value) {
|
||||
return createHash('sha256').update(value).digest('hex');
|
||||
}
|
||||
|
||||
function usage() {
|
||||
return `用法:
|
||||
node scripts/spacetime-clean-editor-image-asset-kind.mjs \\
|
||||
--database <name> --server <name-or-url> [--chunk-size <1-25>] [--apply]
|
||||
|
||||
默认按 asset、project-resource、showcase、canvas 的固定顺序执行全量 dry-run,不修改数据。
|
||||
脚本只把业务 assetKind 精确等于 "image" 的旧值清为空;不会修改 MIME、媒体类型或 asset_object.asset_kind。
|
||||
追加 --apply 后,每批仍会先 dry-run;只有 blocker 为零,才携带该批返回的 SHA-256 立即 apply。
|
||||
apply 完成后脚本会再次从头 dry-run,要求四个 scope 的 matched/blocker 均为零。
|
||||
必须使用已授权 database migration operator 的 spacetime CLI 登录态,并显式指定 server。`;
|
||||
}
|
||||
|
||||
export function parseOptions(argv, env = process.env) {
|
||||
const options = {
|
||||
apply: false,
|
||||
chunkSize: DEFAULT_CHUNK_SIZE,
|
||||
database: env.GENARRATIVE_SPACETIME_DATABASE || '',
|
||||
passthrough: [],
|
||||
server: env.GENARRATIVE_SPACETIME_SERVER || '',
|
||||
serverUrl: env.GENARRATIVE_SPACETIME_SERVER_URL || '',
|
||||
token: env.GENARRATIVE_SPACETIME_TOKEN || '',
|
||||
useHttp: false,
|
||||
};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
const readValue = () => {
|
||||
const value = argv[index + 1];
|
||||
if (!value || value.startsWith('--')) {
|
||||
throw new Error(`${arg} 缺少参数值。`);
|
||||
}
|
||||
index += 1;
|
||||
return value.trim();
|
||||
};
|
||||
if (arg === '--database') {
|
||||
options.database = readValue();
|
||||
} else if (arg === '--server') {
|
||||
options.server = readValue();
|
||||
} else if (arg === '--server-url') {
|
||||
options.serverUrl = readValue();
|
||||
} else if (arg === '--token') {
|
||||
options.token = readValue();
|
||||
} else if (arg === '--chunk-size') {
|
||||
options.chunkSize = parsePositiveInteger(readValue(), arg);
|
||||
} else if (arg === '--apply') {
|
||||
options.apply = true;
|
||||
} else if (arg === '--use-http') {
|
||||
options.useHttp = true;
|
||||
} else if (arg === '--no-config' || arg === '--anonymous') {
|
||||
options.passthrough.push(arg);
|
||||
} else if (arg === '--help' || arg === '-h') {
|
||||
options.help = true;
|
||||
} else {
|
||||
throw new Error(`未知参数: ${arg}`);
|
||||
}
|
||||
}
|
||||
if (options.chunkSize > DEFAULT_CHUNK_SIZE) {
|
||||
throw new Error(`--chunk-size 不能超过 ${DEFAULT_CHUNK_SIZE}。`);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
export function buildCleanupInput({
|
||||
scope,
|
||||
cursor = null,
|
||||
limit,
|
||||
dryRun,
|
||||
expectedBatchSha256 = null,
|
||||
}) {
|
||||
if (!SCOPES.includes(scope)) {
|
||||
throw new Error(`未知普通图片 assetKind 清理 scope: ${scope}`);
|
||||
}
|
||||
if (!Number.isInteger(limit) || limit < 1) {
|
||||
throw new Error('普通图片 assetKind 清理 limit 必须是正整数。');
|
||||
}
|
||||
if (scope === 'canvas' && limit > CANVAS_MAX_CHUNK_SIZE) {
|
||||
throw new Error(`canvas scope limit 不能超过 ${CANVAS_MAX_CHUNK_SIZE}。`);
|
||||
}
|
||||
if (!dryRun && !SHA256_PATTERN.test(expectedBatchSha256 || '')) {
|
||||
throw new Error('apply 必须绑定 dry-run 返回的 64 位 batch SHA-256。');
|
||||
}
|
||||
return {
|
||||
scope,
|
||||
cursor: encodeSpacetimeCliOption(cursor),
|
||||
limit,
|
||||
dry_run: dryRun,
|
||||
expected_batch_sha_256: encodeSpacetimeCliOption(
|
||||
dryRun ? null : expectedBatchSha256,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function scopeLimit(scope, chunkSize) {
|
||||
return scope === 'canvas'
|
||||
? Math.min(chunkSize, CANVAS_MAX_CHUNK_SIZE)
|
||||
: chunkSize;
|
||||
}
|
||||
|
||||
function assertSafeBatch(result, scope) {
|
||||
ensureProcedureOk(result);
|
||||
if (result.scope !== scope) {
|
||||
throw new Error(`procedure 返回 scope ${result.scope},预期为 ${scope}。`);
|
||||
}
|
||||
if (result.blocker_count !== 0 || result.blocker_samples.length !== 0) {
|
||||
throw new Error(`${scope} scope 存在 ${result.blocker_count} 个 blocker。`);
|
||||
}
|
||||
if (!SHA256_PATTERN.test(result.batch_sha256 || '')) {
|
||||
throw new Error(`${scope} scope 未返回有效的 batch SHA-256。`);
|
||||
}
|
||||
}
|
||||
|
||||
async function callBatch(options, input) {
|
||||
return options.useHttp
|
||||
? callSpacetimeProcedure(options, PROCEDURE_NAME, input)
|
||||
: callSpacetimeProcedureViaCli(options, PROCEDURE_NAME, input);
|
||||
}
|
||||
|
||||
export async function scanScopes(
|
||||
options,
|
||||
{ apply = false, verifyZero = false, callProcedure = callBatch } = {},
|
||||
) {
|
||||
const summaries = [];
|
||||
for (const scope of SCOPES) {
|
||||
let cursor = null;
|
||||
const seenCursors = new Set();
|
||||
const summary = {
|
||||
scope,
|
||||
scanned_count: 0,
|
||||
matched_count: 0,
|
||||
updated_count: 0,
|
||||
cleaned_field_count: 0,
|
||||
batches: 0,
|
||||
};
|
||||
do {
|
||||
const limit = scopeLimit(scope, options.chunkSize);
|
||||
const dryRun = await callProcedure(
|
||||
options,
|
||||
buildCleanupInput({ scope, cursor, limit, dryRun: true }),
|
||||
);
|
||||
assertSafeBatch(dryRun, scope);
|
||||
summary.scanned_count += dryRun.scanned_count;
|
||||
summary.matched_count += dryRun.matched_count;
|
||||
summary.cleaned_field_count += dryRun.cleaned_field_count;
|
||||
summary.batches += 1;
|
||||
|
||||
if (verifyZero && dryRun.matched_count !== 0) {
|
||||
throw new Error(
|
||||
`${scope} scope apply 后复核仍有 ${dryRun.matched_count} 行待清理。`,
|
||||
);
|
||||
}
|
||||
if (apply && dryRun.matched_count > 0) {
|
||||
const applied = await callProcedure(
|
||||
options,
|
||||
buildCleanupInput({
|
||||
scope,
|
||||
cursor,
|
||||
limit,
|
||||
dryRun: false,
|
||||
expectedBatchSha256: dryRun.batch_sha256,
|
||||
}),
|
||||
);
|
||||
assertSafeBatch(applied, scope);
|
||||
if (applied.batch_sha256 !== dryRun.batch_sha256) {
|
||||
throw new Error(`${scope} scope apply 返回的 batch SHA-256 与 dry-run 不一致。`);
|
||||
}
|
||||
if (applied.updated_count !== dryRun.matched_count) {
|
||||
throw new Error(
|
||||
`${scope} scope apply 更新 ${applied.updated_count} 行,dry-run 匹配 ${dryRun.matched_count} 行。`,
|
||||
);
|
||||
}
|
||||
if (applied.cleaned_field_count !== dryRun.cleaned_field_count) {
|
||||
throw new Error(`${scope} scope apply 返回的清理字段数与 dry-run 不一致。`);
|
||||
}
|
||||
summary.updated_count += applied.updated_count;
|
||||
}
|
||||
const nextCursor = dryRun.has_more ? dryRun.next_cursor : null;
|
||||
if (dryRun.has_more && !nextCursor) {
|
||||
throw new Error(`${scope} scope 声明 has_more 但未返回 next_cursor。`);
|
||||
}
|
||||
if (nextCursor && seenCursors.has(nextCursor)) {
|
||||
throw new Error(
|
||||
`${scope} scope 返回了重复的 next_cursor(SHA-256: ${sha256(nextCursor)})。`,
|
||||
);
|
||||
}
|
||||
if (nextCursor) {
|
||||
seenCursors.add(nextCursor);
|
||||
}
|
||||
cursor = nextCursor;
|
||||
} while (cursor);
|
||||
summaries.push(summary);
|
||||
}
|
||||
return summaries;
|
||||
}
|
||||
|
||||
export async function main(argv = process.argv.slice(2)) {
|
||||
const options = parseOptions(argv);
|
||||
if (options.help) {
|
||||
console.log(usage());
|
||||
return;
|
||||
}
|
||||
if (!options.database) {
|
||||
throw new Error('必须显式传入 --database。');
|
||||
}
|
||||
if (!options.server && !options.serverUrl) {
|
||||
throw new Error('必须显式传入 --server / --server-url,不使用默认 cloud target。');
|
||||
}
|
||||
if (options.useHttp && !options.token) {
|
||||
throw new Error('--use-http 需要通过 --token 或 GENARRATIVE_SPACETIME_TOKEN 提供身份。');
|
||||
}
|
||||
|
||||
const migration = await scanScopes(options, { apply: options.apply });
|
||||
const verification = options.apply
|
||||
? await scanScopes(options, { verifyZero: true })
|
||||
: null;
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
procedure: PROCEDURE_NAME,
|
||||
applied: options.apply,
|
||||
scope_order: SCOPES,
|
||||
migration,
|
||||
verification,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
if (!options.apply) {
|
||||
console.log('全量 dry-run 已通过;确认输出后追加 --apply 重跑。');
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
||||
main().catch((error) => {
|
||||
console.error(
|
||||
`[spacetime:editor-image-asset-kind:clean] ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
buildCleanupInput,
|
||||
parseOptions,
|
||||
scanScopes,
|
||||
} from './spacetime-clean-editor-image-asset-kind.mjs';
|
||||
|
||||
describe('普通图片 assetKind 清理脚本', () => {
|
||||
it('默认 dry-run 并要求调用方显式选择 apply', () => {
|
||||
expect(
|
||||
parseOptions(
|
||||
['--database', 'genarrative-prod', '--server', 'prod'],
|
||||
{},
|
||||
),
|
||||
).toMatchObject({
|
||||
apply: false,
|
||||
chunkSize: 25,
|
||||
database: 'genarrative-prod',
|
||||
server: 'prod',
|
||||
});
|
||||
});
|
||||
|
||||
it('编码 cursor、限制 canvas 批量并要求 apply hash', () => {
|
||||
expect(
|
||||
buildCleanupInput({
|
||||
scope: 'asset',
|
||||
cursor: 'asset-25',
|
||||
limit: 25,
|
||||
dryRun: true,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: 'asset',
|
||||
cursor: [0, 'asset-25'],
|
||||
limit: 25,
|
||||
dry_run: true,
|
||||
expected_batch_sha_256: null,
|
||||
});
|
||||
expect(() =>
|
||||
buildCleanupInput({ scope: 'canvas', limit: 6, dryRun: true }),
|
||||
).toThrow('canvas scope limit');
|
||||
expect(() =>
|
||||
buildCleanupInput({ scope: 'showcase', limit: 25, dryRun: false }),
|
||||
).toThrow('batch SHA-256');
|
||||
});
|
||||
|
||||
it('按固定 scope 顺序执行 hash 绑定 apply 并保留字段计数', async () => {
|
||||
const calls: Array<Record<string, unknown>> = [];
|
||||
const callProcedure = async (
|
||||
_options: Record<string, unknown>,
|
||||
input: Record<string, unknown>,
|
||||
) => {
|
||||
calls.push(input);
|
||||
const dryRun = input.dry_run === true;
|
||||
const scope = String(input.scope);
|
||||
const hashDigit = {
|
||||
asset: 'a',
|
||||
'project-resource': 'b',
|
||||
showcase: 'c',
|
||||
canvas: 'd',
|
||||
}[scope]!;
|
||||
return {
|
||||
ok: true,
|
||||
scope,
|
||||
dry_run: dryRun,
|
||||
scanned_count: 1,
|
||||
matched_count: 1,
|
||||
updated_count: dryRun ? 0 : 1,
|
||||
cleaned_field_count: scope === 'canvas' ? 3 : 1,
|
||||
blocker_count: 0,
|
||||
blocker_samples: [],
|
||||
next_cursor: null,
|
||||
has_more: false,
|
||||
batch_sha256: hashDigit.repeat(64),
|
||||
error_message: null,
|
||||
};
|
||||
};
|
||||
|
||||
const summaries = await scanScopes(
|
||||
{ chunkSize: 25 },
|
||||
{ apply: true, callProcedure },
|
||||
);
|
||||
|
||||
expect(summaries.map((summary) => summary.scope)).toEqual([
|
||||
'asset',
|
||||
'project-resource',
|
||||
'showcase',
|
||||
'canvas',
|
||||
]);
|
||||
expect(calls.map((call) => `${call.scope}:${call.dry_run}`)).toEqual([
|
||||
'asset:true',
|
||||
'asset:false',
|
||||
'project-resource:true',
|
||||
'project-resource:false',
|
||||
'showcase:true',
|
||||
'showcase:false',
|
||||
'canvas:true',
|
||||
'canvas:false',
|
||||
]);
|
||||
expect(summaries.at(-1)?.cleaned_field_count).toBe(3);
|
||||
expect(calls.at(-2)?.limit).toBe(5);
|
||||
for (let index = 1; index < calls.length; index += 2) {
|
||||
expect(calls[index]?.expected_batch_sha_256).toEqual([
|
||||
0,
|
||||
String(calls[index - 1]?.scope === 'asset'
|
||||
? 'a'
|
||||
: calls[index - 1]?.scope === 'project-resource'
|
||||
? 'b'
|
||||
: calls[index - 1]?.scope === 'showcase'
|
||||
? 'c'
|
||||
: 'd').repeat(64),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('拒绝 apply 后计数漂移与复核残留', async () => {
|
||||
const driftingCall = async (
|
||||
_options: Record<string, unknown>,
|
||||
input: Record<string, unknown>,
|
||||
) => ({
|
||||
ok: true,
|
||||
scope: input.scope,
|
||||
dry_run: input.dry_run,
|
||||
scanned_count: 1,
|
||||
matched_count: 1,
|
||||
updated_count: input.dry_run ? 0 : 1,
|
||||
cleaned_field_count: input.dry_run ? 2 : 1,
|
||||
blocker_count: 0,
|
||||
blocker_samples: [],
|
||||
next_cursor: null,
|
||||
has_more: false,
|
||||
batch_sha256: 'a'.repeat(64),
|
||||
error_message: null,
|
||||
});
|
||||
await expect(
|
||||
scanScopes(
|
||||
{ chunkSize: 25 },
|
||||
{ apply: true, callProcedure: driftingCall },
|
||||
),
|
||||
).rejects.toThrow('清理字段数');
|
||||
|
||||
const residualCall = async (
|
||||
_options: Record<string, unknown>,
|
||||
input: Record<string, unknown>,
|
||||
) => ({
|
||||
ok: true,
|
||||
scope: input.scope,
|
||||
dry_run: true,
|
||||
scanned_count: 1,
|
||||
matched_count: 1,
|
||||
updated_count: 0,
|
||||
cleaned_field_count: 1,
|
||||
blocker_count: 0,
|
||||
blocker_samples: [],
|
||||
next_cursor: null,
|
||||
has_more: false,
|
||||
batch_sha256: 'b'.repeat(64),
|
||||
error_message: null,
|
||||
});
|
||||
await expect(
|
||||
scanScopes(
|
||||
{ chunkSize: 25 },
|
||||
{ verifyZero: true, callProcedure: residualCall },
|
||||
),
|
||||
).rejects.toThrow('apply 后复核仍有');
|
||||
});
|
||||
|
||||
it('报告游标循环时不泄露原始标识', async () => {
|
||||
const privateCursor = 'private-asset-id';
|
||||
const loopingCall = async (
|
||||
_options: Record<string, unknown>,
|
||||
input: Record<string, unknown>,
|
||||
) => ({
|
||||
ok: true,
|
||||
scope: input.scope,
|
||||
dry_run: true,
|
||||
scanned_count: 1,
|
||||
matched_count: 0,
|
||||
updated_count: 0,
|
||||
cleaned_field_count: 0,
|
||||
blocker_count: 0,
|
||||
blocker_samples: [],
|
||||
next_cursor: privateCursor,
|
||||
has_more: true,
|
||||
batch_sha256: 'c'.repeat(64),
|
||||
error_message: null,
|
||||
});
|
||||
|
||||
let message = '';
|
||||
try {
|
||||
await scanScopes(
|
||||
{ chunkSize: 25 },
|
||||
{ callProcedure: loopingCall },
|
||||
);
|
||||
} catch (error) {
|
||||
message = error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
expect(message).toContain('SHA-256');
|
||||
expect(message).not.toContain(privateCursor);
|
||||
});
|
||||
});
|
||||
@@ -294,8 +294,10 @@ function normalizeProcedureResult(value, procedureName) {
|
||||
function normalizeSatsObject(value, procedureName) {
|
||||
const normalized = normalizeSatsValue(value);
|
||||
if (
|
||||
procedureName !==
|
||||
'normalize_editor_character_animation_metadata_and_return' ||
|
||||
![
|
||||
'normalize_editor_character_animation_metadata_and_return',
|
||||
'clean_editor_image_asset_kind_and_return',
|
||||
].includes(procedureName) ||
|
||||
!normalized ||
|
||||
typeof normalized !== 'object' ||
|
||||
Array.isArray(normalized)
|
||||
@@ -311,6 +313,27 @@ function normalizeSatsObject(value, procedureName) {
|
||||
}
|
||||
|
||||
function normalizeSatsProduct(value, procedureName) {
|
||||
if (
|
||||
procedureName === 'clean_editor_image_asset_kind_and_return' &&
|
||||
value.length === 13
|
||||
) {
|
||||
return {
|
||||
ok: normalizeSatsValue(value[0]),
|
||||
scope: normalizeSatsValue(value[1]),
|
||||
dry_run: normalizeSatsValue(value[2]),
|
||||
scanned_count: normalizeSatsValue(value[3]),
|
||||
matched_count: normalizeSatsValue(value[4]),
|
||||
updated_count: normalizeSatsValue(value[5]),
|
||||
cleaned_field_count: normalizeSatsValue(value[6]),
|
||||
blocker_count: normalizeSatsValue(value[7]),
|
||||
blocker_samples: normalizeSatsValue(value[8]),
|
||||
next_cursor: normalizeSatsOption(value[9]),
|
||||
has_more: normalizeSatsValue(value[10]),
|
||||
batch_sha256: normalizeSatsValue(value[11]),
|
||||
error_message: normalizeSatsOption(value[12]),
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
procedureName === 'normalize_editor_character_animation_metadata_and_return' &&
|
||||
value.length === 19
|
||||
|
||||
@@ -174,4 +174,53 @@ describe('SpacetimeDB CLI SATS option encoding', () => {
|
||||
});
|
||||
expect(result).not.toHaveProperty('batch_sha_256');
|
||||
});
|
||||
|
||||
it('normalizes ordinary image assetKind cleanup results', () => {
|
||||
const tupleResult = parseProcedureResult(
|
||||
JSON.stringify([
|
||||
true,
|
||||
'canvas',
|
||||
true,
|
||||
5,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
[],
|
||||
[0, 'canvas-5'],
|
||||
true,
|
||||
'c'.repeat(64),
|
||||
[1],
|
||||
]),
|
||||
'clean_editor_image_asset_kind_and_return',
|
||||
);
|
||||
expect(tupleResult).toMatchObject({
|
||||
scope: 'canvas',
|
||||
cleaned_field_count: 4,
|
||||
next_cursor: 'canvas-5',
|
||||
batch_sha256: 'c'.repeat(64),
|
||||
error_message: null,
|
||||
});
|
||||
|
||||
const objectResult = parseProcedureResult(
|
||||
JSON.stringify({
|
||||
ok: true,
|
||||
scope: 'asset',
|
||||
dry_run: true,
|
||||
scanned_count: 1,
|
||||
matched_count: 1,
|
||||
updated_count: 0,
|
||||
cleaned_field_count: 1,
|
||||
blocker_count: 0,
|
||||
blocker_samples: [],
|
||||
next_cursor: null,
|
||||
has_more: false,
|
||||
batch_sha_256: 'd'.repeat(64),
|
||||
error_message: null,
|
||||
}),
|
||||
'clean_editor_image_asset_kind_and_return',
|
||||
);
|
||||
expect(objectResult.batch_sha256).toBe('d'.repeat(64));
|
||||
expect(objectResult).not.toHaveProperty('batch_sha_256');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -172,6 +172,7 @@ pub mod chapter_progression_type;
|
||||
pub mod claim_external_generation_jobs_and_return_procedure;
|
||||
pub mod claim_profile_recharge_order_expiration_schedule_and_return_procedure;
|
||||
pub mod claim_profile_task_reward_and_return_procedure;
|
||||
pub mod clean_editor_image_asset_kind_and_return_procedure;
|
||||
pub mod clear_database_migration_import_chunks_procedure;
|
||||
pub mod close_profile_recharge_order_and_return_procedure;
|
||||
pub mod combat_outcome_type;
|
||||
@@ -308,6 +309,8 @@ pub mod editor_generation_runtime_identity_rotation_table;
|
||||
pub mod editor_generation_runtime_identity_rotation_type;
|
||||
pub mod editor_generation_target_preflight_input_type;
|
||||
pub mod editor_generation_target_preflight_result_type;
|
||||
pub mod editor_image_asset_kind_cleanup_input_type;
|
||||
pub mod editor_image_asset_kind_cleanup_procedure_result_type;
|
||||
pub mod editor_pixel_art_canvas_completion_input_type;
|
||||
pub mod editor_pixel_art_canvas_placeholder_input_type;
|
||||
pub mod editor_pixel_art_result_persist_input_type;
|
||||
@@ -1021,6 +1024,7 @@ pub use chapter_progression_type::ChapterProgression;
|
||||
pub use claim_external_generation_jobs_and_return_procedure::claim_external_generation_jobs_and_return;
|
||||
pub use claim_profile_recharge_order_expiration_schedule_and_return_procedure::claim_profile_recharge_order_expiration_schedule_and_return;
|
||||
pub use claim_profile_task_reward_and_return_procedure::claim_profile_task_reward_and_return;
|
||||
pub use clean_editor_image_asset_kind_and_return_procedure::clean_editor_image_asset_kind_and_return;
|
||||
pub use clear_database_migration_import_chunks_procedure::clear_database_migration_import_chunks;
|
||||
pub use close_profile_recharge_order_and_return_procedure::close_profile_recharge_order_and_return;
|
||||
pub use combat_outcome_type::CombatOutcome;
|
||||
@@ -1157,6 +1161,8 @@ pub use editor_generation_runtime_identity_rotation_table::*;
|
||||
pub use editor_generation_runtime_identity_rotation_type::EditorGenerationRuntimeIdentityRotation;
|
||||
pub use editor_generation_target_preflight_input_type::EditorGenerationTargetPreflightInput;
|
||||
pub use editor_generation_target_preflight_result_type::EditorGenerationTargetPreflightResult;
|
||||
pub use editor_image_asset_kind_cleanup_input_type::EditorImageAssetKindCleanupInput;
|
||||
pub use editor_image_asset_kind_cleanup_procedure_result_type::EditorImageAssetKindCleanupProcedureResult;
|
||||
pub use editor_pixel_art_canvas_completion_input_type::EditorPixelArtCanvasCompletionInput;
|
||||
pub use editor_pixel_art_canvas_placeholder_input_type::EditorPixelArtCanvasPlaceholderInput;
|
||||
pub use editor_pixel_art_result_persist_input_type::EditorPixelArtResultPersistInput;
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::editor_image_asset_kind_cleanup_input_type::EditorImageAssetKindCleanupInput;
|
||||
use super::editor_image_asset_kind_cleanup_procedure_result_type::EditorImageAssetKindCleanupProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct CleanEditorImageAssetKindAndReturnArgs {
|
||||
pub input: EditorImageAssetKindCleanupInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for CleanEditorImageAssetKindAndReturnArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `clean_editor_image_asset_kind_and_return`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait clean_editor_image_asset_kind_and_return {
|
||||
fn clean_editor_image_asset_kind_and_return(&self, input: EditorImageAssetKindCleanupInput) {
|
||||
self.clean_editor_image_asset_kind_and_return_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn clean_editor_image_asset_kind_and_return_then(
|
||||
&self,
|
||||
input: EditorImageAssetKindCleanupInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<EditorImageAssetKindCleanupProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl clean_editor_image_asset_kind_and_return for super::RemoteProcedures {
|
||||
fn clean_editor_image_asset_kind_and_return_then(
|
||||
&self,
|
||||
input: EditorImageAssetKindCleanupInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<EditorImageAssetKindCleanupProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, EditorImageAssetKindCleanupProcedureResult>(
|
||||
"clean_editor_image_asset_kind_and_return",
|
||||
CleanEditorImageAssetKindAndReturnArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct EditorImageAssetKindCleanupInput {
|
||||
pub scope: String,
|
||||
pub cursor: Option<String>,
|
||||
pub limit: u32,
|
||||
pub dry_run: bool,
|
||||
pub expected_batch_sha_256: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorImageAssetKindCleanupInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct EditorImageAssetKindCleanupProcedureResult {
|
||||
pub ok: bool,
|
||||
pub scope: String,
|
||||
pub dry_run: bool,
|
||||
pub scanned_count: u32,
|
||||
pub matched_count: u32,
|
||||
pub updated_count: u32,
|
||||
pub cleaned_field_count: u32,
|
||||
pub blocker_count: u32,
|
||||
pub blocker_samples: Vec<String>,
|
||||
pub next_cursor: Option<String>,
|
||||
pub has_more: bool,
|
||||
pub batch_sha_256: String,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorImageAssetKindCleanupProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user