@fluidframework/tree
Advanced tools
Comparing version 2.10.0-306579 to 2.10.0-307060
@@ -8,5 +8,5 @@ ## Alpha API Report File for "@fluidframework/tree" | ||
// @alpha | ||
export function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TreeNode & { | ||
export function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TValue extends unknown ? TreeNode & { | ||
readonly value: TValue; | ||
}) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & { | ||
} : never) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & { | ||
readonly value: TEnum[Property]; | ||
@@ -80,10 +80,10 @@ }, Record<string, never>, true, Record<string, never>, undefined>; } & { | ||
// @alpha | ||
export function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TreeNode & { | ||
export function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TValue extends unknown ? TreeNode & { | ||
readonly value: TValue; | ||
}) & Record<Members[number], TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[number]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[number]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>> & { | ||
readonly schema: UnionToTuple<Record<Members[number], TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[number]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[number]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>>[Members[number]]>; | ||
} : never) & { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[Index]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>; } & { | ||
readonly schema: UnionToTuple<Members[number] extends unknown ? { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[Index]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>; }[Members[number]] : never>; | ||
}; | ||
@@ -600,2 +600,4 @@ | ||
]>; | ||
} | { | ||
readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>; | ||
}, false, T, undefined>; | ||
@@ -602,0 +604,0 @@ readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; |
@@ -386,2 +386,4 @@ ## Beta API Report File for "@fluidframework/tree" | ||
]>; | ||
} | { | ||
readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>; | ||
}, false, T, undefined>; | ||
@@ -388,0 +390,0 @@ readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; |
@@ -381,2 +381,4 @@ ## Alpha API Report File for "@fluidframework/tree" | ||
]>; | ||
} | { | ||
readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>; | ||
}, false, T, undefined>; | ||
@@ -383,0 +385,0 @@ readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; |
@@ -381,2 +381,4 @@ ## Public API Report File for "@fluidframework/tree" | ||
]>; | ||
} | { | ||
readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>; | ||
}, false, T, undefined>; | ||
@@ -383,0 +385,0 @@ readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; |
@@ -381,2 +381,4 @@ ## Public API Report File for "@fluidframework/tree" | ||
]>; | ||
} | { | ||
readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>; | ||
}, false, T, undefined>; | ||
@@ -383,0 +385,0 @@ readonly null: TreeNodeSchemaNonClass<"com.fluidframework.leaf.null", NodeKind.Leaf, null, null, true, unknown, never>; |
@@ -14,3 +14,14 @@ /*! | ||
type: TreeNodeSchemaIdentifier; | ||
/** | ||
* Fields of this node. | ||
* @remarks | ||
* This object has exclusive deep ownership of this map (which might mutate it in the future). | ||
* Any code editing this map must update child reference counts. | ||
* | ||
* Like with {@link MapTree}, fields with no nodes must be removed from the map. | ||
*/ | ||
fields: Map<FieldKey, TreeChunk[]>; | ||
/** | ||
* The value on this node, if any. | ||
*/ | ||
value?: TreeValue | undefined; | ||
@@ -21,8 +32,18 @@ readonly topLevelLength: number; | ||
* | ||
* @param fields - provides exclusive deep ownership of this map to this object (which might mutate it in the future). | ||
* The caller must have already accounted for this reference to the children in this map (via `referenceAdded`), | ||
* and any edits to this must update child reference counts. | ||
* @param value - the value on this node, if any. | ||
* Caller must have already accounted for references via `fields` to the children in the fields map (via `referenceAdded`). | ||
*/ | ||
constructor(type: TreeNodeSchemaIdentifier, fields: Map<FieldKey, TreeChunk[]>, value?: TreeValue | undefined); | ||
constructor(type: TreeNodeSchemaIdentifier, | ||
/** | ||
* Fields of this node. | ||
* @remarks | ||
* This object has exclusive deep ownership of this map (which might mutate it in the future). | ||
* Any code editing this map must update child reference counts. | ||
* | ||
* Like with {@link MapTree}, fields with no nodes must be removed from the map. | ||
*/ | ||
fields: Map<FieldKey, TreeChunk[]>, | ||
/** | ||
* The value on this node, if any. | ||
*/ | ||
value?: TreeValue | undefined); | ||
clone(): BasicChunk; | ||
@@ -29,0 +50,0 @@ cursor(): ChunkedCursor; |
@@ -19,8 +19,18 @@ "use strict"; | ||
* | ||
* @param fields - provides exclusive deep ownership of this map to this object (which might mutate it in the future). | ||
* The caller must have already accounted for this reference to the children in this map (via `referenceAdded`), | ||
* and any edits to this must update child reference counts. | ||
* @param value - the value on this node, if any. | ||
* Caller must have already accounted for references via `fields` to the children in the fields map (via `referenceAdded`). | ||
*/ | ||
constructor(type, fields, value) { | ||
constructor(type, | ||
/** | ||
* Fields of this node. | ||
* @remarks | ||
* This object has exclusive deep ownership of this map (which might mutate it in the future). | ||
* Any code editing this map must update child reference counts. | ||
* | ||
* Like with {@link MapTree}, fields with no nodes must be removed from the map. | ||
*/ | ||
fields, | ||
/** | ||
* The value on this node, if any. | ||
*/ | ||
value) { | ||
super(); | ||
@@ -27,0 +37,0 @@ this.type = type; |
@@ -145,2 +145,7 @@ "use strict"; | ||
(0, internal_1.assert)(newContentSource !== oldContentDestination, 0x7b0 /* Replace detached source field and detached destination field must be different */); | ||
// TODO: optimize this to: perform in-place replace in uniform chunks when possible. | ||
// This should result in 3 cases: | ||
// 1. In-place update of uniform chunk. No allocations, no ref count changes, no new TreeChunks. | ||
// 2. Uniform chunk is shared: copy it (and parent path as needed), and update the copy. | ||
// 3. Fallback to detach then attach (Which will copy parents and convert to basic chunks as needed). | ||
this.detachEdit(range, oldContentDestination); | ||
@@ -147,0 +152,0 @@ this.attachEdit(newContentSource, range.end - range.start, range.start); |
@@ -16,3 +16,3 @@ /*! | ||
export { SequenceField }; | ||
export { isNeverField, ModularEditBuilder, type FieldEditDescription as EditDescription, type FieldChangeHandler, type FieldChangeRebaser, type FieldEditor, type FieldChangeMap, type FieldChange, type FieldChangeset, type ToDelta, type ModularChangeset, makeModularChangeCodecFamily, type NodeChangeComposer, type NodeChangeInverter, type NodeChangeRebaser, type NodeChangePruner, type CrossFieldManager, CrossFieldTarget, FlexFieldKind, type FullSchemaPolicy, allowsRepoSuperset, type GenericChangeset, genericFieldKind, type HasFieldChanges, type NodeExistsConstraint, FieldKindWithEditor, ModularChangeFamily, type RelevantRemovedRootsFromChild, EncodedModularChangeset, updateRefreshers, type NodeId, type FieldChangeEncodingContext, type FieldKindConfiguration, type FieldKindConfigurationEntry, getAllowedContentIncompatibilities, isRepoSuperset, isNeverTree, } from "./modular-schema/index.js"; | ||
export { isNeverField, ModularEditBuilder, type FieldEditDescription as EditDescription, type FieldChangeHandler, type FieldChangeRebaser, type FieldEditor, type FieldChangeMap, type FieldChange, type FieldChangeset, type ToDelta, type ModularChangeset, makeModularChangeCodecFamily, type NodeChangeComposer, type NodeChangeInverter, type NodeChangeRebaser, type NodeChangePruner, type CrossFieldManager, CrossFieldTarget, FlexFieldKind, type FullSchemaPolicy, allowsRepoSuperset, type GenericChangeset, genericFieldKind, type HasFieldChanges, type NodeExistsConstraint, FieldKindWithEditor, ModularChangeFamily, type RelevantRemovedRootsFromChild, EncodedModularChangeset, updateRefreshers, type NodeId, type FieldChangeEncodingContext, type FieldKindConfiguration, type FieldKindConfigurationEntry, getAllowedContentDiscrepancies, isRepoSuperset, isNeverTree, } from "./modular-schema/index.js"; | ||
export { mapRootChanges } from "./deltaUtils.js"; | ||
@@ -19,0 +19,0 @@ export { type TreeChunk, chunkTree, chunkFieldSingle, buildChunkedForest, defaultChunkPolicy, type FieldBatch, type FieldBatchCodec, makeTreeChunker, makeFieldBatchCodec, type FieldBatchEncodingContext, } from "./chunked-forest/index.js"; |
@@ -30,3 +30,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.nodeKeyTreeIdentifier = exports.MockNodeKeyManager = exports.isStableNodeKey = exports.createNodeKeyManager = exports.compareLocalNodeKeys = exports.makeFieldBatchCodec = exports.makeTreeChunker = exports.defaultChunkPolicy = exports.buildChunkedForest = exports.chunkFieldSingle = exports.chunkTree = exports.mapRootChanges = exports.isNeverTree = exports.isRepoSuperset = exports.getAllowedContentIncompatibilities = exports.updateRefreshers = exports.EncodedModularChangeset = exports.ModularChangeFamily = exports.FieldKindWithEditor = exports.genericFieldKind = exports.allowsRepoSuperset = exports.FlexFieldKind = exports.CrossFieldTarget = exports.makeModularChangeCodecFamily = exports.ModularEditBuilder = exports.isNeverField = exports.SequenceField = exports.jsonableTreeFromForest = exports.jsonableTreeFromFieldCursor = exports.jsonableTreeFromCursor = exports.cursorForJsonableTreeField = exports.cursorForJsonableTreeNode = exports.stackTreeFieldCursor = exports.prefixFieldPath = exports.prefixPath = exports.stackTreeNodeCursor = exports.makeSchemaCodec = exports.encodeTreeSchema = exports.SchemaSummarizer = exports.buildForest = exports.MemoizedIdRangeAllocator = exports.mapTreeFieldFromCursor = exports.mapTreeFromCursor = exports.cursorForMapTreeNode = exports.cursorForMapTreeField = exports.ForestSummarizer = exports.isTreeValue = exports.assertAllowedValue = exports.allowsValue = exports.toDownPath = void 0; | ||
exports.nodeKeyTreeIdentifier = exports.MockNodeKeyManager = exports.isStableNodeKey = exports.createNodeKeyManager = exports.compareLocalNodeKeys = exports.makeFieldBatchCodec = exports.makeTreeChunker = exports.defaultChunkPolicy = exports.buildChunkedForest = exports.chunkFieldSingle = exports.chunkTree = exports.mapRootChanges = exports.isNeverTree = exports.isRepoSuperset = exports.getAllowedContentDiscrepancies = exports.updateRefreshers = exports.EncodedModularChangeset = exports.ModularChangeFamily = exports.FieldKindWithEditor = exports.genericFieldKind = exports.allowsRepoSuperset = exports.FlexFieldKind = exports.CrossFieldTarget = exports.makeModularChangeCodecFamily = exports.ModularEditBuilder = exports.isNeverField = exports.SequenceField = exports.jsonableTreeFromForest = exports.jsonableTreeFromFieldCursor = exports.jsonableTreeFromCursor = exports.cursorForJsonableTreeField = exports.cursorForJsonableTreeNode = exports.stackTreeFieldCursor = exports.prefixFieldPath = exports.prefixPath = exports.stackTreeNodeCursor = exports.makeSchemaCodec = exports.encodeTreeSchema = exports.SchemaSummarizer = exports.buildForest = exports.MemoizedIdRangeAllocator = exports.mapTreeFieldFromCursor = exports.mapTreeFromCursor = exports.cursorForMapTreeNode = exports.cursorForMapTreeField = exports.ForestSummarizer = exports.isTreeValue = exports.assertAllowedValue = exports.allowsValue = exports.toDownPath = void 0; | ||
exports.makeMitigatedChangeFamily = exports.EncodedSchemaChange = exports.makeSchemaChangeCodecs = exports.DetachedFieldIndexSummarizer = exports.valueSchemaAllows = exports.TreeCompressionStrategy = exports.FlexTreeEntityKind = exports.indexForAt = exports.treeStatusFromAnchorCache = exports.LazyEntity = exports.isFreedSymbol = exports.getSchemaAndPolicy = exports.flexTreeSlot = exports.assertFlexTreeEntityNotFreed = exports.flexTreeMarker = exports.ContextSlot = exports.isFlexTreeNode = exports.Context = exports.TreeStatus = exports.getTreeContext = exports.Skip = exports.isFieldInSchema = exports.isNodeInSchema = exports.SchemaValidationErrors = exports.relevantRemovedRoots = exports.intoDelta = exports.fieldKindConfigurations = exports.fieldKinds = exports.defaultSchemaPolicy = exports.DefaultEditBuilder = exports.DefaultChangeFamily = exports.FieldKinds = void 0; | ||
@@ -80,3 +80,3 @@ var editableTreeBinder_js_1 = require("./editableTreeBinder.js"); | ||
Object.defineProperty(exports, "updateRefreshers", { enumerable: true, get: function () { return index_js_4.updateRefreshers; } }); | ||
Object.defineProperty(exports, "getAllowedContentIncompatibilities", { enumerable: true, get: function () { return index_js_4.getAllowedContentIncompatibilities; } }); | ||
Object.defineProperty(exports, "getAllowedContentDiscrepancies", { enumerable: true, get: function () { return index_js_4.getAllowedContentDiscrepancies; } }); | ||
Object.defineProperty(exports, "isRepoSuperset", { enumerable: true, get: function () { return index_js_4.isRepoSuperset; } }); | ||
@@ -83,0 +83,0 @@ Object.defineProperty(exports, "isNeverTree", { enumerable: true, get: function () { return index_js_4.isNeverTree; } }); |
@@ -9,21 +9,21 @@ /*! | ||
* | ||
* 1. FieldIncompatibility | ||
* 1. FieldDiscrepancy | ||
* | ||
* `FieldIncompatibility` represents the differences between two `TreeFieldStoredSchema` objects. It consists of | ||
* `FieldDiscrepancy` represents the differences between two `TreeFieldStoredSchema` objects. It consists of | ||
* three types of incompatibilities: | ||
* | ||
* - FieldKindIncompatibility: Indicates the differences in `FieldKindIdentifier` between two `TreeFieldStoredSchema` | ||
* - FieldKindDiscrepancy: Indicates the differences in `FieldKindIdentifier` between two `TreeFieldStoredSchema` | ||
* objects (e.g., optional, required, sequence, etc.). | ||
* - AllowedTypesIncompatibility: Indicates the differences in the allowed child types between the two schemas. | ||
* - ValueSchemaIncompatibility: Specifically indicates the differences in the `ValueSchema` of two | ||
* - AllowedTypesDiscrepancy: Indicates the differences in the allowed child types between the two schemas. | ||
* - ValueSchemaDiscrepancy: Specifically indicates the differences in the `ValueSchema` of two | ||
* `LeafNodeStoredSchema` objects. | ||
* | ||
* 2. NodeIncompatibility | ||
* 2. NodeDiscrepancy | ||
* | ||
* `NodeIncompatibility` represents the differences between two `TreeNodeStoredSchema` objects and includes: | ||
* `NodeDiscrepancy` represents the differences between two `TreeNodeStoredSchema` objects and includes: | ||
* | ||
* - NodeKindIncompatibility: Indicates the differences in the types of `TreeNodeStoredSchema` (currently supports | ||
* - NodeKindDiscrepancy: Indicates the differences in the types of `TreeNodeStoredSchema` (currently supports | ||
* `ObjectNodeStoredSchema`, `MapNodeStoredSchema`, and `LeafNodeStoredSchema`). | ||
* - NodeFieldsIncompatibility: Indicates the `FieldIncompatibility` of `TreeFieldStoredSchema` within two | ||
* `TreeNodeStoredSchema`. It includes an array of `FieldIncompatibility` instances in the `differences` field. | ||
* - NodeFieldsDiscrepancy: Indicates the `FieldDiscrepancy` of `TreeFieldStoredSchema` within two | ||
* `TreeNodeStoredSchema`. It includes an array of `FieldDiscrepancy` instances in the `differences` field. | ||
* | ||
@@ -33,12 +33,12 @@ * When comparing two nodes for compatibility, it only makes sense to compare their fields if the nodes are of | ||
* | ||
* 3. Incompatibility | ||
* 3. Discrepancy | ||
* | ||
* Incompatibility consists of both `NodeIncompatibility` and `FieldIncompatibility`, representing any kind of | ||
* schema differences. See {@link getAllowedContentIncompatibilities} for more details about how we process it | ||
* Discrepancy consists of both `NodeDiscrepancy` and `FieldDiscrepancy`, representing any kind of | ||
* schema differences. See {@link getAllowedContentDiscrepancies} for more details about how we process it | ||
* and the ordering. | ||
*/ | ||
export type Incompatibility = FieldIncompatibility | NodeIncompatibility; | ||
export type NodeIncompatibility = NodeKindIncompatibility | NodeFieldsIncompatibility; | ||
export type FieldIncompatibility = AllowedTypeIncompatibility | FieldKindIncompatibility | ValueSchemaIncompatibility; | ||
export interface AllowedTypeIncompatibility { | ||
export type Discrepancy = FieldDiscrepancy | NodeDiscrepancy; | ||
export type NodeDiscrepancy = NodeKindDiscrepancy | NodeFieldsDiscrepancy; | ||
export type FieldDiscrepancy = AllowedTypeDiscrepancy | FieldKindDiscrepancy | ValueSchemaDiscrepancy; | ||
export interface AllowedTypeDiscrepancy { | ||
identifier: string | undefined; | ||
@@ -55,3 +55,3 @@ mismatch: "allowedTypes"; | ||
} | ||
export interface FieldKindIncompatibility { | ||
export interface FieldKindDiscrepancy { | ||
identifier: string | undefined; | ||
@@ -62,3 +62,3 @@ mismatch: "fieldKind"; | ||
} | ||
export interface ValueSchemaIncompatibility { | ||
export interface ValueSchemaDiscrepancy { | ||
identifier: string; | ||
@@ -69,3 +69,3 @@ mismatch: "valueSchema"; | ||
} | ||
export interface NodeKindIncompatibility { | ||
export interface NodeKindDiscrepancy { | ||
identifier: string; | ||
@@ -76,19 +76,19 @@ mismatch: "nodeKind"; | ||
} | ||
export interface NodeFieldsIncompatibility { | ||
export interface NodeFieldsDiscrepancy { | ||
identifier: string; | ||
mismatch: "fields"; | ||
differences: FieldIncompatibility[]; | ||
differences: FieldDiscrepancy[]; | ||
} | ||
type SchemaFactoryNodeKind = "object" | "leaf" | "map"; | ||
/** | ||
* @remarks | ||
* Finds and reports discrepancies between a view schema and a stored schema. | ||
* | ||
* The workflow for finding schema incompatibilities: | ||
* 1. Compare the two root schemas to identify any `FieldIncompatibility`. | ||
* 1. Compare the two root schemas to identify any `FieldDiscrepancy`. | ||
* | ||
* 2. For each node schema in the `view`: | ||
* - Verify if the node schema exists in the stored. If it does, ensure that the `SchemaFactoryNodeKind` are | ||
* consistent. Otherwise this difference is treated as `NodeKindIncompatibility` | ||
* consistent. Otherwise this difference is treated as `NodeKindDiscrepancy` | ||
* - If a node schema with the same identifier exists in both view and stored, and their `SchemaFactoryNodeKind` | ||
* are consistent, perform a exhaustive validation to identify all `FieldIncompatibility`. | ||
* are consistent, perform a exhaustive validation to identify all `FieldDiscrepancy`. | ||
* | ||
@@ -100,3 +100,3 @@ * 3. For each node schema in the stored, verify if it exists in the view. The overlapping parts were already | ||
*/ | ||
export declare function getAllowedContentIncompatibilities(view: TreeStoredSchema, stored: TreeStoredSchema): Incompatibility[]; | ||
export declare function getAllowedContentDiscrepancies(view: TreeStoredSchema, stored: TreeStoredSchema): Discrepancy[]; | ||
/** | ||
@@ -103,0 +103,0 @@ * @remarks |
@@ -7,3 +7,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isRepoSuperset = exports.getAllowedContentIncompatibilities = void 0; | ||
exports.isRepoSuperset = exports.getAllowedContentDiscrepancies = void 0; | ||
const internal_1 = require("@fluidframework/core-utils/internal"); | ||
@@ -13,12 +13,12 @@ const index_js_1 = require("../../core/index.js"); | ||
/** | ||
* @remarks | ||
* Finds and reports discrepancies between a view schema and a stored schema. | ||
* | ||
* The workflow for finding schema incompatibilities: | ||
* 1. Compare the two root schemas to identify any `FieldIncompatibility`. | ||
* 1. Compare the two root schemas to identify any `FieldDiscrepancy`. | ||
* | ||
* 2. For each node schema in the `view`: | ||
* - Verify if the node schema exists in the stored. If it does, ensure that the `SchemaFactoryNodeKind` are | ||
* consistent. Otherwise this difference is treated as `NodeKindIncompatibility` | ||
* consistent. Otherwise this difference is treated as `NodeKindDiscrepancy` | ||
* - If a node schema with the same identifier exists in both view and stored, and their `SchemaFactoryNodeKind` | ||
* are consistent, perform a exhaustive validation to identify all `FieldIncompatibility`. | ||
* are consistent, perform a exhaustive validation to identify all `FieldDiscrepancy`. | ||
* | ||
@@ -30,6 +30,6 @@ * 3. For each node schema in the stored, verify if it exists in the view. The overlapping parts were already | ||
*/ | ||
function getAllowedContentIncompatibilities(view, stored) { | ||
const incompatibilities = []; | ||
function getAllowedContentDiscrepancies(view, stored) { | ||
const discrepancies = []; | ||
// check root schema discrepancies | ||
incompatibilities.push(...trackFieldDiscrepancies(view.rootFieldSchema, stored.rootFieldSchema)); | ||
discrepancies.push(...trackFieldDiscrepancies(view.rootFieldSchema, stored.rootFieldSchema)); | ||
// Verify the existence and type of a node schema given its identifier (key), then determine if | ||
@@ -42,3 +42,3 @@ // an exhaustive search is necessary. | ||
if (!stored.nodeSchema.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -54,3 +54,3 @@ mismatch: "nodeKind", | ||
if (storedNodeSchema instanceof index_js_1.MapNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -63,3 +63,3 @@ mismatch: "nodeKind", | ||
else if (storedNodeSchema instanceof index_js_1.LeafNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -74,3 +74,3 @@ mismatch: "nodeKind", | ||
if (differences.length > 0) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -89,3 +89,3 @@ mismatch: "fields", | ||
if (!stored.nodeSchema.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -101,3 +101,3 @@ mismatch: "nodeKind", | ||
if (storedNodeSchema instanceof index_js_1.ObjectNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -110,3 +110,3 @@ mismatch: "nodeKind", | ||
else if (storedNodeSchema instanceof index_js_1.LeafNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -119,3 +119,3 @@ mismatch: "nodeKind", | ||
else if (storedNodeSchema instanceof index_js_1.MapNodeStoredSchema) { | ||
incompatibilities.push(...trackFieldDiscrepancies(viewNodeSchema.mapFields, storedNodeSchema.mapFields, key)); | ||
discrepancies.push(...trackFieldDiscrepancies(viewNodeSchema.mapFields, storedNodeSchema.mapFields, key)); | ||
} | ||
@@ -129,3 +129,3 @@ else { | ||
if (!stored.nodeSchema.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -141,3 +141,3 @@ mismatch: "nodeKind", | ||
if (storedNodeSchema instanceof index_js_1.MapNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -150,3 +150,3 @@ mismatch: "nodeKind", | ||
else if (storedNodeSchema instanceof index_js_1.ObjectNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -160,3 +160,3 @@ mismatch: "nodeKind", | ||
if (viewNodeSchema.leafValue !== storedNodeSchema.leafValue) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -180,3 +180,3 @@ mismatch: "valueSchema", | ||
if (!viewNodeKeys.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -193,5 +193,5 @@ mismatch: "nodeKind", | ||
} | ||
return incompatibilities; | ||
return discrepancies; | ||
} | ||
exports.getAllowedContentIncompatibilities = getAllowedContentIncompatibilities; | ||
exports.getAllowedContentDiscrepancies = getAllowedContentDiscrepancies; | ||
/** | ||
@@ -290,7 +290,7 @@ * The function to track the discrepancies between two field stored schemas. | ||
function isRepoSuperset(view, stored) { | ||
const incompatibilities = getAllowedContentIncompatibilities(view, stored); | ||
for (const incompatibility of incompatibilities) { | ||
switch (incompatibility.mismatch) { | ||
const discrepancies = getAllowedContentDiscrepancies(view, stored); | ||
for (const discrepancy of discrepancies) { | ||
switch (discrepancy.mismatch) { | ||
case "nodeKind": { | ||
if (incompatibility.stored !== undefined) { | ||
if (discrepancy.stored !== undefined) { | ||
// It's fine for the view schema to know of a node type that the stored schema doesn't know about. | ||
@@ -304,3 +304,3 @@ return false; | ||
case "fieldKind": { | ||
if (!validateFieldIncompatibility(incompatibility)) { | ||
if (!validateFieldIncompatibility(discrepancy)) { | ||
return false; | ||
@@ -311,3 +311,3 @@ } | ||
case "fields": { | ||
if (incompatibility.differences.some((difference) => !validateFieldIncompatibility(difference))) { | ||
if (discrepancy.differences.some((difference) => !validateFieldIncompatibility(difference))) { | ||
return false; | ||
@@ -323,4 +323,4 @@ } | ||
exports.isRepoSuperset = isRepoSuperset; | ||
function validateFieldIncompatibility(incompatibility) { | ||
switch (incompatibility.mismatch) { | ||
function validateFieldIncompatibility(discrepancy) { | ||
switch (discrepancy.mismatch) { | ||
case "allowedTypes": { | ||
@@ -330,6 +330,6 @@ // Since we only track the symmetric difference between the allowed types in the view and | ||
// stored schema. | ||
return incompatibility.stored.length === 0; | ||
return discrepancy.stored.length === 0; | ||
} | ||
case "fieldKind": { | ||
return posetLte(incompatibility.stored, incompatibility.view, fieldRealizer); | ||
return posetLte(discrepancy.stored, discrepancy.view, fieldRealizer); | ||
} | ||
@@ -336,0 +336,0 @@ case "valueSchema": { |
@@ -18,3 +18,3 @@ /*! | ||
export type { FieldKindConfiguration, FieldKindConfigurationEntry, } from "./fieldKindConfiguration.js"; | ||
export { getAllowedContentIncompatibilities, isRepoSuperset } from "./discrepancies.js"; | ||
export { getAllowedContentDiscrepancies, isRepoSuperset, } from "./discrepancies.js"; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -7,3 +7,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isRepoSuperset = exports.getAllowedContentIncompatibilities = exports.makeModularChangeCodecFamily = exports.updateRefreshers = exports.relevantRemovedRoots = exports.intoDelta = exports.rebaseRevisionMetadataFromInfo = exports.ModularEditBuilder = exports.ModularChangeFamily = exports.genericFieldKind = exports.genericChangeHandler = exports.convertGenericChange = exports.NodeAttachState = exports.referenceFreeFieldChangeRebaser = exports.FieldKindWithEditor = exports.FlexFieldKind = exports.EncodedNodeChangeset = exports.EncodedModularChangeset = exports.EncodedRevisionInfo = exports.EncodedChangeAtomId = exports.ChangesetLocalIdSchema = exports.setInCrossFieldMap = exports.CrossFieldTarget = exports.addCrossFieldQuery = exports.isNeverTree = exports.isNeverField = exports.allowsTreeSuperset = exports.allowsFieldSuperset = exports.allowsTreeSchemaIdentifierSuperset = exports.allowsRepoSuperset = void 0; | ||
exports.isRepoSuperset = exports.getAllowedContentDiscrepancies = exports.makeModularChangeCodecFamily = exports.updateRefreshers = exports.relevantRemovedRoots = exports.intoDelta = exports.rebaseRevisionMetadataFromInfo = exports.ModularEditBuilder = exports.ModularChangeFamily = exports.genericFieldKind = exports.genericChangeHandler = exports.convertGenericChange = exports.NodeAttachState = exports.referenceFreeFieldChangeRebaser = exports.FieldKindWithEditor = exports.FlexFieldKind = exports.EncodedNodeChangeset = exports.EncodedModularChangeset = exports.EncodedRevisionInfo = exports.EncodedChangeAtomId = exports.ChangesetLocalIdSchema = exports.setInCrossFieldMap = exports.CrossFieldTarget = exports.addCrossFieldQuery = exports.isNeverTree = exports.isNeverField = exports.allowsTreeSuperset = exports.allowsFieldSuperset = exports.allowsTreeSchemaIdentifierSuperset = exports.allowsRepoSuperset = void 0; | ||
var comparison_js_1 = require("./comparison.js"); | ||
@@ -48,4 +48,4 @@ Object.defineProperty(exports, "allowsRepoSuperset", { enumerable: true, get: function () { return comparison_js_1.allowsRepoSuperset; } }); | ||
var discrepancies_js_1 = require("./discrepancies.js"); | ||
Object.defineProperty(exports, "getAllowedContentIncompatibilities", { enumerable: true, get: function () { return discrepancies_js_1.getAllowedContentIncompatibilities; } }); | ||
Object.defineProperty(exports, "getAllowedContentDiscrepancies", { enumerable: true, get: function () { return discrepancies_js_1.getAllowedContentDiscrepancies; } }); | ||
Object.defineProperty(exports, "isRepoSuperset", { enumerable: true, get: function () { return discrepancies_js_1.isRepoSuperset; } }); | ||
//# sourceMappingURL=index.js.map |
@@ -8,3 +8,3 @@ /*! | ||
export declare const pkgName = "@fluidframework/tree"; | ||
export declare const pkgVersion = "2.10.0-306579"; | ||
export declare const pkgVersion = "2.10.0-307060"; | ||
//# sourceMappingURL=packageVersion.d.ts.map |
@@ -11,3 +11,3 @@ "use strict"; | ||
exports.pkgName = "@fluidframework/tree"; | ||
exports.pkgVersion = "2.10.0-306579"; | ||
exports.pkgVersion = "2.10.0-307060"; | ||
//# sourceMappingURL=packageVersion.js.map |
@@ -75,3 +75,6 @@ "use strict"; | ||
const node = (0, index_js_2.getOrCreateInnerNode)(constraint.node); | ||
(0, internal_1.assert)(exports.treeApi.status(constraint.node) === index_js_1.TreeStatus.InDocument, 0x90f /* Attempted to apply "nodeExists" constraint when building a transaction, but the node is not in the document. */); | ||
const nodeStatus = exports.treeApi.status(constraint.node); | ||
if (nodeStatus !== index_js_1.TreeStatus.InDocument) { | ||
throw new internal_2.UsageError(`Attempted to add a "nodeInDocument" constraint, but the node is not currently in the document. Node status: ${nodeStatus}`); | ||
} | ||
checkout.editor.addNodeExistsConstraint(node.anchorNode); | ||
@@ -78,0 +81,0 @@ break; |
@@ -22,4 +22,4 @@ /*! | ||
* @remarks | ||
* Currently only supports `string` enums. | ||
* The string value of the enum is used as the name of the schema: callers must ensure that it is stable and unique. | ||
* Numeric enums values have the value implicitly converted into a string. | ||
* Consider making a dedicated schema factory with a nested scope to avoid the enum members colliding with other schema. | ||
@@ -37,3 +37,3 @@ * @example | ||
* // Defined the types of the nodes which correspond to this the schema. | ||
* type ModeNodes = NodeFromSchema<(typeof ModeNodes.schema)[number]>; | ||
* type ModeNodes = TreeNodeFromImplicitAllowedTypes<(typeof ModeNodes.schema)>; | ||
* // An example schema which has an enum as a child. | ||
@@ -57,4 +57,2 @@ * class Parent extends schemaFactory.object("Parent", { | ||
* @privateRemarks | ||
* TODO: | ||
* Extend this to support numeric enums. | ||
* Maybe provide `SchemaFactory.nested` to ease creating nested scopes? | ||
@@ -64,5 +62,5 @@ * @see {@link enumFromStrings} for a similar function that works on arrays of strings instead of an enum. | ||
*/ | ||
export declare function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TreeNode & { | ||
export declare function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TValue extends unknown ? TreeNode & { | ||
readonly value: TValue; | ||
}) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & { | ||
} : never) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & { | ||
readonly value: TEnum[Property]; | ||
@@ -85,3 +83,3 @@ }, Record<string, never>, true, Record<string, never>, undefined>; } & { | ||
* const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]); | ||
* type Mode = NodeFromSchema<(typeof Mode.schema)[number]>; | ||
* type Mode = TreeNodeFromImplicitAllowedTypes<typeof Mode.schema>; | ||
* const nodeFromString: Mode = Mode("Fun"); | ||
@@ -98,11 +96,11 @@ * const nodeFromSchema: Mode = new Mode.Fun(); | ||
*/ | ||
export declare function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TreeNode & { | ||
export declare function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TValue extends unknown ? TreeNode & { | ||
readonly value: TValue; | ||
}) & Record<Members[number], TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[number]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[number]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>> & { | ||
readonly schema: UnionToTuple<Record<Members[number], TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[number]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[number]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>>[Members[number]]>; | ||
} : never) & { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[Index]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>; } & { | ||
readonly schema: UnionToTuple<Members[number] extends unknown ? { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[Index]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>; }[Members[number]] : never>; | ||
}; | ||
//# sourceMappingURL=schemaCreationUtilities.d.ts.map |
@@ -49,4 +49,4 @@ "use strict"; | ||
* @remarks | ||
* Currently only supports `string` enums. | ||
* The string value of the enum is used as the name of the schema: callers must ensure that it is stable and unique. | ||
* Numeric enums values have the value implicitly converted into a string. | ||
* Consider making a dedicated schema factory with a nested scope to avoid the enum members colliding with other schema. | ||
@@ -64,3 +64,3 @@ * @example | ||
* // Defined the types of the nodes which correspond to this the schema. | ||
* type ModeNodes = NodeFromSchema<(typeof ModeNodes.schema)[number]>; | ||
* type ModeNodes = TreeNodeFromImplicitAllowedTypes<(typeof ModeNodes.schema)>; | ||
* // An example schema which has an enum as a child. | ||
@@ -84,4 +84,2 @@ * class Parent extends schemaFactory.object("Parent", { | ||
* @privateRemarks | ||
* TODO: | ||
* Extend this to support numeric enums. | ||
* Maybe provide `SchemaFactory.nested` to ease creating nested scopes? | ||
@@ -101,3 +99,5 @@ * @see {@link enumFromStrings} for a similar function that works on arrays of strings instead of an enum. | ||
const factoryOut = (value) => { | ||
return new out[inverse.get(value) ?? (0, index_js_1.fail)("missing enum value")](); | ||
return new out[inverse.get(value) ?? (0, index_js_1.fail)("missing enum value") | ||
// "extends unknown" is required here to handle when TValue is an union: each member of the union should be processed independently. | ||
](); | ||
}; | ||
@@ -135,3 +135,3 @@ const out = factoryOut; | ||
* const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]); | ||
* type Mode = NodeFromSchema<(typeof Mode.schema)[number]>; | ||
* type Mode = TreeNodeFromImplicitAllowedTypes<typeof Mode.schema>; | ||
* const nodeFromString: Mode = Mode("Fun"); | ||
@@ -156,6 +156,8 @@ * const nodeFromSchema: Mode = new Mode.Fun(); | ||
const factoryOut = (value) => { | ||
return new out[value](); | ||
// "extends unknown" is required here to handle when TValue is an union: each member of the union should be processed independently. | ||
return new recordOut[value](); | ||
}; | ||
const schemaArray = []; | ||
const out = factoryOut; | ||
const recordOut = out; | ||
for (const name of members) { | ||
@@ -162,0 +164,0 @@ const schema = singletonSchema(factory, name); |
@@ -463,2 +463,4 @@ /*! | ||
]>; | ||
} | { | ||
readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>; | ||
}, false, T, undefined>; | ||
@@ -465,0 +467,0 @@ } |
@@ -416,3 +416,6 @@ "use strict"; | ||
mapRecursive(name, allowedTypes) { | ||
const MapSchema = this.namedMap(name, allowedTypes, true, false); | ||
const MapSchema = this.namedMap(name, allowedTypes, true, | ||
// Setting this (implicitlyConstructable) to true seems to work ok currently, but not for other node kinds. | ||
// Supporting this could be fragile and might break other future changes, so it's being kept as false for now. | ||
false); | ||
return MapSchema; | ||
@@ -419,0 +422,0 @@ } |
@@ -40,3 +40,2 @@ /*! | ||
* The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states. | ||
* When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method. | ||
*/ | ||
@@ -47,3 +46,2 @@ export declare class TreeNodeKernel { | ||
readonly schema: TreeNodeSchema; | ||
private innerNode; | ||
private readonly initialContext; | ||
@@ -79,3 +77,4 @@ private disposed; | ||
*/ | ||
hydrate(anchorNode: AnchorNode): void; | ||
private hydrate; | ||
private createHydratedState; | ||
getStatus(): TreeStatus; | ||
@@ -113,5 +112,5 @@ get events(): Listenable<KernelEvents>; | ||
* @remarks | ||
* If `target` is a unhydrated node, returns its MapTreeNode. | ||
* If `target` is an unhydrated node, returns its UnhydratedFlexTreeNode. | ||
* If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode. | ||
* If the target is not a node, or a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
* If the target is a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
*/ | ||
@@ -118,0 +117,0 @@ tryGetInnerNode(): InnerNode | undefined; |
@@ -69,3 +69,3 @@ "use strict"; | ||
function isHydrated(state) { | ||
return typeof state === "object"; | ||
return state.anchorNode !== undefined; | ||
} | ||
@@ -76,3 +76,2 @@ /** | ||
* The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states. | ||
* When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method. | ||
*/ | ||
@@ -91,3 +90,2 @@ class TreeNodeKernel { | ||
this.schema = schema; | ||
this.innerNode = innerNode; | ||
this.initialContext = initialContext; | ||
@@ -106,3 +104,3 @@ this.disposed = false; | ||
this.generationNumber = 0; | ||
_TreeNodeKernel_hydrationState.set(this, () => { }); | ||
_TreeNodeKernel_hydrationState.set(this, void 0); | ||
/** | ||
@@ -124,22 +122,26 @@ * Events registered before hydration. | ||
// These will be fired if the unhydrated node is edited, and will also be forwarded later to the hydrated node. | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, innerNode.events.on("childrenChangedAfterBatch", ({ changedFields }) => { | ||
__classPrivateFieldGet(this, _TreeNodeKernel_unhydratedEvents, "f").value.emit("childrenChangedAfterBatch", { | ||
changedFields, | ||
}); | ||
let n = innerNode; | ||
while (n !== undefined) { | ||
const treeNode = exports.mapTreeNodeToProxy.get(n); | ||
if (treeNode !== undefined) { | ||
const kernel = getKernel(treeNode); | ||
__classPrivateFieldGet(kernel, _TreeNodeKernel_unhydratedEvents, "f").value.emit("subtreeChangedAfterBatch"); | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, { | ||
innerNode, | ||
off: innerNode.events.on("childrenChangedAfterBatch", ({ changedFields }) => { | ||
__classPrivateFieldGet(this, _TreeNodeKernel_unhydratedEvents, "f").value.emit("childrenChangedAfterBatch", { | ||
changedFields, | ||
}); | ||
let n = innerNode; | ||
while (n !== undefined) { | ||
const treeNode = exports.mapTreeNodeToProxy.get(n); | ||
if (treeNode !== undefined) { | ||
const kernel = getKernel(treeNode); | ||
__classPrivateFieldGet(kernel, _TreeNodeKernel_unhydratedEvents, "f").value.emit("subtreeChangedAfterBatch"); | ||
} | ||
// This cast is safe because the parent (if it exists) of an unhydrated flex node is always another unhydrated flex node. | ||
n = n.parentField.parent.parent; | ||
} | ||
// This cast is safe because the parent (if it exists) of an unhydrated flex node is always another unhydrated flex node. | ||
n = n.parentField.parent.parent; | ||
} | ||
}), "f"); | ||
}), | ||
}, "f"); | ||
} | ||
else { | ||
// Hydrated case | ||
(0, internal_1.assert)(!innerNode.anchorNode.slots.has(exports.proxySlot), 0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */); | ||
this.hydrate(innerNode.anchorNode); | ||
const { anchorNode } = innerNode; | ||
(0, internal_1.assert)(!anchorNode.slots.has(exports.proxySlot), 0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */); | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, this.createHydratedState(anchorNode), "f"); | ||
} | ||
@@ -165,18 +167,4 @@ } | ||
(0, internal_1.assert)(!isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f")), 0xa2b /* hydration should only happen once */); | ||
// If the this node is raw and thus has a MapTreeNode, forget it: | ||
if (this.innerNode instanceof unhydratedFlexTree_js_1.UnhydratedFlexTreeNode) { | ||
exports.mapTreeNodeToProxy.delete(this.innerNode); | ||
} | ||
// However, it's fine for an anchor node to rotate through different proxies when the content at that place in the tree is replaced. | ||
anchorNode.slots.set(exports.proxySlot, this.node); | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, { | ||
anchorNode, | ||
offAnchorNode: new Set([ | ||
anchorNode.events.on("afterDestroy", () => this.dispose()), | ||
// TODO: this should be triggered on change even for unhydrated nodes. | ||
anchorNode.events.on("childrenChanging", () => { | ||
this.generationNumber += 1; | ||
}), | ||
]), | ||
}, "f"); | ||
exports.mapTreeNodeToProxy.delete(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode); | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, this.createHydratedState(anchorNode), "f"); | ||
// If needed, register forwarding emitters for events from before hydration | ||
@@ -195,2 +183,15 @@ if (__classPrivateFieldGet(this, _TreeNodeKernel_unhydratedEvents, "f").evaluated) { | ||
} | ||
createHydratedState(anchorNode) { | ||
anchorNode.slots.set(exports.proxySlot, this.node); | ||
return { | ||
anchorNode, | ||
offAnchorNode: new Set([ | ||
anchorNode.events.on("afterDestroy", () => this.dispose()), | ||
// TODO: this should be triggered on change even for unhydrated nodes. | ||
anchorNode.events.on("childrenChanging", () => { | ||
this.generationNumber += 1; | ||
}), | ||
]), | ||
}; | ||
} | ||
getStatus() { | ||
@@ -243,9 +244,8 @@ if (this.disposed) { | ||
getOrCreateInnerNode(allowFreed = false) { | ||
if (!(this.innerNode instanceof unhydratedFlexTree_js_1.UnhydratedFlexTreeNode)) { | ||
// Cooked case | ||
return this.innerNode; | ||
} | ||
if (!isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) { | ||
return this.innerNode; | ||
return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode; // Unhydrated case | ||
} | ||
if (__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode !== undefined) { | ||
return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode; // Cooked case | ||
} | ||
// Marinated case -> cooked | ||
@@ -256,17 +256,19 @@ const anchorNode = __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").anchorNode; | ||
if (flexNode !== undefined) { | ||
this.innerNode = flexNode; | ||
return flexNode; // If it does have a flex node, return it... | ||
} // ...otherwise, the flex node must be created | ||
const context = anchorNode.anchorSet.slots.get(index_js_3.ContextSlot) ?? (0, index_js_4.fail)("missing context"); | ||
const cursor = context.checkout.forest.allocateCursor("getFlexNode"); | ||
context.checkout.forest.moveCursorToPath(anchorNode, cursor); | ||
const newFlexNode = (0, lazyNode_js_1.makeTree)(context, cursor); | ||
cursor.free(); | ||
this.innerNode = newFlexNode; | ||
// Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode | ||
anchorForgetters?.get(this.node)?.(); | ||
if (!allowFreed) { | ||
(0, index_js_3.assertFlexTreeEntityNotFreed)(newFlexNode); | ||
// If the flex node already exists, use it... | ||
__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode = flexNode; | ||
} | ||
return newFlexNode; | ||
else { | ||
// ...otherwise, the flex node must be created | ||
const context = anchorNode.anchorSet.slots.get(index_js_3.ContextSlot) ?? (0, index_js_4.fail)("missing context"); | ||
const cursor = context.checkout.forest.allocateCursor("getFlexNode"); | ||
context.checkout.forest.moveCursorToPath(anchorNode, cursor); | ||
__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode = (0, lazyNode_js_1.makeTree)(context, cursor); | ||
cursor.free(); | ||
// Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode | ||
anchorForgetters?.get(this.node)?.(); | ||
if (!allowFreed) { | ||
(0, index_js_3.assertFlexTreeEntityNotFreed)(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode); | ||
} | ||
} | ||
return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode; | ||
} | ||
@@ -303,18 +305,12 @@ /** | ||
* @remarks | ||
* If `target` is a unhydrated node, returns its MapTreeNode. | ||
* If `target` is an unhydrated node, returns its UnhydratedFlexTreeNode. | ||
* If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode. | ||
* If the target is not a node, or a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
* If the target is a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
*/ | ||
tryGetInnerNode() { | ||
if ((0, index_js_3.isFlexTreeNode)(this.innerNode)) { | ||
// Cooked case | ||
return this.innerNode; | ||
if (isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) { | ||
return (__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode ?? | ||
__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").anchorNode.slots.get(index_js_3.flexTreeSlot)); | ||
} | ||
if (!isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) { | ||
return this.innerNode; | ||
} | ||
// Marinated case -> cooked | ||
const anchorNode = __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").anchorNode; | ||
// The proxy is bound to an anchor node, but it may or may not have an actual flex node yet | ||
return anchorNode.slots.get(index_js_3.flexTreeSlot); | ||
return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode; | ||
} | ||
@@ -321,0 +317,0 @@ } |
@@ -13,3 +13,3 @@ /*! | ||
/** | ||
* Helper used to produce types for object nodes. | ||
* Generates the properties for an ObjectNode from its field schema object. | ||
* @system @public | ||
@@ -16,0 +16,0 @@ */ |
@@ -39,2 +39,5 @@ /*! | ||
export declare const ObjectNodeSchema: { | ||
/** | ||
* instanceof-based narrowing support for ObjectNodeSchema in Javascript and TypeScript 5.3 or newer. | ||
*/ | ||
readonly [Symbol.hasInstance]: (value: TreeNodeSchema) => value is ObjectNodeSchema<string, RestrictiveStringRecord<ImplicitFieldSchema>, boolean>; | ||
@@ -41,0 +44,0 @@ }; |
@@ -10,3 +10,5 @@ "use strict"; | ||
exports.ObjectNodeSchema = { | ||
// instanceof-based narrowing support for Javascript and TypeScript 5.3 or newer. | ||
/** | ||
* instanceof-based narrowing support for ObjectNodeSchema in Javascript and TypeScript 5.3 or newer. | ||
*/ | ||
[Symbol.hasInstance](value) { | ||
@@ -13,0 +15,0 @@ return isObjectNodeSchema(value); |
@@ -361,3 +361,3 @@ # SharedTree Merge Semantics | ||
At this time, with the exception of [schema changes](#schema-changes), which have more preconditions, | ||
all supported edits have the same single precondition: | ||
most supported edits have the same single precondition: | ||
the document schema must not have been concurrently changed. | ||
@@ -432,3 +432,3 @@ | ||
"If the node was concurrently moved then the node is unaffected, otherwise, the node is removed". | ||
After an edit with such a conditional postcondition is applied, it is not certain whether the targeted no will be removed or not. | ||
After an edit with such a conditional postcondition is applied, it is not certain whether the targeted node will be removed or not. | ||
@@ -531,2 +531,6 @@ None of `SharedTree`'s currently supported edits have conditional postconditions. | ||
TODO: add a separate document for each node kind and link to them from here. | ||
For specifics on the merge semantics of individual edits operations for each node type, | ||
[Object Node](object-merge-semantics.md) | ||
[Map Node](map-merge-semantics.md) | ||
[Array Node](array-merge-semantics.md) |
@@ -14,3 +14,14 @@ /*! | ||
type: TreeNodeSchemaIdentifier; | ||
/** | ||
* Fields of this node. | ||
* @remarks | ||
* This object has exclusive deep ownership of this map (which might mutate it in the future). | ||
* Any code editing this map must update child reference counts. | ||
* | ||
* Like with {@link MapTree}, fields with no nodes must be removed from the map. | ||
*/ | ||
fields: Map<FieldKey, TreeChunk[]>; | ||
/** | ||
* The value on this node, if any. | ||
*/ | ||
value?: TreeValue | undefined; | ||
@@ -21,8 +32,18 @@ readonly topLevelLength: number; | ||
* | ||
* @param fields - provides exclusive deep ownership of this map to this object (which might mutate it in the future). | ||
* The caller must have already accounted for this reference to the children in this map (via `referenceAdded`), | ||
* and any edits to this must update child reference counts. | ||
* @param value - the value on this node, if any. | ||
* Caller must have already accounted for references via `fields` to the children in the fields map (via `referenceAdded`). | ||
*/ | ||
constructor(type: TreeNodeSchemaIdentifier, fields: Map<FieldKey, TreeChunk[]>, value?: TreeValue | undefined); | ||
constructor(type: TreeNodeSchemaIdentifier, | ||
/** | ||
* Fields of this node. | ||
* @remarks | ||
* This object has exclusive deep ownership of this map (which might mutate it in the future). | ||
* Any code editing this map must update child reference counts. | ||
* | ||
* Like with {@link MapTree}, fields with no nodes must be removed from the map. | ||
*/ | ||
fields: Map<FieldKey, TreeChunk[]>, | ||
/** | ||
* The value on this node, if any. | ||
*/ | ||
value?: TreeValue | undefined); | ||
clone(): BasicChunk; | ||
@@ -29,0 +50,0 @@ cursor(): ChunkedCursor; |
@@ -16,8 +16,18 @@ /*! | ||
* | ||
* @param fields - provides exclusive deep ownership of this map to this object (which might mutate it in the future). | ||
* The caller must have already accounted for this reference to the children in this map (via `referenceAdded`), | ||
* and any edits to this must update child reference counts. | ||
* @param value - the value on this node, if any. | ||
* Caller must have already accounted for references via `fields` to the children in the fields map (via `referenceAdded`). | ||
*/ | ||
constructor(type, fields, value) { | ||
constructor(type, | ||
/** | ||
* Fields of this node. | ||
* @remarks | ||
* This object has exclusive deep ownership of this map (which might mutate it in the future). | ||
* Any code editing this map must update child reference counts. | ||
* | ||
* Like with {@link MapTree}, fields with no nodes must be removed from the map. | ||
*/ | ||
fields, | ||
/** | ||
* The value on this node, if any. | ||
*/ | ||
value) { | ||
super(); | ||
@@ -24,0 +34,0 @@ this.type = type; |
@@ -142,2 +142,7 @@ /*! | ||
assert(newContentSource !== oldContentDestination, 0x7b0 /* Replace detached source field and detached destination field must be different */); | ||
// TODO: optimize this to: perform in-place replace in uniform chunks when possible. | ||
// This should result in 3 cases: | ||
// 1. In-place update of uniform chunk. No allocations, no ref count changes, no new TreeChunks. | ||
// 2. Uniform chunk is shared: copy it (and parent path as needed), and update the copy. | ||
// 3. Fallback to detach then attach (Which will copy parents and convert to basic chunks as needed). | ||
this.detachEdit(range, oldContentDestination); | ||
@@ -144,0 +149,0 @@ this.attachEdit(newContentSource, range.end - range.start, range.start); |
@@ -16,3 +16,3 @@ /*! | ||
export { SequenceField }; | ||
export { isNeverField, ModularEditBuilder, type FieldEditDescription as EditDescription, type FieldChangeHandler, type FieldChangeRebaser, type FieldEditor, type FieldChangeMap, type FieldChange, type FieldChangeset, type ToDelta, type ModularChangeset, makeModularChangeCodecFamily, type NodeChangeComposer, type NodeChangeInverter, type NodeChangeRebaser, type NodeChangePruner, type CrossFieldManager, CrossFieldTarget, FlexFieldKind, type FullSchemaPolicy, allowsRepoSuperset, type GenericChangeset, genericFieldKind, type HasFieldChanges, type NodeExistsConstraint, FieldKindWithEditor, ModularChangeFamily, type RelevantRemovedRootsFromChild, EncodedModularChangeset, updateRefreshers, type NodeId, type FieldChangeEncodingContext, type FieldKindConfiguration, type FieldKindConfigurationEntry, getAllowedContentIncompatibilities, isRepoSuperset, isNeverTree, } from "./modular-schema/index.js"; | ||
export { isNeverField, ModularEditBuilder, type FieldEditDescription as EditDescription, type FieldChangeHandler, type FieldChangeRebaser, type FieldEditor, type FieldChangeMap, type FieldChange, type FieldChangeset, type ToDelta, type ModularChangeset, makeModularChangeCodecFamily, type NodeChangeComposer, type NodeChangeInverter, type NodeChangeRebaser, type NodeChangePruner, type CrossFieldManager, CrossFieldTarget, FlexFieldKind, type FullSchemaPolicy, allowsRepoSuperset, type GenericChangeset, genericFieldKind, type HasFieldChanges, type NodeExistsConstraint, FieldKindWithEditor, ModularChangeFamily, type RelevantRemovedRootsFromChild, EncodedModularChangeset, updateRefreshers, type NodeId, type FieldChangeEncodingContext, type FieldKindConfiguration, type FieldKindConfigurationEntry, getAllowedContentDiscrepancies, isRepoSuperset, isNeverTree, } from "./modular-schema/index.js"; | ||
export { mapRootChanges } from "./deltaUtils.js"; | ||
@@ -19,0 +19,0 @@ export { type TreeChunk, chunkTree, chunkFieldSingle, buildChunkedForest, defaultChunkPolicy, type FieldBatch, type FieldBatchCodec, makeTreeChunker, makeFieldBatchCodec, type FieldBatchEncodingContext, } from "./chunked-forest/index.js"; |
@@ -17,3 +17,3 @@ /*! | ||
export { SequenceField }; | ||
export { isNeverField, ModularEditBuilder, makeModularChangeCodecFamily, CrossFieldTarget, FlexFieldKind, allowsRepoSuperset, genericFieldKind, FieldKindWithEditor, ModularChangeFamily, EncodedModularChangeset, updateRefreshers, getAllowedContentIncompatibilities, isRepoSuperset, isNeverTree, } from "./modular-schema/index.js"; | ||
export { isNeverField, ModularEditBuilder, makeModularChangeCodecFamily, CrossFieldTarget, FlexFieldKind, allowsRepoSuperset, genericFieldKind, FieldKindWithEditor, ModularChangeFamily, EncodedModularChangeset, updateRefreshers, getAllowedContentDiscrepancies, isRepoSuperset, isNeverTree, } from "./modular-schema/index.js"; | ||
export { mapRootChanges } from "./deltaUtils.js"; | ||
@@ -20,0 +20,0 @@ export { chunkTree, chunkFieldSingle, buildChunkedForest, defaultChunkPolicy, makeTreeChunker, makeFieldBatchCodec, } from "./chunked-forest/index.js"; |
@@ -9,21 +9,21 @@ /*! | ||
* | ||
* 1. FieldIncompatibility | ||
* 1. FieldDiscrepancy | ||
* | ||
* `FieldIncompatibility` represents the differences between two `TreeFieldStoredSchema` objects. It consists of | ||
* `FieldDiscrepancy` represents the differences between two `TreeFieldStoredSchema` objects. It consists of | ||
* three types of incompatibilities: | ||
* | ||
* - FieldKindIncompatibility: Indicates the differences in `FieldKindIdentifier` between two `TreeFieldStoredSchema` | ||
* - FieldKindDiscrepancy: Indicates the differences in `FieldKindIdentifier` between two `TreeFieldStoredSchema` | ||
* objects (e.g., optional, required, sequence, etc.). | ||
* - AllowedTypesIncompatibility: Indicates the differences in the allowed child types between the two schemas. | ||
* - ValueSchemaIncompatibility: Specifically indicates the differences in the `ValueSchema` of two | ||
* - AllowedTypesDiscrepancy: Indicates the differences in the allowed child types between the two schemas. | ||
* - ValueSchemaDiscrepancy: Specifically indicates the differences in the `ValueSchema` of two | ||
* `LeafNodeStoredSchema` objects. | ||
* | ||
* 2. NodeIncompatibility | ||
* 2. NodeDiscrepancy | ||
* | ||
* `NodeIncompatibility` represents the differences between two `TreeNodeStoredSchema` objects and includes: | ||
* `NodeDiscrepancy` represents the differences between two `TreeNodeStoredSchema` objects and includes: | ||
* | ||
* - NodeKindIncompatibility: Indicates the differences in the types of `TreeNodeStoredSchema` (currently supports | ||
* - NodeKindDiscrepancy: Indicates the differences in the types of `TreeNodeStoredSchema` (currently supports | ||
* `ObjectNodeStoredSchema`, `MapNodeStoredSchema`, and `LeafNodeStoredSchema`). | ||
* - NodeFieldsIncompatibility: Indicates the `FieldIncompatibility` of `TreeFieldStoredSchema` within two | ||
* `TreeNodeStoredSchema`. It includes an array of `FieldIncompatibility` instances in the `differences` field. | ||
* - NodeFieldsDiscrepancy: Indicates the `FieldDiscrepancy` of `TreeFieldStoredSchema` within two | ||
* `TreeNodeStoredSchema`. It includes an array of `FieldDiscrepancy` instances in the `differences` field. | ||
* | ||
@@ -33,12 +33,12 @@ * When comparing two nodes for compatibility, it only makes sense to compare their fields if the nodes are of | ||
* | ||
* 3. Incompatibility | ||
* 3. Discrepancy | ||
* | ||
* Incompatibility consists of both `NodeIncompatibility` and `FieldIncompatibility`, representing any kind of | ||
* schema differences. See {@link getAllowedContentIncompatibilities} for more details about how we process it | ||
* Discrepancy consists of both `NodeDiscrepancy` and `FieldDiscrepancy`, representing any kind of | ||
* schema differences. See {@link getAllowedContentDiscrepancies} for more details about how we process it | ||
* and the ordering. | ||
*/ | ||
export type Incompatibility = FieldIncompatibility | NodeIncompatibility; | ||
export type NodeIncompatibility = NodeKindIncompatibility | NodeFieldsIncompatibility; | ||
export type FieldIncompatibility = AllowedTypeIncompatibility | FieldKindIncompatibility | ValueSchemaIncompatibility; | ||
export interface AllowedTypeIncompatibility { | ||
export type Discrepancy = FieldDiscrepancy | NodeDiscrepancy; | ||
export type NodeDiscrepancy = NodeKindDiscrepancy | NodeFieldsDiscrepancy; | ||
export type FieldDiscrepancy = AllowedTypeDiscrepancy | FieldKindDiscrepancy | ValueSchemaDiscrepancy; | ||
export interface AllowedTypeDiscrepancy { | ||
identifier: string | undefined; | ||
@@ -55,3 +55,3 @@ mismatch: "allowedTypes"; | ||
} | ||
export interface FieldKindIncompatibility { | ||
export interface FieldKindDiscrepancy { | ||
identifier: string | undefined; | ||
@@ -62,3 +62,3 @@ mismatch: "fieldKind"; | ||
} | ||
export interface ValueSchemaIncompatibility { | ||
export interface ValueSchemaDiscrepancy { | ||
identifier: string; | ||
@@ -69,3 +69,3 @@ mismatch: "valueSchema"; | ||
} | ||
export interface NodeKindIncompatibility { | ||
export interface NodeKindDiscrepancy { | ||
identifier: string; | ||
@@ -76,19 +76,19 @@ mismatch: "nodeKind"; | ||
} | ||
export interface NodeFieldsIncompatibility { | ||
export interface NodeFieldsDiscrepancy { | ||
identifier: string; | ||
mismatch: "fields"; | ||
differences: FieldIncompatibility[]; | ||
differences: FieldDiscrepancy[]; | ||
} | ||
type SchemaFactoryNodeKind = "object" | "leaf" | "map"; | ||
/** | ||
* @remarks | ||
* Finds and reports discrepancies between a view schema and a stored schema. | ||
* | ||
* The workflow for finding schema incompatibilities: | ||
* 1. Compare the two root schemas to identify any `FieldIncompatibility`. | ||
* 1. Compare the two root schemas to identify any `FieldDiscrepancy`. | ||
* | ||
* 2. For each node schema in the `view`: | ||
* - Verify if the node schema exists in the stored. If it does, ensure that the `SchemaFactoryNodeKind` are | ||
* consistent. Otherwise this difference is treated as `NodeKindIncompatibility` | ||
* consistent. Otherwise this difference is treated as `NodeKindDiscrepancy` | ||
* - If a node schema with the same identifier exists in both view and stored, and their `SchemaFactoryNodeKind` | ||
* are consistent, perform a exhaustive validation to identify all `FieldIncompatibility`. | ||
* are consistent, perform a exhaustive validation to identify all `FieldDiscrepancy`. | ||
* | ||
@@ -100,3 +100,3 @@ * 3. For each node schema in the stored, verify if it exists in the view. The overlapping parts were already | ||
*/ | ||
export declare function getAllowedContentIncompatibilities(view: TreeStoredSchema, stored: TreeStoredSchema): Incompatibility[]; | ||
export declare function getAllowedContentDiscrepancies(view: TreeStoredSchema, stored: TreeStoredSchema): Discrepancy[]; | ||
/** | ||
@@ -103,0 +103,0 @@ * @remarks |
@@ -9,12 +9,12 @@ /*! | ||
/** | ||
* @remarks | ||
* Finds and reports discrepancies between a view schema and a stored schema. | ||
* | ||
* The workflow for finding schema incompatibilities: | ||
* 1. Compare the two root schemas to identify any `FieldIncompatibility`. | ||
* 1. Compare the two root schemas to identify any `FieldDiscrepancy`. | ||
* | ||
* 2. For each node schema in the `view`: | ||
* - Verify if the node schema exists in the stored. If it does, ensure that the `SchemaFactoryNodeKind` are | ||
* consistent. Otherwise this difference is treated as `NodeKindIncompatibility` | ||
* consistent. Otherwise this difference is treated as `NodeKindDiscrepancy` | ||
* - If a node schema with the same identifier exists in both view and stored, and their `SchemaFactoryNodeKind` | ||
* are consistent, perform a exhaustive validation to identify all `FieldIncompatibility`. | ||
* are consistent, perform a exhaustive validation to identify all `FieldDiscrepancy`. | ||
* | ||
@@ -26,6 +26,6 @@ * 3. For each node schema in the stored, verify if it exists in the view. The overlapping parts were already | ||
*/ | ||
export function getAllowedContentIncompatibilities(view, stored) { | ||
const incompatibilities = []; | ||
export function getAllowedContentDiscrepancies(view, stored) { | ||
const discrepancies = []; | ||
// check root schema discrepancies | ||
incompatibilities.push(...trackFieldDiscrepancies(view.rootFieldSchema, stored.rootFieldSchema)); | ||
discrepancies.push(...trackFieldDiscrepancies(view.rootFieldSchema, stored.rootFieldSchema)); | ||
// Verify the existence and type of a node schema given its identifier (key), then determine if | ||
@@ -38,3 +38,3 @@ // an exhaustive search is necessary. | ||
if (!stored.nodeSchema.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -50,3 +50,3 @@ mismatch: "nodeKind", | ||
if (storedNodeSchema instanceof MapNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -59,3 +59,3 @@ mismatch: "nodeKind", | ||
else if (storedNodeSchema instanceof LeafNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -70,3 +70,3 @@ mismatch: "nodeKind", | ||
if (differences.length > 0) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -85,3 +85,3 @@ mismatch: "fields", | ||
if (!stored.nodeSchema.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -97,3 +97,3 @@ mismatch: "nodeKind", | ||
if (storedNodeSchema instanceof ObjectNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -106,3 +106,3 @@ mismatch: "nodeKind", | ||
else if (storedNodeSchema instanceof LeafNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -115,3 +115,3 @@ mismatch: "nodeKind", | ||
else if (storedNodeSchema instanceof MapNodeStoredSchema) { | ||
incompatibilities.push(...trackFieldDiscrepancies(viewNodeSchema.mapFields, storedNodeSchema.mapFields, key)); | ||
discrepancies.push(...trackFieldDiscrepancies(viewNodeSchema.mapFields, storedNodeSchema.mapFields, key)); | ||
} | ||
@@ -125,3 +125,3 @@ else { | ||
if (!stored.nodeSchema.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -137,3 +137,3 @@ mismatch: "nodeKind", | ||
if (storedNodeSchema instanceof MapNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -146,3 +146,3 @@ mismatch: "nodeKind", | ||
else if (storedNodeSchema instanceof ObjectNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -156,3 +156,3 @@ mismatch: "nodeKind", | ||
if (viewNodeSchema.leafValue !== storedNodeSchema.leafValue) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -176,3 +176,3 @@ mismatch: "valueSchema", | ||
if (!viewNodeKeys.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -189,3 +189,3 @@ mismatch: "nodeKind", | ||
} | ||
return incompatibilities; | ||
return discrepancies; | ||
} | ||
@@ -285,7 +285,7 @@ /** | ||
export function isRepoSuperset(view, stored) { | ||
const incompatibilities = getAllowedContentIncompatibilities(view, stored); | ||
for (const incompatibility of incompatibilities) { | ||
switch (incompatibility.mismatch) { | ||
const discrepancies = getAllowedContentDiscrepancies(view, stored); | ||
for (const discrepancy of discrepancies) { | ||
switch (discrepancy.mismatch) { | ||
case "nodeKind": { | ||
if (incompatibility.stored !== undefined) { | ||
if (discrepancy.stored !== undefined) { | ||
// It's fine for the view schema to know of a node type that the stored schema doesn't know about. | ||
@@ -299,3 +299,3 @@ return false; | ||
case "fieldKind": { | ||
if (!validateFieldIncompatibility(incompatibility)) { | ||
if (!validateFieldIncompatibility(discrepancy)) { | ||
return false; | ||
@@ -306,3 +306,3 @@ } | ||
case "fields": { | ||
if (incompatibility.differences.some((difference) => !validateFieldIncompatibility(difference))) { | ||
if (discrepancy.differences.some((difference) => !validateFieldIncompatibility(difference))) { | ||
return false; | ||
@@ -317,4 +317,4 @@ } | ||
} | ||
function validateFieldIncompatibility(incompatibility) { | ||
switch (incompatibility.mismatch) { | ||
function validateFieldIncompatibility(discrepancy) { | ||
switch (discrepancy.mismatch) { | ||
case "allowedTypes": { | ||
@@ -324,6 +324,6 @@ // Since we only track the symmetric difference between the allowed types in the view and | ||
// stored schema. | ||
return incompatibility.stored.length === 0; | ||
return discrepancy.stored.length === 0; | ||
} | ||
case "fieldKind": { | ||
return posetLte(incompatibility.stored, incompatibility.view, fieldRealizer); | ||
return posetLte(discrepancy.stored, discrepancy.view, fieldRealizer); | ||
} | ||
@@ -330,0 +330,0 @@ case "valueSchema": { |
@@ -18,3 +18,3 @@ /*! | ||
export type { FieldKindConfiguration, FieldKindConfigurationEntry, } from "./fieldKindConfiguration.js"; | ||
export { getAllowedContentIncompatibilities, isRepoSuperset } from "./discrepancies.js"; | ||
export { getAllowedContentDiscrepancies, isRepoSuperset, } from "./discrepancies.js"; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -15,3 +15,3 @@ /*! | ||
export { makeModularChangeCodecFamily } from "./modularChangeCodecs.js"; | ||
export { getAllowedContentIncompatibilities, isRepoSuperset } from "./discrepancies.js"; | ||
export { getAllowedContentDiscrepancies, isRepoSuperset, } from "./discrepancies.js"; | ||
//# sourceMappingURL=index.js.map |
@@ -8,3 +8,3 @@ /*! | ||
export declare const pkgName = "@fluidframework/tree"; | ||
export declare const pkgVersion = "2.10.0-306579"; | ||
export declare const pkgVersion = "2.10.0-307060"; | ||
//# sourceMappingURL=packageVersion.d.ts.map |
@@ -8,3 +8,3 @@ /*! | ||
export const pkgName = "@fluidframework/tree"; | ||
export const pkgVersion = "2.10.0-306579"; | ||
export const pkgVersion = "2.10.0-307060"; | ||
//# sourceMappingURL=packageVersion.js.map |
@@ -5,3 +5,3 @@ /*! | ||
*/ | ||
import { assert, unreachableCase } from "@fluidframework/core-utils/internal"; | ||
import { unreachableCase } from "@fluidframework/core-utils/internal"; | ||
import { UsageError } from "@fluidframework/telemetry-utils/internal"; | ||
@@ -72,3 +72,6 @@ import { TreeStatus } from "../feature-libraries/index.js"; | ||
const node = getOrCreateInnerNode(constraint.node); | ||
assert(treeApi.status(constraint.node) === TreeStatus.InDocument, 0x90f /* Attempted to apply "nodeExists" constraint when building a transaction, but the node is not in the document. */); | ||
const nodeStatus = treeApi.status(constraint.node); | ||
if (nodeStatus !== TreeStatus.InDocument) { | ||
throw new UsageError(`Attempted to add a "nodeInDocument" constraint, but the node is not currently in the document. Node status: ${nodeStatus}`); | ||
} | ||
checkout.editor.addNodeExistsConstraint(node.anchorNode); | ||
@@ -75,0 +78,0 @@ break; |
@@ -22,4 +22,4 @@ /*! | ||
* @remarks | ||
* Currently only supports `string` enums. | ||
* The string value of the enum is used as the name of the schema: callers must ensure that it is stable and unique. | ||
* Numeric enums values have the value implicitly converted into a string. | ||
* Consider making a dedicated schema factory with a nested scope to avoid the enum members colliding with other schema. | ||
@@ -37,3 +37,3 @@ * @example | ||
* // Defined the types of the nodes which correspond to this the schema. | ||
* type ModeNodes = NodeFromSchema<(typeof ModeNodes.schema)[number]>; | ||
* type ModeNodes = TreeNodeFromImplicitAllowedTypes<(typeof ModeNodes.schema)>; | ||
* // An example schema which has an enum as a child. | ||
@@ -57,4 +57,2 @@ * class Parent extends schemaFactory.object("Parent", { | ||
* @privateRemarks | ||
* TODO: | ||
* Extend this to support numeric enums. | ||
* Maybe provide `SchemaFactory.nested` to ease creating nested scopes? | ||
@@ -64,5 +62,5 @@ * @see {@link enumFromStrings} for a similar function that works on arrays of strings instead of an enum. | ||
*/ | ||
export declare function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TreeNode & { | ||
export declare function adaptEnum<TScope extends string, const TEnum extends Record<string, string | number>>(factory: SchemaFactory<TScope>, members: TEnum): (<TValue extends TEnum[keyof TEnum]>(value: TValue) => TValue extends unknown ? TreeNode & { | ||
readonly value: TValue; | ||
}) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & { | ||
} : never) & { readonly [Property in keyof TEnum]: TreeNodeSchemaClass<ScopedSchemaName<TScope, TEnum[Property]>, NodeKind.Object, TreeNode & { | ||
readonly value: TEnum[Property]; | ||
@@ -85,3 +83,3 @@ }, Record<string, never>, true, Record<string, never>, undefined>; } & { | ||
* const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]); | ||
* type Mode = NodeFromSchema<(typeof Mode.schema)[number]>; | ||
* type Mode = TreeNodeFromImplicitAllowedTypes<typeof Mode.schema>; | ||
* const nodeFromString: Mode = Mode("Fun"); | ||
@@ -98,11 +96,11 @@ * const nodeFromSchema: Mode = new Mode.Fun(); | ||
*/ | ||
export declare function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TreeNode & { | ||
export declare function enumFromStrings<TScope extends string, const Members extends readonly string[]>(factory: SchemaFactory<TScope>, members: Members): (<TValue extends Members[number]>(value: TValue) => TValue extends unknown ? TreeNode & { | ||
readonly value: TValue; | ||
}) & Record<Members[number], TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[number]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[number]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>> & { | ||
readonly schema: UnionToTuple<Record<Members[number], TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[number]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[number]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>>[Members[number]]>; | ||
} : never) & { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[Index]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>; } & { | ||
readonly schema: UnionToTuple<Members[number] extends unknown ? { [Index in Extract<keyof Members, `${number}`> extends `${infer N extends number}` ? N : never as Members[Index]]: TreeNodeSchemaClass<ScopedSchemaName<TScope, Members[Index]>, NodeKind.Object, TreeNode & { | ||
readonly value: Members[Index]; | ||
}, Record<string, never>, true, Record<string, never>, undefined>; }[Members[number]] : never>; | ||
}; | ||
//# sourceMappingURL=schemaCreationUtilities.d.ts.map |
@@ -45,4 +45,4 @@ /*! | ||
* @remarks | ||
* Currently only supports `string` enums. | ||
* The string value of the enum is used as the name of the schema: callers must ensure that it is stable and unique. | ||
* Numeric enums values have the value implicitly converted into a string. | ||
* Consider making a dedicated schema factory with a nested scope to avoid the enum members colliding with other schema. | ||
@@ -60,3 +60,3 @@ * @example | ||
* // Defined the types of the nodes which correspond to this the schema. | ||
* type ModeNodes = NodeFromSchema<(typeof ModeNodes.schema)[number]>; | ||
* type ModeNodes = TreeNodeFromImplicitAllowedTypes<(typeof ModeNodes.schema)>; | ||
* // An example schema which has an enum as a child. | ||
@@ -80,4 +80,2 @@ * class Parent extends schemaFactory.object("Parent", { | ||
* @privateRemarks | ||
* TODO: | ||
* Extend this to support numeric enums. | ||
* Maybe provide `SchemaFactory.nested` to ease creating nested scopes? | ||
@@ -97,3 +95,5 @@ * @see {@link enumFromStrings} for a similar function that works on arrays of strings instead of an enum. | ||
const factoryOut = (value) => { | ||
return new out[inverse.get(value) ?? fail("missing enum value")](); | ||
return new out[inverse.get(value) ?? fail("missing enum value") | ||
// "extends unknown" is required here to handle when TValue is an union: each member of the union should be processed independently. | ||
](); | ||
}; | ||
@@ -130,3 +130,3 @@ const out = factoryOut; | ||
* const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]); | ||
* type Mode = NodeFromSchema<(typeof Mode.schema)[number]>; | ||
* type Mode = TreeNodeFromImplicitAllowedTypes<typeof Mode.schema>; | ||
* const nodeFromString: Mode = Mode("Fun"); | ||
@@ -151,6 +151,8 @@ * const nodeFromSchema: Mode = new Mode.Fun(); | ||
const factoryOut = (value) => { | ||
return new out[value](); | ||
// "extends unknown" is required here to handle when TValue is an union: each member of the union should be processed independently. | ||
return new recordOut[value](); | ||
}; | ||
const schemaArray = []; | ||
const out = factoryOut; | ||
const recordOut = out; | ||
for (const name of members) { | ||
@@ -157,0 +159,0 @@ const schema = singletonSchema(factory, name); |
@@ -463,2 +463,4 @@ /*! | ||
]>; | ||
} | { | ||
readonly [x: string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>; | ||
}, false, T, undefined>; | ||
@@ -465,0 +467,0 @@ } |
@@ -412,3 +412,6 @@ /*! | ||
mapRecursive(name, allowedTypes) { | ||
const MapSchema = this.namedMap(name, allowedTypes, true, false); | ||
const MapSchema = this.namedMap(name, allowedTypes, true, | ||
// Setting this (implicitlyConstructable) to true seems to work ok currently, but not for other node kinds. | ||
// Supporting this could be fragile and might break other future changes, so it's being kept as false for now. | ||
false); | ||
return MapSchema; | ||
@@ -415,0 +418,0 @@ } |
@@ -40,3 +40,2 @@ /*! | ||
* The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states. | ||
* When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method. | ||
*/ | ||
@@ -47,3 +46,2 @@ export declare class TreeNodeKernel { | ||
readonly schema: TreeNodeSchema; | ||
private innerNode; | ||
private readonly initialContext; | ||
@@ -79,3 +77,4 @@ private disposed; | ||
*/ | ||
hydrate(anchorNode: AnchorNode): void; | ||
private hydrate; | ||
private createHydratedState; | ||
getStatus(): TreeStatus; | ||
@@ -113,5 +112,5 @@ get events(): Listenable<KernelEvents>; | ||
* @remarks | ||
* If `target` is a unhydrated node, returns its MapTreeNode. | ||
* If `target` is an unhydrated node, returns its UnhydratedFlexTreeNode. | ||
* If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode. | ||
* If the target is not a node, or a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
* If the target is a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
*/ | ||
@@ -118,0 +117,0 @@ tryGetInnerNode(): InnerNode | undefined; |
@@ -20,3 +20,3 @@ /*! | ||
import { anchorSlot, } from "../../core/index.js"; | ||
import { assertFlexTreeEntityNotFreed, ContextSlot, flexTreeSlot, isFlexTreeNode, isFreedSymbol, LazyEntity, TreeStatus, treeStatusFromAnchorCache, } from "../../feature-libraries/index.js"; | ||
import { assertFlexTreeEntityNotFreed, ContextSlot, flexTreeSlot, isFreedSymbol, LazyEntity, TreeStatus, treeStatusFromAnchorCache, } from "../../feature-libraries/index.js"; | ||
import { fail } from "../../util/index.js"; | ||
@@ -64,3 +64,3 @@ // TODO: decide how to deal with dependencies on flex-tree implementation. | ||
function isHydrated(state) { | ||
return typeof state === "object"; | ||
return state.anchorNode !== undefined; | ||
} | ||
@@ -71,3 +71,2 @@ /** | ||
* The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states. | ||
* When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method. | ||
*/ | ||
@@ -86,3 +85,2 @@ export class TreeNodeKernel { | ||
this.schema = schema; | ||
this.innerNode = innerNode; | ||
this.initialContext = initialContext; | ||
@@ -101,3 +99,3 @@ this.disposed = false; | ||
this.generationNumber = 0; | ||
_TreeNodeKernel_hydrationState.set(this, () => { }); | ||
_TreeNodeKernel_hydrationState.set(this, void 0); | ||
/** | ||
@@ -119,22 +117,26 @@ * Events registered before hydration. | ||
// These will be fired if the unhydrated node is edited, and will also be forwarded later to the hydrated node. | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, innerNode.events.on("childrenChangedAfterBatch", ({ changedFields }) => { | ||
__classPrivateFieldGet(this, _TreeNodeKernel_unhydratedEvents, "f").value.emit("childrenChangedAfterBatch", { | ||
changedFields, | ||
}); | ||
let n = innerNode; | ||
while (n !== undefined) { | ||
const treeNode = mapTreeNodeToProxy.get(n); | ||
if (treeNode !== undefined) { | ||
const kernel = getKernel(treeNode); | ||
__classPrivateFieldGet(kernel, _TreeNodeKernel_unhydratedEvents, "f").value.emit("subtreeChangedAfterBatch"); | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, { | ||
innerNode, | ||
off: innerNode.events.on("childrenChangedAfterBatch", ({ changedFields }) => { | ||
__classPrivateFieldGet(this, _TreeNodeKernel_unhydratedEvents, "f").value.emit("childrenChangedAfterBatch", { | ||
changedFields, | ||
}); | ||
let n = innerNode; | ||
while (n !== undefined) { | ||
const treeNode = mapTreeNodeToProxy.get(n); | ||
if (treeNode !== undefined) { | ||
const kernel = getKernel(treeNode); | ||
__classPrivateFieldGet(kernel, _TreeNodeKernel_unhydratedEvents, "f").value.emit("subtreeChangedAfterBatch"); | ||
} | ||
// This cast is safe because the parent (if it exists) of an unhydrated flex node is always another unhydrated flex node. | ||
n = n.parentField.parent.parent; | ||
} | ||
// This cast is safe because the parent (if it exists) of an unhydrated flex node is always another unhydrated flex node. | ||
n = n.parentField.parent.parent; | ||
} | ||
}), "f"); | ||
}), | ||
}, "f"); | ||
} | ||
else { | ||
// Hydrated case | ||
assert(!innerNode.anchorNode.slots.has(proxySlot), 0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */); | ||
this.hydrate(innerNode.anchorNode); | ||
const { anchorNode } = innerNode; | ||
assert(!anchorNode.slots.has(proxySlot), 0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */); | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, this.createHydratedState(anchorNode), "f"); | ||
} | ||
@@ -160,18 +162,4 @@ } | ||
assert(!isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f")), 0xa2b /* hydration should only happen once */); | ||
// If the this node is raw and thus has a MapTreeNode, forget it: | ||
if (this.innerNode instanceof UnhydratedFlexTreeNode) { | ||
mapTreeNodeToProxy.delete(this.innerNode); | ||
} | ||
// However, it's fine for an anchor node to rotate through different proxies when the content at that place in the tree is replaced. | ||
anchorNode.slots.set(proxySlot, this.node); | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, { | ||
anchorNode, | ||
offAnchorNode: new Set([ | ||
anchorNode.events.on("afterDestroy", () => this.dispose()), | ||
// TODO: this should be triggered on change even for unhydrated nodes. | ||
anchorNode.events.on("childrenChanging", () => { | ||
this.generationNumber += 1; | ||
}), | ||
]), | ||
}, "f"); | ||
mapTreeNodeToProxy.delete(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode); | ||
__classPrivateFieldSet(this, _TreeNodeKernel_hydrationState, this.createHydratedState(anchorNode), "f"); | ||
// If needed, register forwarding emitters for events from before hydration | ||
@@ -190,2 +178,15 @@ if (__classPrivateFieldGet(this, _TreeNodeKernel_unhydratedEvents, "f").evaluated) { | ||
} | ||
createHydratedState(anchorNode) { | ||
anchorNode.slots.set(proxySlot, this.node); | ||
return { | ||
anchorNode, | ||
offAnchorNode: new Set([ | ||
anchorNode.events.on("afterDestroy", () => this.dispose()), | ||
// TODO: this should be triggered on change even for unhydrated nodes. | ||
anchorNode.events.on("childrenChanging", () => { | ||
this.generationNumber += 1; | ||
}), | ||
]), | ||
}; | ||
} | ||
getStatus() { | ||
@@ -238,9 +239,8 @@ if (this.disposed) { | ||
getOrCreateInnerNode(allowFreed = false) { | ||
if (!(this.innerNode instanceof UnhydratedFlexTreeNode)) { | ||
// Cooked case | ||
return this.innerNode; | ||
} | ||
if (!isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) { | ||
return this.innerNode; | ||
return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode; // Unhydrated case | ||
} | ||
if (__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode !== undefined) { | ||
return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode; // Cooked case | ||
} | ||
// Marinated case -> cooked | ||
@@ -251,17 +251,19 @@ const anchorNode = __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").anchorNode; | ||
if (flexNode !== undefined) { | ||
this.innerNode = flexNode; | ||
return flexNode; // If it does have a flex node, return it... | ||
} // ...otherwise, the flex node must be created | ||
const context = anchorNode.anchorSet.slots.get(ContextSlot) ?? fail("missing context"); | ||
const cursor = context.checkout.forest.allocateCursor("getFlexNode"); | ||
context.checkout.forest.moveCursorToPath(anchorNode, cursor); | ||
const newFlexNode = makeTree(context, cursor); | ||
cursor.free(); | ||
this.innerNode = newFlexNode; | ||
// Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode | ||
anchorForgetters?.get(this.node)?.(); | ||
if (!allowFreed) { | ||
assertFlexTreeEntityNotFreed(newFlexNode); | ||
// If the flex node already exists, use it... | ||
__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode = flexNode; | ||
} | ||
return newFlexNode; | ||
else { | ||
// ...otherwise, the flex node must be created | ||
const context = anchorNode.anchorSet.slots.get(ContextSlot) ?? fail("missing context"); | ||
const cursor = context.checkout.forest.allocateCursor("getFlexNode"); | ||
context.checkout.forest.moveCursorToPath(anchorNode, cursor); | ||
__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode = makeTree(context, cursor); | ||
cursor.free(); | ||
// Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode | ||
anchorForgetters?.get(this.node)?.(); | ||
if (!allowFreed) { | ||
assertFlexTreeEntityNotFreed(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode); | ||
} | ||
} | ||
return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode; | ||
} | ||
@@ -298,18 +300,12 @@ /** | ||
* @remarks | ||
* If `target` is a unhydrated node, returns its MapTreeNode. | ||
* If `target` is an unhydrated node, returns its UnhydratedFlexTreeNode. | ||
* If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode. | ||
* If the target is not a node, or a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
* If the target is a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
*/ | ||
tryGetInnerNode() { | ||
if (isFlexTreeNode(this.innerNode)) { | ||
// Cooked case | ||
return this.innerNode; | ||
if (isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) { | ||
return (__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode ?? | ||
__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").anchorNode.slots.get(flexTreeSlot)); | ||
} | ||
if (!isHydrated(__classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f"))) { | ||
return this.innerNode; | ||
} | ||
// Marinated case -> cooked | ||
const anchorNode = __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").anchorNode; | ||
// The proxy is bound to an anchor node, but it may or may not have an actual flex node yet | ||
return anchorNode.slots.get(flexTreeSlot); | ||
return __classPrivateFieldGet(this, _TreeNodeKernel_hydrationState, "f").innerNode; | ||
} | ||
@@ -316,0 +312,0 @@ } |
@@ -13,3 +13,3 @@ /*! | ||
/** | ||
* Helper used to produce types for object nodes. | ||
* Generates the properties for an ObjectNode from its field schema object. | ||
* @system @public | ||
@@ -16,0 +16,0 @@ */ |
@@ -39,2 +39,5 @@ /*! | ||
export declare const ObjectNodeSchema: { | ||
/** | ||
* instanceof-based narrowing support for ObjectNodeSchema in Javascript and TypeScript 5.3 or newer. | ||
*/ | ||
readonly [Symbol.hasInstance]: (value: TreeNodeSchema) => value is ObjectNodeSchema<string, RestrictiveStringRecord<ImplicitFieldSchema>, boolean>; | ||
@@ -41,0 +44,0 @@ }; |
@@ -7,3 +7,5 @@ /*! | ||
export const ObjectNodeSchema = { | ||
// instanceof-based narrowing support for Javascript and TypeScript 5.3 or newer. | ||
/** | ||
* instanceof-based narrowing support for ObjectNodeSchema in Javascript and TypeScript 5.3 or newer. | ||
*/ | ||
[Symbol.hasInstance](value) { | ||
@@ -10,0 +12,0 @@ return isObjectNodeSchema(value); |
{ | ||
"name": "@fluidframework/tree", | ||
"version": "2.10.0-306579", | ||
"version": "2.10.0-307060", | ||
"description": "Distributed tree", | ||
@@ -92,13 +92,13 @@ "homepage": "https://fluidframework.com", | ||
"dependencies": { | ||
"@fluid-internal/client-utils": "2.10.0-306579", | ||
"@fluidframework/container-runtime": "2.10.0-306579", | ||
"@fluidframework/core-interfaces": "2.10.0-306579", | ||
"@fluidframework/core-utils": "2.10.0-306579", | ||
"@fluidframework/datastore-definitions": "2.10.0-306579", | ||
"@fluidframework/driver-definitions": "2.10.0-306579", | ||
"@fluidframework/id-compressor": "2.10.0-306579", | ||
"@fluidframework/runtime-definitions": "2.10.0-306579", | ||
"@fluidframework/runtime-utils": "2.10.0-306579", | ||
"@fluidframework/shared-object-base": "2.10.0-306579", | ||
"@fluidframework/telemetry-utils": "2.10.0-306579", | ||
"@fluid-internal/client-utils": "2.10.0-307060", | ||
"@fluidframework/container-runtime": "2.10.0-307060", | ||
"@fluidframework/core-interfaces": "2.10.0-307060", | ||
"@fluidframework/core-utils": "2.10.0-307060", | ||
"@fluidframework/datastore-definitions": "2.10.0-307060", | ||
"@fluidframework/driver-definitions": "2.10.0-307060", | ||
"@fluidframework/id-compressor": "2.10.0-307060", | ||
"@fluidframework/runtime-definitions": "2.10.0-307060", | ||
"@fluidframework/runtime-utils": "2.10.0-307060", | ||
"@fluidframework/shared-object-base": "2.10.0-307060", | ||
"@fluidframework/telemetry-utils": "2.10.0-307060", | ||
"@sinclair/typebox": "^0.32.29", | ||
@@ -113,6 +113,6 @@ "@tylerbu/sorted-btree-es6": "^1.8.0", | ||
"@biomejs/biome": "~1.9.3", | ||
"@fluid-internal/mocha-test-setup": "2.10.0-306579", | ||
"@fluid-private/stochastic-test-utils": "2.10.0-306579", | ||
"@fluid-private/test-dds-utils": "2.10.0-306579", | ||
"@fluid-private/test-drivers": "2.10.0-306579", | ||
"@fluid-internal/mocha-test-setup": "2.10.0-307060", | ||
"@fluid-private/stochastic-test-utils": "2.10.0-307060", | ||
"@fluid-private/test-dds-utils": "2.10.0-307060", | ||
"@fluid-private/test-drivers": "2.10.0-307060", | ||
"@fluid-tools/benchmark": "^0.50.0", | ||
@@ -122,7 +122,7 @@ "@fluid-tools/build-cli": "^0.50.0", | ||
"@fluidframework/build-tools": "^0.50.0", | ||
"@fluidframework/container-definitions": "2.10.0-306579", | ||
"@fluidframework/container-loader": "2.10.0-306579", | ||
"@fluidframework/container-definitions": "2.10.0-307060", | ||
"@fluidframework/container-loader": "2.10.0-307060", | ||
"@fluidframework/eslint-config-fluid": "^5.4.0", | ||
"@fluidframework/test-runtime-utils": "2.10.0-306579", | ||
"@fluidframework/test-utils": "2.10.0-306579", | ||
"@fluidframework/test-runtime-utils": "2.10.0-307060", | ||
"@fluidframework/test-utils": "2.10.0-307060", | ||
"@fluidframework/tree-previous": "npm:@fluidframework/tree@2.5.0", | ||
@@ -129,0 +129,0 @@ "@microsoft/api-extractor": "7.47.8", |
@@ -33,10 +33,18 @@ /*! | ||
* | ||
* @param fields - provides exclusive deep ownership of this map to this object (which might mutate it in the future). | ||
* The caller must have already accounted for this reference to the children in this map (via `referenceAdded`), | ||
* and any edits to this must update child reference counts. | ||
* @param value - the value on this node, if any. | ||
* Caller must have already accounted for references via `fields` to the children in the fields map (via `referenceAdded`). | ||
*/ | ||
public constructor( | ||
public type: TreeNodeSchemaIdentifier, | ||
/** | ||
* Fields of this node. | ||
* @remarks | ||
* This object has exclusive deep ownership of this map (which might mutate it in the future). | ||
* Any code editing this map must update child reference counts. | ||
* | ||
* Like with {@link MapTree}, fields with no nodes must be removed from the map. | ||
*/ | ||
public fields: Map<FieldKey, TreeChunk[]>, | ||
/** | ||
* The value on this node, if any. | ||
*/ | ||
public value?: TreeValue, | ||
@@ -43,0 +51,0 @@ ) { |
@@ -198,2 +198,7 @@ /*! | ||
); | ||
// TODO: optimize this to: perform in-place replace in uniform chunks when possible. | ||
// This should result in 3 cases: | ||
// 1. In-place update of uniform chunk. No allocations, no ref count changes, no new TreeChunks. | ||
// 2. Uniform chunk is shared: copy it (and parent path as needed), and update the copy. | ||
// 3. Fallback to detach then attach (Which will copy parents and convert to basic chunks as needed). | ||
this.detachEdit(range, oldContentDestination); | ||
@@ -200,0 +205,0 @@ this.attachEdit(newContentSource, range.end - range.start, range.start); |
@@ -77,3 +77,3 @@ /*! | ||
type FieldKindConfigurationEntry, | ||
getAllowedContentIncompatibilities, | ||
getAllowedContentDiscrepancies, | ||
isRepoSuperset, | ||
@@ -80,0 +80,0 @@ isNeverTree, |
@@ -30,21 +30,21 @@ /*! | ||
* | ||
* 1. FieldIncompatibility | ||
* 1. FieldDiscrepancy | ||
* | ||
* `FieldIncompatibility` represents the differences between two `TreeFieldStoredSchema` objects. It consists of | ||
* `FieldDiscrepancy` represents the differences between two `TreeFieldStoredSchema` objects. It consists of | ||
* three types of incompatibilities: | ||
* | ||
* - FieldKindIncompatibility: Indicates the differences in `FieldKindIdentifier` between two `TreeFieldStoredSchema` | ||
* - FieldKindDiscrepancy: Indicates the differences in `FieldKindIdentifier` between two `TreeFieldStoredSchema` | ||
* objects (e.g., optional, required, sequence, etc.). | ||
* - AllowedTypesIncompatibility: Indicates the differences in the allowed child types between the two schemas. | ||
* - ValueSchemaIncompatibility: Specifically indicates the differences in the `ValueSchema` of two | ||
* - AllowedTypesDiscrepancy: Indicates the differences in the allowed child types between the two schemas. | ||
* - ValueSchemaDiscrepancy: Specifically indicates the differences in the `ValueSchema` of two | ||
* `LeafNodeStoredSchema` objects. | ||
* | ||
* 2. NodeIncompatibility | ||
* 2. NodeDiscrepancy | ||
* | ||
* `NodeIncompatibility` represents the differences between two `TreeNodeStoredSchema` objects and includes: | ||
* `NodeDiscrepancy` represents the differences between two `TreeNodeStoredSchema` objects and includes: | ||
* | ||
* - NodeKindIncompatibility: Indicates the differences in the types of `TreeNodeStoredSchema` (currently supports | ||
* - NodeKindDiscrepancy: Indicates the differences in the types of `TreeNodeStoredSchema` (currently supports | ||
* `ObjectNodeStoredSchema`, `MapNodeStoredSchema`, and `LeafNodeStoredSchema`). | ||
* - NodeFieldsIncompatibility: Indicates the `FieldIncompatibility` of `TreeFieldStoredSchema` within two | ||
* `TreeNodeStoredSchema`. It includes an array of `FieldIncompatibility` instances in the `differences` field. | ||
* - NodeFieldsDiscrepancy: Indicates the `FieldDiscrepancy` of `TreeFieldStoredSchema` within two | ||
* `TreeNodeStoredSchema`. It includes an array of `FieldDiscrepancy` instances in the `differences` field. | ||
* | ||
@@ -54,18 +54,18 @@ * When comparing two nodes for compatibility, it only makes sense to compare their fields if the nodes are of | ||
* | ||
* 3. Incompatibility | ||
* 3. Discrepancy | ||
* | ||
* Incompatibility consists of both `NodeIncompatibility` and `FieldIncompatibility`, representing any kind of | ||
* schema differences. See {@link getAllowedContentIncompatibilities} for more details about how we process it | ||
* Discrepancy consists of both `NodeDiscrepancy` and `FieldDiscrepancy`, representing any kind of | ||
* schema differences. See {@link getAllowedContentDiscrepancies} for more details about how we process it | ||
* and the ordering. | ||
*/ | ||
export type Incompatibility = FieldIncompatibility | NodeIncompatibility; | ||
export type Discrepancy = FieldDiscrepancy | NodeDiscrepancy; | ||
export type NodeIncompatibility = NodeKindIncompatibility | NodeFieldsIncompatibility; | ||
export type NodeDiscrepancy = NodeKindDiscrepancy | NodeFieldsDiscrepancy; | ||
export type FieldIncompatibility = | ||
| AllowedTypeIncompatibility | ||
| FieldKindIncompatibility | ||
| ValueSchemaIncompatibility; | ||
export type FieldDiscrepancy = | ||
| AllowedTypeDiscrepancy | ||
| FieldKindDiscrepancy | ||
| ValueSchemaDiscrepancy; | ||
export interface AllowedTypeIncompatibility { | ||
export interface AllowedTypeDiscrepancy { | ||
identifier: string | undefined; // undefined indicates root field schema | ||
@@ -83,3 +83,3 @@ mismatch: "allowedTypes"; | ||
export interface FieldKindIncompatibility { | ||
export interface FieldKindDiscrepancy { | ||
identifier: string | undefined; // undefined indicates root field schema | ||
@@ -91,3 +91,3 @@ mismatch: "fieldKind"; | ||
export interface ValueSchemaIncompatibility { | ||
export interface ValueSchemaDiscrepancy { | ||
identifier: string; | ||
@@ -99,3 +99,3 @@ mismatch: "valueSchema"; | ||
export interface NodeKindIncompatibility { | ||
export interface NodeKindDiscrepancy { | ||
identifier: string; | ||
@@ -107,6 +107,6 @@ mismatch: "nodeKind"; | ||
export interface NodeFieldsIncompatibility { | ||
export interface NodeFieldsDiscrepancy { | ||
identifier: string; | ||
mismatch: "fields"; | ||
differences: FieldIncompatibility[]; | ||
differences: FieldDiscrepancy[]; | ||
} | ||
@@ -117,12 +117,12 @@ | ||
/** | ||
* @remarks | ||
* Finds and reports discrepancies between a view schema and a stored schema. | ||
* | ||
* The workflow for finding schema incompatibilities: | ||
* 1. Compare the two root schemas to identify any `FieldIncompatibility`. | ||
* 1. Compare the two root schemas to identify any `FieldDiscrepancy`. | ||
* | ||
* 2. For each node schema in the `view`: | ||
* - Verify if the node schema exists in the stored. If it does, ensure that the `SchemaFactoryNodeKind` are | ||
* consistent. Otherwise this difference is treated as `NodeKindIncompatibility` | ||
* consistent. Otherwise this difference is treated as `NodeKindDiscrepancy` | ||
* - If a node schema with the same identifier exists in both view and stored, and their `SchemaFactoryNodeKind` | ||
* are consistent, perform a exhaustive validation to identify all `FieldIncompatibility`. | ||
* are consistent, perform a exhaustive validation to identify all `FieldDiscrepancy`. | ||
* | ||
@@ -134,12 +134,10 @@ * 3. For each node schema in the stored, verify if it exists in the view. The overlapping parts were already | ||
*/ | ||
export function getAllowedContentIncompatibilities( | ||
export function getAllowedContentDiscrepancies( | ||
view: TreeStoredSchema, | ||
stored: TreeStoredSchema, | ||
): Incompatibility[] { | ||
const incompatibilities: Incompatibility[] = []; | ||
): Discrepancy[] { | ||
const discrepancies: Discrepancy[] = []; | ||
// check root schema discrepancies | ||
incompatibilities.push( | ||
...trackFieldDiscrepancies(view.rootFieldSchema, stored.rootFieldSchema), | ||
); | ||
discrepancies.push(...trackFieldDiscrepancies(view.rootFieldSchema, stored.rootFieldSchema)); | ||
@@ -154,3 +152,3 @@ // Verify the existence and type of a node schema given its identifier (key), then determine if | ||
if (!stored.nodeSchema.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -168,3 +166,3 @@ mismatch: "nodeKind", | ||
if (storedNodeSchema instanceof MapNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -174,5 +172,5 @@ mismatch: "nodeKind", | ||
stored: "map", | ||
} satisfies NodeKindIncompatibility); | ||
} satisfies NodeKindDiscrepancy); | ||
} else if (storedNodeSchema instanceof LeafNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -182,11 +180,11 @@ mismatch: "nodeKind", | ||
stored: "leaf", | ||
} satisfies NodeKindIncompatibility); | ||
} satisfies NodeKindDiscrepancy); | ||
} else if (storedNodeSchema instanceof ObjectNodeStoredSchema) { | ||
const differences = trackObjectNodeDiscrepancies(viewNodeSchema, storedNodeSchema); | ||
if (differences.length > 0) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
mismatch: "fields", | ||
differences, | ||
} satisfies NodeFieldsIncompatibility); | ||
} satisfies NodeFieldsDiscrepancy); | ||
} | ||
@@ -199,3 +197,3 @@ } else { | ||
if (!stored.nodeSchema.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -205,3 +203,3 @@ mismatch: "nodeKind", | ||
stored: undefined, | ||
} satisfies NodeKindIncompatibility); | ||
} satisfies NodeKindDiscrepancy); | ||
} else { | ||
@@ -214,3 +212,3 @@ const storedNodeSchema = stored.nodeSchema.get(key); | ||
if (storedNodeSchema instanceof ObjectNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -220,5 +218,5 @@ mismatch: "nodeKind", | ||
stored: "object", | ||
} satisfies NodeKindIncompatibility); | ||
} satisfies NodeKindDiscrepancy); | ||
} else if (storedNodeSchema instanceof LeafNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -228,5 +226,5 @@ mismatch: "nodeKind", | ||
stored: "leaf", | ||
} satisfies NodeKindIncompatibility); | ||
} satisfies NodeKindDiscrepancy); | ||
} else if (storedNodeSchema instanceof MapNodeStoredSchema) { | ||
incompatibilities.push( | ||
discrepancies.push( | ||
...trackFieldDiscrepancies( | ||
@@ -244,3 +242,3 @@ viewNodeSchema.mapFields, | ||
if (!stored.nodeSchema.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -258,3 +256,3 @@ mismatch: "nodeKind", | ||
if (storedNodeSchema instanceof MapNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -264,5 +262,5 @@ mismatch: "nodeKind", | ||
stored: "map", | ||
} satisfies NodeKindIncompatibility); | ||
} satisfies NodeKindDiscrepancy); | ||
} else if (storedNodeSchema instanceof ObjectNodeStoredSchema) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -272,6 +270,6 @@ mismatch: "nodeKind", | ||
stored: "object", | ||
} satisfies NodeKindIncompatibility); | ||
} satisfies NodeKindDiscrepancy); | ||
} else if (storedNodeSchema instanceof LeafNodeStoredSchema) { | ||
if (viewNodeSchema.leafValue !== storedNodeSchema.leafValue) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -281,3 +279,3 @@ mismatch: "valueSchema", | ||
stored: storedNodeSchema.leafValue, | ||
} satisfies ValueSchemaIncompatibility); | ||
} satisfies ValueSchemaDiscrepancy); | ||
} | ||
@@ -295,3 +293,3 @@ } else { | ||
if (!viewNodeKeys.has(key)) { | ||
incompatibilities.push({ | ||
discrepancies.push({ | ||
identifier: key, | ||
@@ -306,7 +304,7 @@ mismatch: "nodeKind", | ||
: "leaf", | ||
} satisfies NodeKindIncompatibility); | ||
} satisfies NodeKindDiscrepancy); | ||
} | ||
} | ||
return incompatibilities; | ||
return discrepancies; | ||
} | ||
@@ -323,4 +321,4 @@ | ||
keyOrRoot?: string, | ||
): FieldIncompatibility[] { | ||
const differences: FieldIncompatibility[] = []; | ||
): FieldDiscrepancy[] { | ||
const differences: FieldDiscrepancy[] = []; | ||
@@ -344,3 +342,3 @@ // Only track the symmetric differences of two sets. | ||
stored: allowedTypesDiscrepancies[1], | ||
} satisfies AllowedTypeIncompatibility); | ||
} satisfies AllowedTypeDiscrepancy); | ||
} | ||
@@ -354,3 +352,3 @@ | ||
stored: stored.kind, | ||
} satisfies FieldKindIncompatibility); | ||
} satisfies FieldKindDiscrepancy); | ||
} | ||
@@ -364,4 +362,4 @@ | ||
stored: ObjectNodeStoredSchema, | ||
): FieldIncompatibility[] { | ||
const differences: FieldIncompatibility[] = []; | ||
): FieldDiscrepancy[] { | ||
const differences: FieldDiscrepancy[] = []; | ||
const viewFieldKeys = new Set<FieldKey>(); | ||
@@ -390,3 +388,3 @@ /** | ||
stored: storedEmptyFieldSchema.kind, | ||
} satisfies FieldKindIncompatibility); | ||
} satisfies FieldKindDiscrepancy); | ||
} else { | ||
@@ -414,3 +412,3 @@ differences.push( | ||
stored: fieldStoredSchema.kind, | ||
} satisfies FieldKindIncompatibility); | ||
} satisfies FieldKindDiscrepancy); | ||
} | ||
@@ -440,8 +438,8 @@ } | ||
export function isRepoSuperset(view: TreeStoredSchema, stored: TreeStoredSchema): boolean { | ||
const incompatibilities = getAllowedContentIncompatibilities(view, stored); | ||
const discrepancies = getAllowedContentDiscrepancies(view, stored); | ||
for (const incompatibility of incompatibilities) { | ||
switch (incompatibility.mismatch) { | ||
for (const discrepancy of discrepancies) { | ||
switch (discrepancy.mismatch) { | ||
case "nodeKind": { | ||
if (incompatibility.stored !== undefined) { | ||
if (discrepancy.stored !== undefined) { | ||
// It's fine for the view schema to know of a node type that the stored schema doesn't know about. | ||
@@ -455,3 +453,3 @@ return false; | ||
case "fieldKind": { | ||
if (!validateFieldIncompatibility(incompatibility)) { | ||
if (!validateFieldIncompatibility(discrepancy)) { | ||
return false; | ||
@@ -463,3 +461,3 @@ } | ||
if ( | ||
incompatibility.differences.some( | ||
discrepancy.differences.some( | ||
(difference) => !validateFieldIncompatibility(difference), | ||
@@ -478,4 +476,4 @@ ) | ||
function validateFieldIncompatibility(incompatibility: FieldIncompatibility): boolean { | ||
switch (incompatibility.mismatch) { | ||
function validateFieldIncompatibility(discrepancy: FieldDiscrepancy): boolean { | ||
switch (discrepancy.mismatch) { | ||
case "allowedTypes": { | ||
@@ -485,6 +483,6 @@ // Since we only track the symmetric difference between the allowed types in the view and | ||
// stored schema. | ||
return incompatibility.stored.length === 0; | ||
return discrepancy.stored.length === 0; | ||
} | ||
case "fieldKind": { | ||
return posetLte(incompatibility.stored, incompatibility.view, fieldRealizer); | ||
return posetLte(discrepancy.stored, discrepancy.view, fieldRealizer); | ||
} | ||
@@ -491,0 +489,0 @@ case "valueSchema": { |
@@ -77,2 +77,5 @@ /*! | ||
} from "./fieldKindConfiguration.js"; | ||
export { getAllowedContentIncompatibilities, isRepoSuperset } from "./discrepancies.js"; | ||
export { | ||
getAllowedContentDiscrepancies, | ||
isRepoSuperset, | ||
} from "./discrepancies.js"; |
@@ -9,2 +9,2 @@ /*! | ||
export const pkgName = "@fluidframework/tree"; | ||
export const pkgVersion = "2.10.0-306579"; | ||
export const pkgVersion = "2.10.0-307060"; |
@@ -6,3 +6,3 @@ /*! | ||
import { assert, unreachableCase } from "@fluidframework/core-utils/internal"; | ||
import { unreachableCase } from "@fluidframework/core-utils/internal"; | ||
import { UsageError } from "@fluidframework/telemetry-utils/internal"; | ||
@@ -468,6 +468,8 @@ | ||
const node = getOrCreateInnerNode(constraint.node); | ||
assert( | ||
treeApi.status(constraint.node) === TreeStatus.InDocument, | ||
0x90f /* Attempted to apply "nodeExists" constraint when building a transaction, but the node is not in the document. */, | ||
); | ||
const nodeStatus = treeApi.status(constraint.node); | ||
if (nodeStatus !== TreeStatus.InDocument) { | ||
throw new UsageError( | ||
`Attempted to add a "nodeInDocument" constraint, but the node is not currently in the document. Node status: ${nodeStatus}`, | ||
); | ||
} | ||
checkout.editor.addNodeExistsConstraint(node.anchorNode); | ||
@@ -474,0 +476,0 @@ break; |
@@ -76,4 +76,4 @@ /*! | ||
* @remarks | ||
* Currently only supports `string` enums. | ||
* The string value of the enum is used as the name of the schema: callers must ensure that it is stable and unique. | ||
* Numeric enums values have the value implicitly converted into a string. | ||
* Consider making a dedicated schema factory with a nested scope to avoid the enum members colliding with other schema. | ||
@@ -91,3 +91,3 @@ * @example | ||
* // Defined the types of the nodes which correspond to this the schema. | ||
* type ModeNodes = NodeFromSchema<(typeof ModeNodes.schema)[number]>; | ||
* type ModeNodes = TreeNodeFromImplicitAllowedTypes<(typeof ModeNodes.schema)>; | ||
* // An example schema which has an enum as a child. | ||
@@ -111,4 +111,2 @@ * class Parent extends schemaFactory.object("Parent", { | ||
* @privateRemarks | ||
* TODO: | ||
* Extend this to support numeric enums. | ||
* Maybe provide `SchemaFactory.nested` to ease creating nested scopes? | ||
@@ -145,5 +143,8 @@ * @see {@link enumFromStrings} for a similar function that works on arrays of strings instead of an enum. | ||
const factoryOut = <TValue extends Values>(value: TValue) => { | ||
return new out[inverse.get(value) ?? fail("missing enum value")]() as NodeFromSchema< | ||
ReturnType<typeof singletonSchema<TScope, TValue>> | ||
>; | ||
return new out[ | ||
inverse.get(value) ?? fail("missing enum value") | ||
// "extends unknown" is required here to handle when TValue is an union: each member of the union should be processed independently. | ||
]() as TValue extends unknown | ||
? NodeFromSchema<ReturnType<typeof singletonSchema<TScope, TValue>>> | ||
: never; | ||
}; | ||
@@ -183,3 +184,3 @@ const out = factoryOut as typeof factoryOut & TOut & { readonly schema: SchemaArray }; | ||
* const Mode = enumFromStrings(schemaFactory, ["Fun", "Cool"]); | ||
* type Mode = NodeFromSchema<(typeof Mode.schema)[number]>; | ||
* type Mode = TreeNodeFromImplicitAllowedTypes<typeof Mode.schema>; | ||
* const nodeFromString: Mode = Mode("Fun"); | ||
@@ -206,17 +207,28 @@ * const nodeFromSchema: Mode = new Mode.Fun(); | ||
type TOut = Record< | ||
Members[number], | ||
ReturnType<typeof singletonSchema<TScope, Members[number]>> | ||
>; | ||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
const factoryOut = <TValue extends Members[number]>(value: TValue) => { | ||
return new out[value]() as NodeFromSchema< | ||
ReturnType<typeof singletonSchema<TScope, TValue>> | ||
type MembersUnion = Members[number]; | ||
// Get all keys of the Members tuple which are numeric strings as union of numbers: | ||
type Indexes = Extract<keyof Members, `${number}`> extends `${infer N extends number}` | ||
? N | ||
: never; | ||
type TOut = { | ||
[Index in Indexes as Members[Index]]: ReturnType< | ||
typeof singletonSchema<TScope, Members[Index] & string> | ||
>; | ||
}; | ||
type SchemaArray = UnionToTuple<TOut[Members[number]]>; | ||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
const factoryOut = <TValue extends MembersUnion>(value: TValue) => { | ||
// "extends unknown" is required here to handle when TValue is an union: each member of the union should be processed independently. | ||
return new recordOut[value]() as TValue extends unknown | ||
? NodeFromSchema<ReturnType<typeof singletonSchema<TScope, TValue>>> | ||
: never; | ||
}; | ||
type SchemaArray = UnionToTuple<MembersUnion extends unknown ? TOut[MembersUnion] : never>; | ||
const schemaArray: TreeNodeSchema[] = []; | ||
const out = factoryOut as typeof factoryOut & TOut & { readonly schema: SchemaArray }; | ||
const recordOut = out as Record<MembersUnion, new () => unknown>; | ||
for (const name of members) { | ||
@@ -223,0 +235,0 @@ const schema = singletonSchema(factory, name); |
@@ -807,2 +807,4 @@ /*! | ||
true, | ||
// Setting this (implicitlyConstructable) to true seems to work ok currently, but not for other node kinds. | ||
// Supporting this could be fragile and might break other future changes, so it's being kept as false for now. | ||
false, | ||
@@ -815,20 +817,25 @@ ); | ||
TreeMapNodeUnsafe<T> & WithType<ScopedSchemaName<TScope, Name>, NodeKind.Map>, | ||
{ | ||
/** | ||
* Iterator for the iterable of content for this node. | ||
* @privateRemarks | ||
* Wrapping the constructor parameter for recursive arrays and maps in an inlined object type avoids (for unknown reasons) | ||
* the following compile error when declaring the recursive schema: | ||
* `Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.` | ||
* To benefit from this without impacting the API, the definition of `Iterable` has been inlined as such an object. | ||
* | ||
* If this workaround is kept, ideally this comment would be deduplicated with the other instance of it. | ||
* Unfortunately attempts to do this failed to avoid the compile error this was introduced to solve. | ||
*/ | ||
[Symbol.iterator](): Iterator< | ||
[string, InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>] | ||
>; | ||
}, | ||
// Ideally this would be included, but doing so breaks recursive types. | ||
// | RestrictiveStringRecord<InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>>, | ||
| { | ||
/** | ||
* Iterator for the iterable of content for this node. | ||
* @privateRemarks | ||
* Wrapping the constructor parameter for recursive arrays and maps in an inlined object type avoids (for unknown reasons) | ||
* the following compile error when declaring the recursive schema: | ||
* `Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.` | ||
* To benefit from this without impacting the API, the definition of `Iterable` has been inlined as such an object. | ||
* | ||
* If this workaround is kept, ideally this comment would be deduplicated with the other instance of it. | ||
* Unfortunately attempts to do this failed to avoid the compile error this was introduced to solve. | ||
*/ | ||
[Symbol.iterator](): Iterator< | ||
[string, InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>] | ||
>; | ||
} | ||
// Ideally this would be | ||
// RestrictiveStringRecord<InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>>, | ||
// but doing so breaks recursive types. | ||
// Instead we do a less nice version: | ||
| { | ||
readonly [P in string]: InsertableTreeNodeFromImplicitAllowedTypesUnsafe<T>; | ||
}, | ||
false, | ||
@@ -835,0 +842,0 @@ T, |
@@ -143,3 +143,3 @@ /*! | ||
}[T["kind"]], | ||
// ImplicitlyConstructable: recursive types are not implicitly constructable. | ||
// ImplicitlyConstructable: recursive types are currently not implicitly constructable. | ||
false, | ||
@@ -146,0 +146,0 @@ // Info: What's passed to the method to create the schema. Constraining these here should be about as effective as if the actual constraints existed on the actual method itself. |
@@ -20,3 +20,2 @@ /*! | ||
flexTreeSlot, | ||
isFlexTreeNode, | ||
isFreedSymbol, | ||
@@ -75,6 +74,11 @@ LazyEntity, | ||
/** The {@link HydrationState} of a {@link TreeNodeKernel} before the kernel is hydrated */ | ||
type UnhydratedState = Off; | ||
interface UnhydratedState { | ||
off: Off; | ||
innerNode: UnhydratedFlexTreeNode; | ||
} | ||
/** The {@link HydrationState} of a {@link TreeNodeKernel} after the kernel is hydrated */ | ||
interface HydratedState { | ||
/** The flex node for this kernel (lazy - undefined if it has not yet been demanded) */ | ||
innerNode?: FlexTreeNode; | ||
/** The {@link AnchorNode} that this node is associated with. */ | ||
@@ -91,3 +95,3 @@ anchorNode: AnchorNode; | ||
function isHydrated(state: HydrationState): state is HydratedState { | ||
return typeof state === "object"; | ||
return (state as Partial<HydratedState>).anchorNode !== undefined; | ||
} | ||
@@ -99,3 +103,2 @@ | ||
* The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states. | ||
* When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method. | ||
*/ | ||
@@ -117,3 +120,3 @@ export class TreeNodeKernel { | ||
#hydrationState: HydrationState = () => {}; | ||
#hydrationState: HydrationState; | ||
@@ -141,3 +144,3 @@ /** | ||
public readonly schema: TreeNodeSchema, | ||
private innerNode: InnerNode, | ||
innerNode: InnerNode, | ||
private readonly initialContext: Context, | ||
@@ -153,5 +156,5 @@ ) { | ||
// These will be fired if the unhydrated node is edited, and will also be forwarded later to the hydrated node. | ||
this.#hydrationState = innerNode.events.on( | ||
"childrenChangedAfterBatch", | ||
({ changedFields }) => { | ||
this.#hydrationState = { | ||
innerNode, | ||
off: innerNode.events.on("childrenChangedAfterBatch", ({ changedFields }) => { | ||
this.#unhydratedEvents.value.emit("childrenChangedAfterBatch", { | ||
@@ -171,11 +174,12 @@ changedFields, | ||
} | ||
}, | ||
); | ||
}), | ||
}; | ||
} else { | ||
// Hydrated case | ||
const { anchorNode } = innerNode; | ||
assert( | ||
!innerNode.anchorNode.slots.has(proxySlot), | ||
!anchorNode.slots.has(proxySlot), | ||
0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */, | ||
); | ||
this.hydrate(innerNode.anchorNode); | ||
this.#hydrationState = this.createHydratedState(anchorNode); | ||
} | ||
@@ -202,24 +206,8 @@ } | ||
*/ | ||
public hydrate(anchorNode: AnchorNode): void { | ||
private hydrate(anchorNode: AnchorNode): void { | ||
assert(!this.disposed, 0xa2a /* cannot hydrate a disposed node */); | ||
assert(!isHydrated(this.#hydrationState), 0xa2b /* hydration should only happen once */); | ||
mapTreeNodeToProxy.delete(this.#hydrationState.innerNode); | ||
this.#hydrationState = this.createHydratedState(anchorNode); | ||
// If the this node is raw and thus has a MapTreeNode, forget it: | ||
if (this.innerNode instanceof UnhydratedFlexTreeNode) { | ||
mapTreeNodeToProxy.delete(this.innerNode); | ||
} | ||
// However, it's fine for an anchor node to rotate through different proxies when the content at that place in the tree is replaced. | ||
anchorNode.slots.set(proxySlot, this.node); | ||
this.#hydrationState = { | ||
anchorNode, | ||
offAnchorNode: new Set([ | ||
anchorNode.events.on("afterDestroy", () => this.dispose()), | ||
// TODO: this should be triggered on change even for unhydrated nodes. | ||
anchorNode.events.on("childrenChanging", () => { | ||
this.generationNumber += 1; | ||
}), | ||
]), | ||
}; | ||
// If needed, register forwarding emitters for events from before hydration | ||
@@ -240,2 +228,16 @@ if (this.#unhydratedEvents.evaluated) { | ||
private createHydratedState(anchorNode: AnchorNode): HydratedState { | ||
anchorNode.slots.set(proxySlot, this.node); | ||
return { | ||
anchorNode, | ||
offAnchorNode: new Set([ | ||
anchorNode.events.on("afterDestroy", () => this.dispose()), | ||
// TODO: this should be triggered on change even for unhydrated nodes. | ||
anchorNode.events.on("childrenChanging", () => { | ||
this.generationNumber += 1; | ||
}), | ||
]), | ||
}; | ||
} | ||
public getStatus(): TreeStatus { | ||
@@ -295,9 +297,8 @@ if (this.disposed) { | ||
public getOrCreateInnerNode(allowFreed = false): InnerNode { | ||
if (!(this.innerNode instanceof UnhydratedFlexTreeNode)) { | ||
// Cooked case | ||
return this.innerNode; | ||
if (!isHydrated(this.#hydrationState)) { | ||
return this.#hydrationState.innerNode; // Unhydrated case | ||
} | ||
if (!isHydrated(this.#hydrationState)) { | ||
return this.innerNode; | ||
if (this.#hydrationState.innerNode !== undefined) { | ||
return this.#hydrationState.innerNode; // Cooked case | ||
} | ||
@@ -310,17 +311,19 @@ | ||
if (flexNode !== undefined) { | ||
this.innerNode = flexNode; | ||
return flexNode; // If it does have a flex node, return it... | ||
} // ...otherwise, the flex node must be created | ||
const context = anchorNode.anchorSet.slots.get(ContextSlot) ?? fail("missing context"); | ||
const cursor = context.checkout.forest.allocateCursor("getFlexNode"); | ||
context.checkout.forest.moveCursorToPath(anchorNode, cursor); | ||
const newFlexNode = makeTree(context, cursor); | ||
cursor.free(); | ||
this.innerNode = newFlexNode; | ||
// Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode | ||
anchorForgetters?.get(this.node)?.(); | ||
if (!allowFreed) { | ||
assertFlexTreeEntityNotFreed(newFlexNode); | ||
// If the flex node already exists, use it... | ||
this.#hydrationState.innerNode = flexNode; | ||
} else { | ||
// ...otherwise, the flex node must be created | ||
const context = anchorNode.anchorSet.slots.get(ContextSlot) ?? fail("missing context"); | ||
const cursor = context.checkout.forest.allocateCursor("getFlexNode"); | ||
context.checkout.forest.moveCursorToPath(anchorNode, cursor); | ||
this.#hydrationState.innerNode = makeTree(context, cursor); | ||
cursor.free(); | ||
// Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode | ||
anchorForgetters?.get(this.node)?.(); | ||
if (!allowFreed) { | ||
assertFlexTreeEntityNotFreed(this.#hydrationState.innerNode); | ||
} | ||
} | ||
return newFlexNode; | ||
return this.#hydrationState.innerNode; | ||
} | ||
@@ -359,20 +362,15 @@ | ||
* @remarks | ||
* If `target` is a unhydrated node, returns its MapTreeNode. | ||
* If `target` is an unhydrated node, returns its UnhydratedFlexTreeNode. | ||
* If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode. | ||
* If the target is not a node, or a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
* If the target is a marinated node with no FlexTreeNode for its anchor, returns undefined. | ||
*/ | ||
public tryGetInnerNode(): InnerNode | undefined { | ||
if (isFlexTreeNode(this.innerNode)) { | ||
// Cooked case | ||
return this.innerNode; | ||
if (isHydrated(this.#hydrationState)) { | ||
return ( | ||
this.#hydrationState.innerNode ?? | ||
this.#hydrationState.anchorNode.slots.get(flexTreeSlot) | ||
); | ||
} | ||
if (!isHydrated(this.#hydrationState)) { | ||
return this.innerNode; | ||
} | ||
// Marinated case -> cooked | ||
const anchorNode = this.#hydrationState.anchorNode; | ||
// The proxy is bound to an anchor node, but it may or may not have an actual flex node yet | ||
return anchorNode.slots.get(flexTreeSlot); | ||
return this.#hydrationState.innerNode; | ||
} | ||
@@ -379,0 +377,0 @@ } |
@@ -50,3 +50,3 @@ /*! | ||
/** | ||
* Helper used to produce types for object nodes. | ||
* Generates the properties for an ObjectNode from its field schema object. | ||
* @system @public | ||
@@ -53,0 +53,0 @@ */ |
@@ -61,3 +61,5 @@ /*! | ||
export const ObjectNodeSchema = { | ||
// instanceof-based narrowing support for Javascript and TypeScript 5.3 or newer. | ||
/** | ||
* instanceof-based narrowing support for ObjectNodeSchema in Javascript and TypeScript 5.3 or newer. | ||
*/ | ||
[Symbol.hasInstance](value: TreeNodeSchema): value is ObjectNodeSchema { | ||
@@ -64,0 +66,0 @@ return isObjectNodeSchema(value); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
14481518
2517
148408
+ Added@fluid-internal/client-utils@2.10.0-307060(transitive)
+ Added@fluidframework/container-definitions@2.10.0-307060(transitive)
+ Added@fluidframework/container-runtime@2.10.0-307060(transitive)
+ Added@fluidframework/container-runtime-definitions@2.10.0-307060(transitive)
+ Added@fluidframework/core-interfaces@2.10.0-307060(transitive)
+ Added@fluidframework/core-utils@2.10.0-307060(transitive)
+ Added@fluidframework/datastore@2.10.0-307060(transitive)
+ Added@fluidframework/datastore-definitions@2.10.0-307060(transitive)
+ Added@fluidframework/driver-definitions@2.10.0-307060(transitive)
+ Added@fluidframework/driver-utils@2.10.0-307060(transitive)
+ Added@fluidframework/id-compressor@2.10.0-307060(transitive)
+ Added@fluidframework/runtime-definitions@2.10.0-307060(transitive)
+ Added@fluidframework/runtime-utils@2.10.0-307060(transitive)
+ Added@fluidframework/shared-object-base@2.10.0-307060(transitive)
+ Added@fluidframework/telemetry-utils@2.10.0-307060(transitive)
- Removed@fluid-internal/client-utils@2.10.0-306579(transitive)
- Removed@fluidframework/container-definitions@2.10.0-306579(transitive)
- Removed@fluidframework/container-runtime@2.10.0-306579(transitive)
- Removed@fluidframework/container-runtime-definitions@2.10.0-306579(transitive)
- Removed@fluidframework/core-interfaces@2.10.0-306579(transitive)
- Removed@fluidframework/core-utils@2.10.0-306579(transitive)
- Removed@fluidframework/datastore@2.10.0-306579(transitive)
- Removed@fluidframework/datastore-definitions@2.10.0-306579(transitive)
- Removed@fluidframework/driver-definitions@2.10.0-306579(transitive)
- Removed@fluidframework/driver-utils@2.10.0-306579(transitive)
- Removed@fluidframework/id-compressor@2.10.0-306579(transitive)
- Removed@fluidframework/runtime-definitions@2.10.0-306579(transitive)
- Removed@fluidframework/runtime-utils@2.10.0-306579(transitive)
- Removed@fluidframework/shared-object-base@2.10.0-306579(transitive)
- Removed@fluidframework/telemetry-utils@2.10.0-306579(transitive)