Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tree: Add alpha tree configuration APIs #22701

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .changeset/clever-dancers-post.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
"fluid-framework": minor
"@fluidframework/tree": minor
---
---
"section": tree
---

Add alpha API for providing SharedTree configuration options

A new alpha `configuredSharedTree` had been added.
This allows providing configuration options, primarily for debugging, testing and evaluation of upcoming features.
The resulting configured `SharedTree` object can then be used in-place of the regular `SharedTree` imported from `fluid-framework`.

```typescript
import {
ForestType,
TreeCompressionStrategy,
configuredSharedTree,
typeboxValidator,
} from "@fluid-framework/alpha";
// Maximum debuggability and validation enabled:
const SharedTree = configuredSharedTree({
forest: ForestType.Expensive,
jsonValidator: typeboxValidator,
treeEncodeType: TreeCompressionStrategy.Uncompressed,
});
// Opts into the under development optimized tree storage planned to be the eventual default implementation:
const SharedTree = configuredSharedTree({
forest: ForestType.Optimized,
});
```
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"cSpell.words": [
"boop",
"contoso",
"debuggability",
"denormalized",
"endregion",
"fluidframework",
Expand Down
58 changes: 58 additions & 0 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ type FlexList<Item = unknown> = readonly LazyItem<Item>[];
// @public
type FlexListToUnion<TList extends FlexList> = ExtractItemType<TList[number]>;

// @alpha
export interface ForestOptions {
readonly forest?: ForestType;
}

// @alpha
export enum ForestType {
Expensive = 2,
Optimized = 1,
Reference = 0
}

// @alpha
export function getBranch(tree: ITree): TreeBranch;

Expand All @@ -101,6 +113,11 @@ export function getBranch(view: TreeView<ImplicitFieldSchema>): TreeBranch;
// @alpha
export function getJsonSchema(schema: ImplicitFieldSchema): JsonTreeSchema;

// @alpha
export interface ICodecOptions {
readonly jsonValidator: JsonValidator;
}

// @public
export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema;

Expand Down Expand Up @@ -272,6 +289,11 @@ export type JsonTreeSchema = JsonFieldSchema & {
readonly $defs: Record<JsonSchemaId, JsonNodeSchema>;
};

// @alpha
export interface JsonValidator {
compile<Schema extends TSchema>(schema: Schema): SchemaValidationFunction<Schema>;
}

// @public
export type LazyItem<Item = unknown> = Item | (() => Item);

Expand Down Expand Up @@ -325,6 +347,9 @@ export enum NodeKind {
Object = 2
}

// @alpha
export const noopValidator: JsonValidator;

// @public
type ObjectFromSchemaRecord<T extends RestrictiveStringRecord<ImplicitFieldSchema>> = {
-readonly [Property in keyof T]: Property extends string ? TreeFieldFromImplicitField<T[Property]> : unknown;
Expand Down Expand Up @@ -443,9 +468,33 @@ export class SchemaFactory<out TScope extends string | undefined = string | unde
readonly string: TreeNodeSchema<"com.fluidframework.leaf.string", NodeKind.Leaf, string, string>;
}

// @alpha
export interface SchemaValidationFunction<Schema extends TSchema> {
check(data: unknown): data is Static<Schema>;
}

// @public
type ScopedSchemaName<TScope extends string | undefined, TName extends number | string> = TScope extends undefined ? `${TName}` : `${TScope}.${TName}`;

// @alpha
export interface SharedTreeFormatOptions {
formatVersion: SharedTreeFormatVersion[keyof SharedTreeFormatVersion];
treeEncodeType: TreeCompressionStrategy;
}

// @alpha
export const SharedTreeFormatVersion: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we default to V3 (thus dubbing it our actual "V1") and that we do not support documents in V1 (and maybe V2?), how would you feel about only exposing V3?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My instinct is this is a foot-gun, otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think we should just expose a single framework wide enum which lists every framework minor version which had an opt in compat impacting change, and its value of v2.0 would map to v3 in our internal format numbers.

Fixing that is mostly orthogonal to this change.

That said, if someone has legacy documents (from before 2.0), and wants to generate test documents so they can ensure their old production documents will continue to work, these extra option might be useful to them.

Since this is just alpha, I think we can sort out these details later, but I do agree this isn't want we want long term and/or when this goes public.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we haven't done the work needed to create separate public vs internal configuration, but as long as these are targeted at alpha / debugging usage I think it may be better to keep using the internal types here rather than adding all the complexity needed to create distinct public API configuration, at least for now.

I have added a TODO to the APi's private remarks. I'll let API reviewers decide if they want to block exposing these as alpha on that work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you set this to V1? Do we support writing legacy documents (which are immediately broken/unsupported)? I thought we only support writing N and N-1.
I'll agree that allowing V2 writing seems fine for the (possibly nonexistent) customers that want to test support for that, but exposing V1 at all seems very strange to me. @noencke thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(regardless, I did approve and don't think this needs to block this since it's alpha)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't support V1 then I don't see why we'd expose V1, but exposing V2 makes sense for now, particularly since this is alpha.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we leave some of the context from this discussion in @privateRemarks in the code for future reference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we leave some of the context from this discussion in @privateRemarks in the code for future reference?

Is there something more you want beyond what's already in fe8daec ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, no, I didn't see that. Looks good to me!

readonly v1: 1;
readonly v2: 2;
readonly v3: 3;
};

