migrations/README.md
This is a prop type migration system, not a general data migration system.
Trigger: Migrations run when it finds a mismatch between data prop type, and actual schema (code) (e.g., string → string-v2). Without a type change, there's no trigger and no migration.
Scope: Having mismatch of type (e.g., string → string-v2) will find all instances of mismatch, and send the object to run migration script on.
See Migration Scope for details.
Manifest describes the different migrations, it can contain widget key migrations and prop-type migrations
{
"widgetKeys": {
"e-logo": [
{ "from": "svg", "to": "icon" }
]
},
"propTypes": {
"string-to-html": {
"fromType": "string",
"toType": "html",
"url": "string-to-html.json"
}
}
}
Prop type migrations are a set of operations, up for upgrade and reverse down for downgrade.
Prop type migrations support wildcard paths and conditions (see below)
{
"up": [
{
"op": {
"fn": "set",
"path": "$$type",
"value": "html"
}
}
],
"down": [
{
"op": {
"fn": "set",
"path": "$$type",
"value": "string"
}
}
]
}
Path parameter works with wildcard, starting from the root of the prop object.
Important: For prop type migrations (the primary use case), paths always start at the prop root—not the widget or document root. For example:
$$type refers to the type field at the prop rootvalue.nested refers to propObject.value.nestedvalue.* or value.items[*] match children within the propWidget key migrations (rare) start at the widget/element root instead. See Migration Scope for details.
Conditions check whether to run the migration or not, with many helper functions such as exists, conditions can be compounded by AND and OR.
Full list can be found here
Migrations do NOT support value transformations. Migrations are purely structural - they create or update, but can't activate any functions on the data.
Handle transformations in transformer code
{ "color": "#fff" } → { "color": { "$$type": "color", "value": "#fff" } }{ "oldColor": "#fff", "newColor": {} } -> { "oldColor": "#fff", "newColor": { "gradient": "something", "value": "#fff" }}Example widget:
{
"id": "heading-1",
"elType": "widget",
"widgetType": "e-heading",
"settings": {
"title": {
"$$type": "string",
"value": "Hello"
},
"tag": {
"$$type": "string",
"value": "h2"
}
}
}
Prop Type Migration (e.g., title migrating from string → string-v2):
{
"$$type": "string",
"value": "Hello"
}
$$type, valueWidget Key Migration (e.g., renaming tag → htmlTag):
tag, title.valueset creates or updates data, it can update key / value or both. Full Documentation here
Params:
Replaces nested key and value
{ "op": { "fn": "set", "path": "value.*.nested", "value": ["a"], "key": "nested2" } }
Appends to array
{ "op": { "fn": "set", "path": "value.*.nested.[]", "value": "a" } }
Creates empty object at path
{ "op": { "fn": "set", "path": "value.*.nested.[*]" } }
delete removes keys/values at specified path. Full Documentation here
Params:
Delete specific nested key
{ "op": { "fn": "delete", "path": "value.deprecated" } }
Delete all matching wildcard paths
{ "op": { "fn": "delete", "path": "value.items[*].legacy" } }
Delete without cleaning empty parents
{ "op": { "fn": "delete", "path": "value.old", "clean": false } }
move relocates values from one path to another. Full Documentation here
Params:
Move simple value to new location
{ "op": { "fn": "move", "src": "value.oldField", "dest": "value.nested.newField" } }
Move without cleaning source
{ "op": { "fn": "move", "src": "value.data", "dest": "value.backup", "clean": false } }
Move nested object structure
{ "op": { "fn": "move", "src": "value.settings", "dest": "value.config.settings" } }
Context: Rename a widget settings key from tag → htmlTag. Paths start at widget settings root.
//manifest.json
{
"widgetKeys": {
"e-heading": [
{ "from": "tag", "to": "htmlTag" }
],
"propTypes": {}
}
Before:
{
"id": "heading-1",
"elType": "widget",
"widgetType": "e-heading",
"settings": {
"tag": {
"$$type": "string",
"value": "h2"
},
"title": {
"$$type": "string",
"value": "Hello"
}
}
}
After:
{
"id": "heading-1",
"elType": "widget",
"widgetType": "e-heading",
"settings": {
"htmlTag": {
"$$type": "string",
"value": "h2"
},
"title": {
"$$type": "string",
"value": "Hello"
}
}
}
Context: Migrate string → html type. Paths start at prop root.
{
"up": [
{ "op": { "fn": "set", "path": "$$type", "value": "html" } }
],
"down": [
{ "op": { "fn": "set", "path": "$$type", "value": "string" } }
]
}
Before: { "$$type": "string", "value": "Hello" }
After: { "$$type": "html", "value": "Hello" }
Context: Rename keys using key parameter, with and without wildcards. Paths start at prop root.
Simple key rename:
{
"up": [
{ "op": { "fn": "set", "path": "value.oldName", "key": "newName" } }
],
"down": [
{ "op": { "fn": "set", "path": "value.newName", "key": "oldName" } }
]
}
Wildcard key rename across multiple objects:
{
"up": [
{
"op": { "fn": "set", "path": "value.*.oldName", "key": "newName" },
"condition": { "fn": "exists", "path": "value.*.oldName" }
}
],
"down": [
{
"op": { "fn": "set", "path": "value.*.newName", "key": "oldName" },
"condition": { "fn": "exists", "path": "value.*.newName" }
}
]
}
Before (wildcard example):
{
"$$type": "responsive",
"value": {
"desktop": { "oldName": "value1" },
"tablet": { "oldName": "value2" },
"mobile": { "oldName": "value3" }
}
}
After:
{
"$$type": "responsive",
"value": {
"desktop": { "newName": "value1" },
"tablet": { "newName": "value2" },
"mobile": { "newName": "value3" }
}
}
Context: Update all color types in a gradient using array wildcards [*]. Paths start at prop root.
{
"up": [
{
"op": { "fn": "set", "path": "value.stops[*].color.$$type", "value": "color" },
"condition": { "fn": "equals", "path": "value.stops[*].color.$$type", "value": "string" }
}
],
"down": [
{
"op": { "fn": "set", "path": "value.stops[*].color.$$type", "value": "string" },
"condition": { "fn": "equals", "path": "value.stops[*].color.$$type", "value": "color" }
}
]
}
Before:
{
"$$type": "gradient",
"value": {
"stops": [
{ "position": 0, "color": { "$$type": "string", "value": "#ff0000" } },
{ "position": 50, "color": { "$$type": "string", "value": "#00ff00" } },
{ "position": 100, "color": { "$$type": "string", "value": "#0000ff" } }
]
}
}
After:
{
"$$type": "gradient",
"value": {
"stops": [
{ "position": 0, "color": { "$$type": "color", "value": "#ff0000" } },
{ "position": 50, "color": { "$$type": "color", "value": "#00ff00" } },
{ "position": 100, "color": { "$$type": "color", "value": "#0000ff" } }
]
}
}
Context: Use and/or conditions to selectively migrate items. Paths start at prop root.
{
"up": [
{
"op": { "fn": "set", "path": "value.items[*].type", "value": "enhanced" },
"condition": {
"fn": "and",
"conditions": [
{ "fn": "equals", "path": "value.items[*].type", "value": "legacy" },
{ "fn": "exists", "path": "value.items[*].data" }
]
}
},
{
"op": { "fn": "set", "path": "value.items[*].migrated", "value": true },
"condition": {
"fn": "or",
"conditions": [
{ "fn": "equals", "path": "value.items[*].type", "value": "enhanced" },
{ "fn": "not_exists", "path": "value.items[*].migrated" }
]
}
}
],
"down": [
{
"op": { "fn": "set", "path": "value.items[*].type", "value": "legacy" },
"condition": { "fn": "equals", "path": "value.items[*].type", "value": "enhanced" }
},
{
"op": { "fn": "delete", "path": "value.items[*].migrated" }
}
]
}
Before:
{
"$$type": "list",
"value": {
"items": [
{ "type": "legacy", "data": { "content": "Item 1" } },
{ "type": "legacy", "data": { "content": "Item 2" } },
{ "type": "new", "data": { "content": "Item 3" } }
]
}
}
After:
{
"$$type": "list",
"value": {
"items": [
{ "type": "enhanced", "data": { "content": "Item 1" } },
{ "type": "enhanced", "data": { "content": "Item 2" } },
{ "type": "new", "data": { "content": "Item 3" } }
]
}
}