Skip to content

Examples

Below several examples of migrations are offered.

Introduce a new protocol property

Consider the following protocol.

protocol[a] Before(var field1: Text) {};

Property field2 of type Number must be added. First, the original definition is replaced by the following definition.

protocol[a] After(var field1: Text, var field2: Number) {};

This new field is populated with the value in the following migration.

migration("add property")
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Before",
        targetTypeId = "/app-2.0.0?/example/After",
    ) {
        put("field2") {
            NumberValue(2)
        }
    },

Introduce a new protocol party

Consider the following protocol.

protocol[p1] Foo() { };

Party p2 must be added. First, the original definition is replaced by the following definition.

protocol[p1, p2] Foo() { };

This new party is added in the following migration.

migration("introduce a party")
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Foo",
        targetTypeId = "/app-2.0.0?/example/Foo",
    ) {
        parties(parties.single(), createParty(p2))
    },

Replace an existing protocol property value

Consider the following protocol.

protocol[a] Foo(var n: Text) {};

Property n must be changed to some other value. This is accomplished in the following migration:

migration("replace value")
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Foo",
        targetTypeId = "/app-2.0.0?/example/Foo",
    ) {
        replace<TextValue>("n") {
            createText("two")
        }
    },

Replace an existing protocol symbol value

Consider the following symbol and protocol definition.

symbol chf;
protocol[p] Foo(var u: chf) {};

If the current value of u is 0, then the value of u must be changed to 20. That is accomplished in the following migration:

migration("replace symbol value")
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Foo",
        targetTypeId = "/app-2.0.0?/example/Foo",
    ) {
        if (get<SymbolValue>("u") ==
            SymbolValue(NumberValue(0), NameType("chf"))
        ) {
            replace<SymbolValue>("u") {
                createSymbolValue(20, "chf")
            }
        }
    },

Introduce an identifier property

Consider the following struct and protocol definition.

struct FavoriteStructId { id: Number };

protocol[owner] Before(firstItem: Text) {
    var favorites: Map<FavoriteStructId, Text> = mapOf<FavoriteStructId, Text>()
        .with(FavoriteStructId(1), firstItem);

    permission[owner] addTitle(title: Text) { /* ... */ }
};

You would like to replace the user-defined struct definition with an identifier.

identifier FavoriteId;

protocol[owner] After() {
    var favorites: Map<FavoriteId, Text> = mapOf<FavoriteId, Text>();

    permission[owner] addTitle(title: Text) { /* ... */  }
};

This is accomplished in the following migration:

migration("transform user-defined value to a identifier value")
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Before",
        targetTypeId = "/app-2.0.0?/example/After",
    ) {
        replace<MapValue>("favorites") { oldMap -> // map of Pair<Number, Text>
            val mapWithTransformedKeys = oldMap.value.mapKeys {
                createIdentifier("/app-2.0.0?/example/FavoriteId")
            }
            createMap(LinkedHashMap(mapWithTransformedKeys))
        }
    },

Delete an existing protocol property

Consider the following protocol.

protocol[a] Before(var x: Number) {};

Property x must be removed. First, the existing definition is replaced by the following definition.

protocol[a] After() {};

This field is removed in the following migration.

migration("remove property")
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Before",
        targetTypeId = "/app-2.0.0?/example/After",
    ) {
        delete("x")
    },

Collecting data using read

Reading values

Consider the following code.

protocol[a] Tiny(var n: Number) {};

We can collect the values of n from all instances of the protocol in the following migration.

val collectedNumbers = mutableListOf<NumberValue>()
migration("read and write")
    .read("/app-1.0.0?/example/Tiny") {
        collectedNumbers += get<NumberValue>("n")
    }

collectedNumbers can then be used in transformations within the same migration.

Reading unions

Consider the following code.

union U { Text, Number };
protocol[p] Foo(var u: U) {};

The values u need to be collected in a step prior to an actual migration. The following migration accomplishes this.

val texts = mutableListOf<TextValue>()
val numbers = mutableListOf<NumberValue>()
migration("read union")
    .read("/app-1.0.0?/example/Foo") {
        when (val v = get<Value>("u")) {
            is TextValue -> texts.add(v)
            is NumberValue -> numbers.add(v)
            else -> throw RuntimeException("Expected union value ${v::class}")
        }
    },

Note that if these do not need to be organized by type, they could also be stored in a List<Value> without the use of a match-statement.

Creating new protocol instances using existing protocol data

Consider the following protocol.

protocol[p] Baz(var x: Text) { };

A new protocol Qux must be created that uses a value from Baz.

protocol[p] Qux(var x: Text) { };

The following migration emits a new protocol Qux, while Baz continues to exist.

migration("create protocol")
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Baz",
        targetTypeId = "/app-2.0.0?/example/Baz",
    ) {
        val x = get<TextValue>("x")
        createProtocol("/app-2.0.0?/example/Qux", parties, listOf(x))
    },

Replacing protocol instances using existing protocol data

Consider the following protocol.

protocol[p] Bar(var field1: Text) { };

It is to be replaced by a new protocol Foo that has the same value(s) as Bar.

protocol[p] Foo(var field1: Text) { };

The following migration emits a new protocol Foo, while Bar ceases to exist. Foo therefore gets Bar's identifier.

migration("migrate protocol")
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Bar",
        targetTypeId = "/app-2.0.0?/example/Foo",
    ),

The operation of transformProtocol and its various modes of operation are discussed in more detail here.

Split a protocol

Consider the following protocol.

