⬆️ Add simple upgrade/downgrade package for MyST AST#1802
Conversation
🦋 Changeset detectedLatest commit: fcfdf97 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
rowanc1
left a comment
There was a problem hiding this comment.
I think the upgrade downgrade should be on the output of what is written to json in the site.
{
version: 1,
sha: "",
mdast: {},
citations: {order:} ... etc,
}
That way we can execute and move around.
Currently the upgrade/downgrade is on the mdast only (likely the bulk of the changes, but citations were a poor choice to do outside of the tree as an example...!).
| import { downgrade } from './version_2_1.js'; | ||
| import type { Parent } from 'mdast'; | ||
|
|
||
| const SIMPLE_AST: Parent = { |
There was a problem hiding this comment.
I don't think it matters for now (as in we can get this in asap), however, I think this would be easier to manage with separate test dir with json files and then a global test runner.
fwkoch
left a comment
There was a problem hiding this comment.
This is looking good to me - appreciate how it is simple and explicit.
| import { upgrade as upgrade1To2 } from './version_1_2.js'; | ||
|
|
||
| export function upgrade(from: string, to: string, ast: Parent): void { | ||
| if (from === '1' && to === '2') { |
There was a problem hiding this comment.
This means we need to be careful and explicit about bumping version in the myst metadata.
An alternative could be having tests against the content to determine if the upgrade is required (e.g. "if footnoteReference numbering is defined") - that could allow more flexibility to introduce compatibility updates that missed a version bump on the myst side.
I think the explicit version numbering is good, just musing on alternatives.
There was a problem hiding this comment.
Maybe a slightly different thought - do we need/want a minor_version field here? Where we can have a bit more intentionality around what constitutes a major change (outputs) vs something that is pretty minor and can be dealt with with fallbacks in the theme, etc.
There was a problem hiding this comment.
@fwkoch right now we need to do some work to ensure that we're talking about the same "version" (being the schema version), certainly.
I have flipped between thinking about using a minor version vs major version. These kind of changes are hard to categorise. Ultimately, my experience with Packaging leads me to feel that version numbers are not very good at communication compatibility when "compatibility" is not well-defined. And for MyST's AST schema, I think that's the case (it's used in so many different ways with differing assumptions).
The angle I've been working of late is to prefer handling compatibility explicitly by downgrading/upgrading, rather than implicitly encoding compatibility in the version number semantics.
Do either of you see a case where this might be a problem?
There was a problem hiding this comment.
I agree that for the schema, major/minor versioning are less useful. Any change to the schema could be breaking for some client I think. So then a minor bump would only mean something more like "this change isn't breaking for us"
It gets extra fuzzy with how we've used myst-spec-ext - because we have that package, the entire MyST development has happened with a single, unchanged version of myst-spec despite the AST extending and evolving. Even further, the site JSON Rowan mentioned in his review is defined by myst-cli only. So - does the upgrade/downgrade logic consider all those aspects and extended fields, but only is able to apply when the spec version changes?
There was a problem hiding this comment.
@fwkoch yes, this reminds me why I originally scoped the package to the AST; we don't export a promise about our page types.
Here's my view of where we go.
- At some point in future, we move to 🔄 Redefine schema in terms of
@types/mdastmyst-spec#67 - We add types for
$.jsonfiles tomyst-spec myst-spec-extbecomes a shim that exports frommyst-spec- We define a single version number for
myst-specthat changes whenever we make an AST or auxiliary change. - We define a breaking change to be one that removes fields or changes types (in this case, "change" excludes "new fields").
This means that the MyST "spec" version is not just the MyST AST. I think that is probably a good thing, because the AST and the auxiliary state are intimately connected. This is especially pertinent now that MyST can consume .myst.json files generated by other tools.
Consequently, we have the myst.xref.json and myst.search.json files which are separately versioned.
In the meantime, we could redefine the interface of this package to be
interface File {
mdast: Parent,
};
export function upgrade(file: File, from: Version, to: Version) {
}This means that at some point we can grow the File interface.
@rowanc1 @fwkoch do you have any feelings about this approach?
There was a problem hiding this comment.
We can not strongly type the interface. The file type will change over time, so that upgrade File type will also change (e.g. citations is in there now, and won't be in the future). The types are known after the version is known, and then we can cast to the right type, which can be File_v1 in this package, or something.
For the options I would prefer them in a dict, so that we can add to it over time { throwError: false, inPlace: true } etc. The upgrade options, should ideally be optional, and then the function should just upgrade to the latest.
upgrade(file);
upgrade(file, { from: 1, to: 15 });
The upgrade process should stamp the version into the file if it isn't there (where we are right now, which is 1).
The mdast version and the file version should be the same, and I agree that myst.xref.json and myst.search.json have different version numbers.
There was a problem hiding this comment.
I think we can strongly type the interface; we already do that here for the AST. The idea is not to exhaustively type the AST, though; only the types that you actually need to work with during the upgrade step.
The upgrade options, should ideally be optional, and then the function should just upgrade to the latest.
Yes, I think if we add the proper version field, we can do-away with the from version arg. We'll still want to if the version consuming the package is older than the latest, though.
I've been toying with the same idea too. Doing that would require that "version" here refers to both the AST version and the version of the page data. We probably can tie the two together. |
| @@ -0,0 +1,17 @@ | |||
| import type { IFile } from '../types/index.js'; | |||
There was a problem hiding this comment.
The idea here is that we accept unvalidated inputs, and enforce strict types inside the upgrade/downgrade routines
| export interface IFile { | ||
| version: any; | ||
| mdast: any; | ||
| } |
There was a problem hiding this comment.
The generic "untyped input" interface.
packages/myst-compat/src/types/v1.ts
Outdated
| export interface IFile { | ||
| version: '1'; | ||
| mdast: any; | ||
| } |
There was a problem hiding this comment.
A "more typed" per-version interface.
I don't imagine that we'll actually type mdast — this would require bundling the entire MyST JSON spec with each iteration, which is probably overkill. Ultimately, we're still mostly going to need to just tell the type system that what we did was allowed. Let's not drown in types along the way.
There was a problem hiding this comment.
Agree that we don't need to do strong typing here at all.
|
I will take a pass through this and get it merged @agoose77. |
21e9b9c to
fcfdf97
Compare
| import type { Migration } from './types.js'; | ||
| import * as v1 from './v1_footnotes.js'; | ||
|
|
||
| export const MIGRATIONS: Migration[] = [v1]; |
There was a problem hiding this comment.
This types each module for an upgrade/downgrade/description, and we then just put it in an array.
|
I have merged a few of the files together, keeping the upgrade/downgrade/types all in one file. I have also added a description and ensured that this will work with chains of migrations (0-->5, etc.). In discussion with @agoose77 I have changed the spec to a number. I also put the tests in their own folder and changed to |
This PR adds a simple package that sets an API for upgrading and downgrading an AST. Over time, we can use this to define imperative motions between AST schema versions.
For now, this PR implements a new "version 2" of the MyST schema which drops the use of
numberin favour ofenumeratorin footnotes.Next Steps
mystmd