-
Notifications
You must be signed in to change notification settings - Fork 558
FieldKind cleanup #25869
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
base: main
Are you sure you want to change the base?
FieldKind cleanup #25869
Changes from 1 commit
dc27e2a
99c8c89
f5a7bfd
996ac36
07db7b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,7 +59,15 @@ export const noChangeHandler: FieldChangeHandler<0> = { | |
| getCrossFieldKeys: () => [], | ||
| }; | ||
|
|
||
| export interface ValueFieldEditor extends FieldEditor<OptionalChangeset> { | ||
| /** | ||
| * {@link FieldEditor} for required fields (always contain exactly 1 child). | ||
| * @remarks | ||
| * This shared code with optional fields, since they are the same edit wise except setting to empty is not allowed, | ||
| * and the content is always assumed to not be empty. | ||
| * This means the actual edits implemented for optional fields are sufficient to support required fields | ||
| * which is why this is defined and implemented in terms of optional fields. | ||
| */ | ||
| export interface RequiredFieldEditor extends FieldEditor<OptionalChangeset> { | ||
| /** | ||
| * Creates a change which replaces the current value of the field with `newValue`. | ||
| * @param ids - The ids for the fill and detach fields. | ||
|
|
@@ -68,42 +76,41 @@ export interface ValueFieldEditor extends FieldEditor<OptionalChangeset> { | |
| } | ||
|
|
||
| const optionalIdentifier = brandConst("Optional")<FieldKindIdentifier>(); | ||
|
|
||
| /** | ||
| * 0 or 1 items. | ||
| */ | ||
| export const optional = new FlexFieldKind( | ||
| optionalIdentifier, | ||
| Multiplicity.Optional, | ||
| optionalChangeHandler, | ||
| (types, other) => | ||
| export const optional = new FlexFieldKind(optionalIdentifier, Multiplicity.Optional, { | ||
| changeHandler: optionalChangeHandler, | ||
| allowsTreeSupersetOf: (types, other) => | ||
| (other.kind === sequence.identifier || other.kind === optionalIdentifier) && | ||
| allowsTreeSchemaIdentifierSuperset(types, other.types), | ||
| new Set([]), | ||
| ); | ||
| }); | ||
|
|
||
| export const valueFieldEditor: ValueFieldEditor = { | ||
| export const requiredFieldEditor: RequiredFieldEditor = { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rename from "value" to "required" was done long ago, but not everything was renamed. The field kind identifier has to stay, but these kinds of things can be updated to make things more consistant. |
||
| ...optionalFieldEditor, | ||
| set: (ids: { | ||
| fill: ChangeAtomId; | ||
| detach: ChangeAtomId; | ||
| }): OptionalChangeset => optionalFieldEditor.set(false, ids), | ||
| }; | ||
|
|
||
| export const valueChangeHandler: FieldChangeHandler<OptionalChangeset, ValueFieldEditor> = { | ||
| ...optional.changeHandler, | ||
| editor: valueFieldEditor, | ||
| export const requiredFieldChangeHandler: FieldChangeHandler< | ||
| OptionalChangeset, | ||
| RequiredFieldEditor | ||
| > = { | ||
| ...optionalChangeHandler, | ||
| editor: requiredFieldEditor, | ||
| }; | ||
|
|
||
| const requiredIdentifier = brandConst("Value")<FieldKindIdentifier>(); | ||
|
|
||
| /** | ||
| * Exactly one item. | ||
| */ | ||
| export const required = new FlexFieldKind( | ||
| requiredIdentifier, | ||
| Multiplicity.Single, | ||
| valueChangeHandler, | ||
| (types, other) => | ||
| export const required = new FlexFieldKind(requiredIdentifier, Multiplicity.Single, { | ||
| changeHandler: requiredFieldChangeHandler, | ||
| allowsTreeSupersetOf: (types, other) => | ||
| // By omitting Identifier here, | ||
| // this is making a policy choice that a schema upgrade cannot be done from required to identifier. | ||
| // Since an identifier can be upgraded into a required field, | ||
|
|
@@ -113,43 +120,35 @@ export const required = new FlexFieldKind( | |
| other.kind === requiredIdentifier || | ||
| other.kind === optional.identifier) && | ||
| allowsTreeSchemaIdentifierSuperset(types, other.types), | ||
| new Set(), | ||
| ); | ||
| }); | ||
|
|
||
| const sequenceIdentifier = brandConst("Sequence")<FieldKindIdentifier>(); | ||
|
|
||
| /** | ||
| * 0 or more items. | ||
| */ | ||
| export const sequence = new FlexFieldKind( | ||
| sequenceIdentifier, | ||
| Multiplicity.Sequence, | ||
| sequenceFieldChangeHandler, | ||
| (types, other) => | ||
| export const sequence = new FlexFieldKind(sequenceIdentifier, Multiplicity.Sequence, { | ||
| changeHandler: sequenceFieldChangeHandler, | ||
| allowsTreeSupersetOf: (types, other) => | ||
| other.kind === sequenceIdentifier && | ||
| allowsTreeSchemaIdentifierSuperset(types, other.types), | ||
| // TODO: add normalizer/importers for handling ops from other kinds. | ||
| new Set([]), | ||
| ); | ||
| }); | ||
|
|
||
| const identifierFieldIdentifier = brandConst("Identifier")<FieldKindIdentifier>(); | ||
|
|
||
| /** | ||
| * Exactly one identifier. | ||
| */ | ||
| export const identifier = new FlexFieldKind( | ||
| identifierFieldIdentifier, | ||
| Multiplicity.Single, | ||
| noChangeHandler, | ||
| (types, other) => | ||
| export const identifier = new FlexFieldKind(identifierFieldIdentifier, Multiplicity.Single, { | ||
| changeHandler: noChangeHandler, | ||
| allowsTreeSupersetOf: (types, other) => | ||
| // Allows upgrading from identifier to required: which way this upgrade is allowed to go is a subjective policy choice. | ||
| (other.kind === sequence.identifier || | ||
| other.kind === requiredIdentifier || | ||
| other.kind === optional.identifier || | ||
| other.kind === identifierFieldIdentifier) && | ||
| allowsTreeSchemaIdentifierSuperset(types, other.types), | ||
| new Set(), | ||
| ); | ||
| }); | ||
|
|
||
| /** | ||
| * Exactly 0 items. | ||
|
|
@@ -182,10 +181,12 @@ export const identifier = new FlexFieldKind( | |
| export const forbidden = new FlexFieldKind( | ||
| forbiddenFieldKindIdentifier, | ||
| Multiplicity.Forbidden, | ||
| noChangeHandler, | ||
| // All multiplicities other than Value support empty. | ||
| (types, other) => fieldKinds.get(other.kind)?.multiplicity !== Multiplicity.Single, | ||
| new Set(), | ||
| { | ||
| changeHandler: noChangeHandler, | ||
| // All multiplicities other than Value support empty. | ||
| allowsTreeSupersetOf: (types, other) => | ||
| fieldKinds.get(other.kind)?.multiplicity !== Multiplicity.Single, | ||
| }, | ||
| ); | ||
|
|
||
| export const fieldKindConfigurations: ReadonlyMap< | ||
|
|
@@ -270,15 +271,31 @@ export const fieldKinds: ReadonlyMap<FieldKindIdentifier, FlexFieldKind> = new M | |
| // TODO: ensure thy work in generated docs. | ||
| // TODO: add these comments to the rest of the cases below. | ||
| export interface Required | ||
| extends FlexFieldKind<ValueFieldEditor, "Value", Multiplicity.Single> {} | ||
| extends FlexFieldKind<RequiredFieldEditor, typeof requiredIdentifier, Multiplicity.Single> {} | ||
| export interface Optional | ||
| extends FlexFieldKind<OptionalFieldEditor, "Optional", Multiplicity.Optional> {} | ||
| extends FlexFieldKind< | ||
| OptionalFieldEditor, | ||
| typeof optionalIdentifier, | ||
| Multiplicity.Optional | ||
| > {} | ||
| export interface Sequence | ||
| extends FlexFieldKind<SequenceFieldEditor, "Sequence", Multiplicity.Sequence> {} | ||
| extends FlexFieldKind< | ||
| SequenceFieldEditor, | ||
| typeof sequenceIdentifier, | ||
| Multiplicity.Sequence | ||
| > {} | ||
| export interface Identifier | ||
| extends FlexFieldKind<FieldEditor<0>, "Identifier", Multiplicity.Single> {} | ||
| extends FlexFieldKind< | ||
| FieldEditor<0>, | ||
| typeof identifierFieldIdentifier, | ||
| Multiplicity.Single | ||
| > {} | ||
| export interface Forbidden | ||
| extends FlexFieldKind<FieldEditor<0>, "Forbidden", Multiplicity.Forbidden> {} | ||
| extends FlexFieldKind< | ||
| FieldEditor<0>, | ||
| typeof forbiddenFieldKindIdentifier, | ||
| Multiplicity.Forbidden | ||
| > {} | ||
|
|
||
| /** | ||
| * Default FieldKinds with their editor types erased. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,43 +36,34 @@ export class FlexFieldKind< | |
| // TODO: stronger typing | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| TEditor extends FieldEditor<any> = FieldEditor<any>, | ||
| TName extends string = string, | ||
| TName extends FieldKindIdentifier = FieldKindIdentifier, | ||
| TMultiplicity extends Multiplicity = Multiplicity, | ||
| > implements FieldKindData | ||
| { | ||
| protected _typeCheck!: MakeNominal; | ||
|
|
||
| /** | ||
| * @param identifier - Globally scoped identifier. | ||
| * @param multiplicity - bound on the number of children that fields of this kind may have. | ||
| * TODO: replace with numeric upper and lower bounds. | ||
|
Comment on lines
-46
to
-48
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These docs were moved to FieldKindData, and still apply here (and show up in IntelliSense) due to |
||
| * @param changeHandler - Change handling policy. | ||
| * @param allowsTreeSupersetOf - returns true iff `superset` supports all that this does | ||
| * and `superset` is an allowed upgrade. Does not have to handle the `never` cases. | ||
| * See {@link isNeverField}. | ||
| * TODO: when used as a method (instead of a free function like the other superset related functions), | ||
| * this name is/signature is confusing and seems backwards. | ||
| * @param handlesEditsFrom - Kinds (in addition to this) whose edits can be processed by changeHandler. | ||
| * If the kind of a field changes, and edits are rebased across that kind change, | ||
| * listing the other old kind here can prevent those edits from being conflicted and | ||
| * provide a chance to handle them. | ||
|
Comment on lines
-49
to
-58
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These docs moved to the members of FieldKindOptions |
||
| * Change handling policy. | ||
| */ | ||
| // TODO: stronger typing | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| public readonly changeHandler: FieldChangeHandler<any, TEditor>; | ||
|
|
||
| public constructor( | ||
| public readonly identifier: TName & FieldKindIdentifier, | ||
| public readonly identifier: TName, | ||
| public readonly multiplicity: TMultiplicity, | ||
| // TODO: stronger typing | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| public readonly changeHandler: FieldChangeHandler<any, TEditor>, | ||
| private readonly allowsTreeSupersetOf: ( | ||
| originalTypes: TreeTypeSet, | ||
| superset: TreeFieldStoredSchema, | ||
| ) => boolean, | ||
| public readonly handlesEditsFrom: ReadonlySet<FieldKindIdentifier>, | ||
| ) {} | ||
| private readonly options: FieldKindOptions<FieldChangeHandler<any, TEditor>>, | ||
| ) { | ||
| this.changeHandler = options.changeHandler; | ||
| } | ||
|
|
||
| /** | ||
| * Returns true if and only if `superset` permits a (non-strict) superset of the subtrees | ||
| * allowed by field made from `this` and `originalTypes`. | ||
| * | ||
| * TODO: clarify the relationship between this and FieldKindData, and issues with cyclic schema upgrades. | ||
| */ | ||
| public allowsFieldSuperset( | ||
| policy: SchemaPolicy, | ||
|
|
@@ -93,10 +84,43 @@ export class FlexFieldKind< | |
| if (isNeverField(policy, originalData, superset)) { | ||
| return false; | ||
| } | ||
| return this.allowsTreeSupersetOf(originalTypes, superset); | ||
| return this.options.allowsTreeSupersetOf(originalTypes, superset); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Compatibility portion of a {@link FlexFieldKind}. | ||
| * | ||
| * @remarks | ||
| * Specifies everything needed to determine if a given field schema upgrade is allowed. | ||
| */ | ||
| export interface FieldKindOptions<TFieldChangeHandler> { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was made force some of the less obvious from context parameters to the constructor into named members of an object for improved readability.
|
||
| /** | ||
| * Change handling policy. | ||
| */ | ||
| readonly changeHandler: TFieldChangeHandler; | ||
|
|
||
| /** | ||
| * Returns true if and only if `superset` permits a (non-strict) superset of the subtrees | ||
| * allowed by field made from `this` and `originalTypes`. | ||
| * @remarks | ||
| * Used by {@link FlexFieldKind.allowsFieldSuperset}, which handles the `never` cases before calling this. | ||
| */ | ||
| readonly allowsTreeSupersetOf: ( | ||
| originalTypes: TreeTypeSet, | ||
| superset: TreeFieldStoredSchema, | ||
| ) => boolean; | ||
|
|
||
| /** | ||
| * Kinds (in addition to this) whose edits can be processed by changeHandler. | ||
| * If the kind of a field changes, and edits are rebased across that kind change, | ||
| * listing the other old kind here can prevent those edits from being conflicted and | ||
| * provide a chance to handle them. | ||
| */ | ||
| // TODO: provide this and use it for improved support for rebasing changes across schema upgrades. | ||
| // readonly handlesEditsFrom: ReadonlySet<FieldKindIdentifier>; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We never implemented the ability to preserve edits across schema changes, so this option which only exists to allow preserving edits to a field whose schema has changed never has had an opportunity to do anything, and has also never been provided as anything but empty. Someday we will likely want this back, but for now it does not need to exist. |
||
| } | ||
|
|
||
| /** | ||
| * Policy from the app for interpreting the stored schema. | ||
| * The app must ensure consistency for all users of the document. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.