protocol[p] Foo(var field1: Text, var field2: Number) { };

Two new protocols need to replace this protocol: Bar, which gets Foo's identity, and Baz, which is a new protocol altogether.

protocol[p] Bar(var field1: Text) { };

protocol[p] Baz(var field2: Number) { };

The following migration replaces Foo with Bar, and also creates a new protocol Baz.

migration("split protocol")
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Foo",
        targetTypeId = "/app-2.0.0?/example/Bar",
    ) {
        val field2 = get<NumberValue>("field2")
        createProtocol("/app-2.0.0?/example/Baz", parties, listOf(field2))
        delete("field2")
    },

A migration with multiple changes

Consider the following protocol.

identifier KeyId;

protocol[p] Foo(var id: Number, var beta: Number) {
    initial state red;
    state green;

    var gamma: Text = "gamma";
    var keyId: KeyId = KeyId();
};

The quite different protocol below is intended to replace it.

identifier Id;

protocol[p] Foo(var beta: Text) {
    initial state wind;
    state fire;

    var zeta: Number = -1;
    var id: Id = Id();
};

Note the use of replace, delete, put, get and state within one migration.

migration("multiple transformations")
    // rename identifier type
    .transformIdentifier(
        currentTypeId = "/app-1.0.0?/example/KeyId",
        targetTypeId = "/app-2.0.0?/example/Id",
    )
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Foo",
        targetTypeId = "/app-2.0.0?/example/Foo",
    ) {
        // `beta` is changed into a string.
        replace<NumberValue>("beta") { oldBeta ->
            createText(oldBeta.value.toString())
        }

        // `gamma` is deleted and `zeta` is added (keep the old `id` Number value)
        delete("gamma")
        put("zeta") { get<NumberValue>("id") }

        // rename identifier field from `keyId` to `id` (keep the old `keyId` identifier value)
        put("id") {
            withTransformations(get<IdentifierValue>("keyId")) { it }
        }
        delete("keyId")

        // map the states
        state("red" to "wind", "green" to "fire")
    },

Using withTransformations

Consider the following definitions.

struct Bar1 { n: Number };

protocol[p] Foo1() {
    var contents: List<Bar1> = listOf<Bar1>(Bar1(n = 2));
};

The following definitions need to replace the existing definitions. Note how not only does Bar1 change to Bar2, but also turns from a List into a Set.

struct Bar2 { n: Number };

protocol[p] Foo2() {
    var contents: Set<Bar2> = setOf<Bar2>(); // this is now a Set

    permission[p] c() returns Number {
        return 42;
    };
};

The following migration accomplishes this:

migration("nested structs")
    .transformStruct(
        currentTypeId = "/app-1.0.0?/example/Bar1",
        targetTypeId = "/app-2.0.0?/example/Bar2",
    )
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Foo1",
        targetTypeId = "/app-2.0.0?/example/Foo2",
    ) {
        replace<ListValue>("contents") { old ->
            // Change List to Set, and invoke the
            // registered transformation for the struct
            withTransformations(old) { transformed ->
                // Due to withTransformations, 'transformed' is now
                // a List of /app-2.0.0?/example/Bar.
                // It does not know how to change the List to a Set.
                createSet(transformed.value, type("/app-2.0.0?/example/Bar2"))
            }
        }
    },

Note how type("/app-2.0.0?/example/Bar2") is used to retrieve the type reference of the Bar2 struct, which is a user-defined type.

Using type

Consider the following protocol:

protocol[p] Before(var fooBar: List<FooBar>) {};

The protocol makes use of a user-defined type FooBar. FooBar is defined as a struct containing a Boolean and a Number. There are also a couple of other user-defined types.

struct FooBar { foo: Boolean, bar: Number };

union Version { Number, Text };
struct VersionedFooBar { fooBar: FooBar, version: Version };

We now want our protocol to use versioned FooBars (Version being either a version Number or Text in this example). We also want it to take another parameter, which is a list of Versions:

protocol[p] After(var fooBar: List<VersionedFooBar>, var baz: List<Version>) {};

The following migration transforms Before into After so that it uses VersionedFooBar rather than FooBar. It also adds our new parameter (and populates the new field with a couple of bonus Version values).

Notice especially how we use the type function here to resolve the user-defined types. In the case of baz, this is used to explicitly state that the elements of the list have a union type. Notice also how we do not need to provide createList with a type -- if we leave it out, it can be inferred. As a rule of thumb this can be done whenever we're not dealing with elements that have union types -- but if we are, it is better to specify the type in order to avoid unforeseen consequences.

migration("type example")
    .transformStruct(
        currentTypeId = "/app-1.0.0?/example/FooBar",
        targetTypeId = "/app-2.0.0?/example/FooBar",
    )
    .transformProtocol(
        currentTypeId = "/app-1.0.0?/example/Before",
        targetTypeId = "/app-2.0.0?/example/After",
    ) {
        replace<ListValue>("fooBar") { oldList ->
            createList(
                oldList.value.map { oldFooBar ->
                    withTransformations(oldFooBar) { transformedFooBar ->
                        createStruct(
                            structTypeId = "/app-2.0.0?/example/VersionedFooBar",
                            values = mapOf(
                                "fooBar" to transformedFooBar,
                                "version" to createText("NONE"),
                            ),
                        )
                    }
                },
            )
        }
        put("baz") {
            createList(
                listOf(createText("RELEASE"), createNumber(1)),
                type("/app-2.0.0?/example/Version"),
            )
        }
    },