mcpField
M
migrating-motoko

migrating-motoko

github.com/dfinity开发3977 次安装今天更新
代码仓库

关于 migrating-motoko

migrating-motoko 是用于 Motoko Actor 升级时迁移状态的技能,面向需要调整持久化字段或数据结构的开发者。它通过附加到 Actor 的迁移表达式处理显式转换,并说明可兼容变更的隐式迁移情形。

主要功能

  1. 01区分隐式与显式迁移需求
  2. 02通过迁移函数转换 Actor 状态
  3. 03提供字段重命名与类型转换模式
  4. 04说明迁移函数的输入输出规则
  5. 05列出构建与检查步骤

适用场景

  1. 01升级时重命名持久化字段
  2. 02变更字段类型或状态结构
  3. 03转换集合中的数据
  4. 04为新增字段设置默认值

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dfinity/icskills --skill migrating-motoko

安装后在 Claude Code 中输入/migrating-motoko确认已加载。

详细介绍

内联 Actor 迁移

通过附加到 Actor 的迁移表达式,在 canister 升级时迁移 Actor 状态。每次升级最多只能有一个迁移函数。

如需使用 migrations/ 目录进行多次迁移,请改为加载 migrating-motoko-enhanced。

何时使用

隐式迁移(无需编写代码)

新程序与旧程序兼容时,运行时允许升级:

  • 添加 Actor 字段
  • 移除 Actor 字段
  • 更改可变性(var ↔ let)
  • 添加变体构造器
  • 扩宽类型(Nat → Int)
需要显式迁移
  • 重命名字段
  • 更改字段类型(例如,Bool → 变体、Int → Float)
  • 重构状态(拆分/合并字段)
  • 转换集合值

语法

紧邻 Actor 之前的括号表达式:

import Migration "migration";

(with migration = Migration.run)
actor {
  var newState : Float = 0.0;
};

或内联:

import Int "mo:core/Int";

(with migration = func(old : { var state : Int }) : { var newState : Float } {
  { var newState = old.state.toFloat() }
})
actor {
  var newState : Float = 0.0;
};

或者,当导入的模块导出 migration 字段时,使用简写形式:

import { migration } "migration";

(with migration)
actor { ... };

迁移函数规则

  • 类型:func (old : { ... }) : { ... } — 必须是本地、非泛型函数,且两个记录都必须使用可持久化类型(不能包含函数或可变数组)
  • 定义域:旧 Actor 字段(上一版本中的名称和类型)
  • 陪域:新 Actor 字段(必须存在于新 Actor 中,且类型兼容)
  • 仅在升级时运行——全新安装时,初始化器会正常运行
  • 如果迁移触发陷阱,升级会中止,canister 保留旧版本
字段语义
字段出现位置效果
输入和输出转换该字段
仅输出由迁移生成新字段
仅输入使用该字段(编译器会警告可能的数据丢失)
两者都不保留该字段或通过声明初始化

迁移模块模式

将迁移放在单独的模块中。直接内联定义旧类型——不要从旧代码路径导入:

// migration.mo
import Types "types";
import Map "mo:core/Map";

module {
  type OldTask = { id : Nat; title : Text; completed : Bool };

  type OldActor = {
    var tasks : Map.Map<Nat, OldTask>;
    var nextId : Nat;
  };

  type NewActor = {
    var tasks : Map.Map<Nat, Types.Task>;
    var nextId : Nat;
  };

  public func run(old : OldActor) : NewActor {
    let tasks = old.tasks.map<Nat, OldTask, Types.Task>(
      func(_, task) {
        {
          id = task.id;
          title = task.title;
          due = 0;
          var status = if (task.completed) #completed else #pending;
        }
      }
    );
    { var tasks; var nextId = old.nextId };
  };
};
// main.mo
import Map "mo:core/Map";
import Types "types";
import Migration "migration";

(with migration = Migration.run)
actor {
  var tasks = Map.empty<Nat, Types.Task>();
  var nextId : Nat = 0;
};

字段必须有初始化器——迁移函数仅在升级时运行。全新安装时会使用初始化器。

常见模式

添加带默认值的字段
old.users.map<Nat, OldUser, NewUser>(
  func(_, u) { { u with zipCode = "" } }
)
添加可选字段
{ task with var assignee = null : ?Principal }
将 Bool 转为变体
var status = if (task.completed) #completed else #pending;
重命名字段

使用旧名称,并生成新名称:

func(old : { var state : Int }) : { var value : Int } {
  { var value = old.state }
}
删除字段

在输入中使用该字段,在输出中省略。编译器会发出警告——请确认这是有意的数据丢失。

检查清单

  • 决定使用隐式迁移(兼容性变更)还是显式迁移(迁移函数)
  • 如果使用显式迁移:在 migration.mo 中内联定义旧类型
  • 迁移类型:func (old : RecordIn) : RecordOut,且使用可持久化类型
  • 在 Actor 之前附加 (with migration = Migration.run)
  • 不要使用 preupgrade/postupgrade 迁移数据
  • 使用 mops check --fix 和 mops build 验证

更多参考

  • 加载 motoko,查看 Motoko 语言通用参考和 mo:core API
  • 加载 migrating-motoko-enhanced,使用 --enhanced-migration 进行多次迁移
  • 加载 mops-cli,了解 mops check、mops build 和工具链设置

评论

支持 Markdown