// @alpha
export type SharedTreeFormatVersion = typeof SharedTreeFormatVersion;

// @alpha
export type SharedTreeOptions = Partial<ICodecOptions> & Partial<SharedTreeFormatOptions> & ForestOptions;

// @public
export type TransactionConstraint = NodeInDocumentConstraint;

Expand Down Expand Up @@ -522,6 +571,12 @@ export interface TreeChangeEventsBeta<TNode extends TreeNode = TreeNode> extends
nodeChanged: (data: NodeChangedData<TNode> & (TNode extends WithType<string, NodeKind.Map | NodeKind.Object> ? Required<Pick<NodeChangedData<TNode>, "changedProperties">> : unknown)) => void;
}

// @alpha
export enum TreeCompressionStrategy {
Compressed = 0,
Uncompressed = 1
}

// @public
export type TreeFieldFromImplicitField<TSchema extends ImplicitFieldSchema = FieldSchema> = TSchema extends FieldSchema<infer Kind, infer Types> ? ApplyKind<TreeNodeFromImplicitAllowedTypes<Types>, Kind, false> : TSchema extends ImplicitAllowedTypes ? TreeNodeFromImplicitAllowedTypes<TSchema> : unknown;

Expand Down Expand Up @@ -641,6 +696,9 @@ export interface TreeViewEvents {
schemaChanged(): void;
}

// @alpha
export const typeboxValidator: JsonValidator;

// @public @deprecated
const typeNameSymbol: unique symbol;

Expand Down
8 changes: 4 additions & 4 deletions packages/dds/tree/src/codec/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ export interface IDecoder<TDecoded, TEncoded, TContext> {
/**
* Validates data complies with some particular schema.
* Implementations are typically created by a {@link JsonValidator}.
* @internal
* @alpha
*/
export interface SchemaValidationFunction<Schema extends TSchema> {
/**
* @returns Whether the data matches a schema.
* Returns whether the data matches a schema.
*/
check(data: unknown): data is Static<Schema>;
}

/**
* JSON schema validator compliant with draft 6 schema. See https://json-schema.org.
* @internal
* @alpha
*/
export interface JsonValidator {
/**
Expand All @@ -63,7 +63,7 @@ export interface JsonValidator {

/**
* Options relating to handling of persisted data.
* @internal
* @alpha
*/
export interface ICodecOptions {
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/tree/src/codec/noopValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { JsonValidator } from "./codec.js";
* A {@link JsonValidator} implementation which performs no validation and accepts all data as valid.
* @privateRemarks Having this as an option unifies opting out of validation with selection of
* validators, simplifying code performing validation.
* @internal
* @alpha
*/
export const noopValidator: JsonValidator = {
compile: <Schema extends TSchema>() => ({ check: (data): data is Static<Schema> => true }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import type { JsonValidator } from "../codec/index.js";
*
* Defining this validator in its own file also helps to ensure it is tree-shakeable.
*
* @internal
* @alpha
*/
export const typeboxValidator: JsonValidator = {
compile: <Schema extends TSchema>(schema: Schema) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* Selects which heuristics to use when encoding tree content.
* All encoding options here are compatible with the same decoder:
* the selection here does not impact compatibility.
* @internal
* @alpha
*/
export enum TreeCompressionStrategy {
/**
Expand Down
12 changes: 10 additions & 2 deletions packages/dds/tree/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export {
type NodeInDocumentConstraint,
type RunTransaction,
rollback,
type ForestOptions,
getBranch,
type TreeBranch,
type TreeBranchFork,
Expand Down Expand Up @@ -150,9 +151,16 @@ export {
type JsonLeafSchemaType,
getJsonSchema,
} from "./simple-tree/index.js";
export { SharedTree, configuredSharedTree } from "./treeFactory.js";
export {
SharedTree,
configuredSharedTree,
} from "./treeFactory.js";

export type { ICodecOptions, JsonValidator, SchemaValidationFunction } from "./codec/index.js";
export type {
ICodecOptions,
JsonValidator,
SchemaValidationFunction,
} from "./codec/index.js";
export { noopValidator } from "./codec/index.js";
export { typeboxValidator } from "./external-utilities/index.js";

Expand Down
5 changes: 5 additions & 0 deletions packages/dds/tree/src/shared-tree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export {
type SharedTreeContentSnapshot,
type SharedTreeFormatOptions,
SharedTreeFormatVersion,
buildConfiguredForest,
defaultSharedTreeOptions,
type ForestOptions,
} from "./sharedTree.js";

export {
Expand All @@ -29,6 +32,8 @@ export {

export { type TreeStoredContent } from "./schematizeTree.js";

export { SchematizingSimpleTreeView } from "./schematizingTreeView.js";

export { CheckoutFlexTreeView } from "./checkoutFlexTreeView.js";

export type { ISharedTreeEditor, ISchemaEditor } from "./sharedTreeEditBuilder.js";
Expand Down
Loading
Loading