-
-
Notifications
You must be signed in to change notification settings - Fork 332
Add declarative per-node migrations feature #3145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Copilot
wants to merge
5
commits into
main
Choose a base branch
from
copilot/add-declarative-migrations-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
* Initial plan * Fix NOT logic operation requiring unnecessary second input Co-authored-by: joeyballentine <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: joeyballentine <[email protected]>
Co-authored-by: joeyballentine <[email protected]>
Co-authored-by: joeyballentine <[email protected]>
Copilot stopped work on behalf of
joeyballentine due to an error
October 19, 2025 07:08
Member
|
@copilot continue where you left off |
Co-authored-by: joeyballentine <[email protected]>
Contributor
Author
Completed the implementation! Here's what was done: Frontend Migration System (commit 725005b):
Key Features:
Example Usage: @register(
schema_id="chainner:image:my_node",
migrations=[
rename(old="chainner:image:old_name"),
change_inputs(remove=[3], rename={1: 2, 2: 1}),
],
# ... rest of node definition
)
def my_node(...):
passThe system is fully functional and ready for use. Node authors can now add migrations in Python without writing any TypeScript code! |
Copilot
AI
changed the title
[WIP] Add declarative per-node migrations feature
Add declarative per-node migrations feature
Oct 19, 2025
d0e4684 to
5007ca9
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Implements a complete declarative per-node migration system that allows node authors to declare migrations in Python without writing TypeScript code.
Overview
This PR adds a new migration system where migrations are declared directly on nodes in the backend using Python decorators. The frontend automatically applies these migrations when loading save files, eliminating the need for node authors to write TypeScript migration code.
Backend Changes
backend/src/api/migration.pywith migration base classes:RenameMigration- For node schema ID changesChangeInputsMigration- For input modifications (add/remove/rename)ChangeOutputsMigration- For output modifications (remove/rename)NodeDependencyMigration- For cross-node migration dependenciesmigrationsparameter to node registration inapi.pyserver.pyFrontend Changes
src/common/migrations/migrations.d.tssrc/common/migrations/apply-migration.tssrc/common/migrations/topo-sort.tsfor handling migration dependenciessrc/common/migrations.tsandsrc/main/SaveFile.tstests/common/migrations.test.tsdocs/migrations.mdExample Usage
Key Features
Testing
The system is fully functional and ready for use by node authors.
Fixes #1689
Original prompt
This section details on the original issue you should resolve
<issue_title>Declarative per-node migrations</issue_title>
<issue_description>With the new import system and related API changes shaping up, I thought it would be good to share my vision of a new way for nodes to declare migrations.
The problem
Right now, migrations are implemented in the frontend as an every-growing list of JS/TS functions that directly modify save data on load. This system works, but it makes it difficult to implement migrations. Not only do we need to know the exact form of save data, we also need to know JS/TS, which might not be the case for some node/plugin authors.
Many migrations are also pretty similar to each other. Some breaking changes happen relatively frequently and require migrations, but we still need to write the same every time.
Declarative per-node migrations
My solution to this problem are declarative per-node migrations. Migrations are declared on the backend on the specific node that needs the migration, and the frontend is responsible for carrying out the migration.
This system has several advantages:
In code, these migrations would look something like this:
Non-linear history
However, it's no all roses and sunshine. The per-node aspect of this system produces a huge issue: there is no global ordering for migrations.
Right now, migrations have a global ordering, so they form a linear history that we can simply go through. When loading a save file, we read its migration counter, and then apply all migrations after the read counter value. The migration counter is essentially a timestamp, and we use it to figure out which migrations were added after the file was saved.
However, we lose this global ordering with per-node migrations. When 2 nodes have some number of migrations, there is no sense of order between the migrations between those nodes.
This isn't always a problem, though. As long as the per-node migrations only affect and depend on the node that they are declared on, all is good. The nodes and their migrations are completely independent of each other, so we can apply them in order. Global order is only necessary when nodes depend on other nodes for a migration. However, we don't actually need a total global order, we only care about the order of migrations of those 2 nodes. This creates a DAG that describes the dependencies between nodes and migrations. We then simply need to apply a topological sort to figure out the order in which we need to apply migrations.
Example
Suppose we have 2 nodes N and M with migrations n1, n2, n3, m1, m2, and m3, such that
migrations(N) = [n1, n2, n3]andmigrations(M) = [m1, m2, m3].Further, the version of a node is simply its number of migrations. So a node with one migration is v1 and a node with no migrations is v0. Versions are also only whole numbers, so don't think of them as something like semantic versions.
If the migrations of N and M are independent of each other, then we can apply them in any order as long as we keep the per-node migration order. So the dependencies between migrations look like this:
The graph should make it pretty obvious in which order we can apply migrations.
But what if migration m2 needs to create an N node? Well, then m2 would depend on N, but that's only half of the story. Since N might change (via migrations), m2 actually depends on a specific version of N. Specifically, m2 depends on the latest version of N when m2 was added.
Suppose N only had migrations n1 (version 1) when migration m2 was added. So m2 creates a node N v1. But in the next release, we change and remove some inputs from N, which are migrations n2 and n3. m2 would then create an invalid node N (v3) or would have to be updated to account for n2 and n3. Instead, we can simply say that m2 always creates a Node N v1 and simply migrate the created node to the latest version of N.
So migrations not only depend on specific nodes, but specific versions of nodes. In our example, m2 depends on n1 (version 1). Somewhat counterintuitively, this one dependency adds 2 edges to our dependency graph. Since m2 needs the version of N to be exactly v1, m2 must be appl...
Fixes #1689
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.