Skip to content

Commit

Permalink
tree2: Update SchemaBuilder APIs (microsoft#17707)
Browse files Browse the repository at this point in the history
## Description

Before this change, the APIs for building FieldSchema were static and
prefixed with `field`. Both of these distinguishing factors have been
removed making the APIs consistent with how TreeSchema are created.



This continues the recent work of making schema building APIs less
explicit and more concise and implicit.

## Breaking Changes

Static "field" API on SchemaBuilder have been renamed to remove the
"field" prefix.
Additionally these FieldSchema builder methods now take in
ImplicitAllowedTypes, and are no longer are variadic, so callers
providing a number of schema other than exactly 1 will need to provide
the items in an array explicitly.
  • Loading branch information
CraigMacomber authored Oct 11, 2023
1 parent a449d9d commit 310345c
Show file tree
Hide file tree
Showing 44 changed files with 256 additions and 235 deletions.
14 changes: 8 additions & 6 deletions api-report/tree2.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1667,9 +1667,9 @@ export function recordDependency(dependent: ObservingDependent | undefined, depe
const recursiveStruct: TreeSchema<"Test Recursive Domain.struct", {
structFields: {
readonly recursive: FieldSchema<Optional, readonly [() => TreeSchema<"Test Recursive Domain.struct", any>]>;
readonly number: FieldSchema<Required_2, readonly [TreeSchema<"com.fluidframework.leaf.number", {
readonly number: TreeSchema<"com.fluidframework.leaf.number", {
leafValue: import("..").ValueSchema.Number;
}>]>;
}>;
};
}>;

Expand Down Expand Up @@ -1776,9 +1776,6 @@ export class SchemaBuilder<TScope extends string = string, TName extends number
[""]: T;
};
}>;
static fieldOptional<const T extends AllowedTypes>(...allowedTypes: T): FieldSchema<typeof FieldKinds.optional, T>;
static fieldRequired<const T extends AllowedTypes>(...allowedTypes: T): FieldSchema<typeof FieldKinds.required, T>;
static fieldSequence<const T extends AllowedTypes>(...t: T): FieldSchema<typeof FieldKinds.sequence, T>;
leaf<Name extends TName, const T extends ValueSchema>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
leafValue: T;
}>;
Expand All @@ -1788,6 +1785,12 @@ export class SchemaBuilder<TScope extends string = string, TName extends number
mapRecursive<Name extends TName, const T extends Unenforced<ImplicitFieldSchema>>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
mapFields: T;
}>;
static optional<const T extends ImplicitAllowedTypes>(allowedTypes: T): FieldSchema<typeof FieldKinds.optional, NormalizeAllowedTypes<T>>;
readonly optional: typeof SchemaBuilder.optional;
static required<const T extends ImplicitAllowedTypes>(allowedTypes: T): FieldSchema<typeof FieldKinds.required, NormalizeAllowedTypes<T>>;
readonly required: typeof SchemaBuilder.required;
static sequence<const T extends ImplicitAllowedTypes>(allowedTypes: T): FieldSchema<typeof FieldKinds.sequence, NormalizeAllowedTypes<T>>;
readonly sequence: typeof SchemaBuilder.sequence;
struct<const Name extends TName, const T extends RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
structFields: {
[key in keyof T]: NormalizeField_2<T[key], DefaultFieldKind>;
Expand All @@ -1813,7 +1816,6 @@ export class SchemaBuilderBase<TScope extends string, TName extends number | str
static fieldRecursive<Kind extends FieldKind, T extends FlexList<Unenforced<TreeSchema>>>(kind: Kind, ...allowedTypes: T): FieldSchema<Kind, T>;
finalize(): SchemaLibrary;
readonly name: string;
// (undocumented)
readonly scope: TScope;
// (undocumented)
protected scoped<Name extends TName>(name: Name): `${TScope}.${Name}` & TreeSchemaIdentifier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export const bubbleSchema = builder.struct("BubbleBenchAppStateBubble-1.0.0", {
export const clientSchema = builder.struct("BubbleBenchAppStateClient-1.0.0", {
clientId: leaf.string,
color: leaf.string,
bubbles: SchemaBuilder.fieldSequence(bubbleSchema),
bubbles: builder.sequence(bubbleSchema),
});

export const rootAppStateSchema = SchemaBuilder.fieldSequence(clientSchema);
export const rootAppStateSchema = SchemaBuilder.sequence(clientSchema);

export const appSchemaData = builder.toDocumentSchema(rootAppStateSchema);

Expand Down
8 changes: 4 additions & 4 deletions examples/data-objects/inventory-app/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import { SchemaBuilder, TypedField, TypedNode, leaf } from "@fluid-experimental/
const builder = new SchemaBuilder({ scope: "inventory app", libraries: [leaf.library] });

export const part = builder.struct("Contoso:Part-1.0.0", {
name: SchemaBuilder.fieldRequired(leaf.string),
quantity: SchemaBuilder.fieldRequired(leaf.number),
name: leaf.string,
quantity: leaf.number,
});

export const inventory = builder.struct("Contoso:Inventory-1.0.0", {
parts: SchemaBuilder.fieldSequence(part),
parts: builder.sequence(part),
});

export const inventoryField = SchemaBuilder.fieldRequired(inventory);
export const inventoryField = SchemaBuilder.required(inventory);
export type InventoryField = TypedField<typeof inventoryField>;

export const schema = builder.toDocumentSchema(inventoryField);
Expand Down
2 changes: 1 addition & 1 deletion examples/data-objects/inventory-app/src/view/counter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useTreeContext } from "@fluid-experimental/tree-react-api";
import { SchemaBuilder, TypedField, leaf } from "@fluid-experimental/tree2";
import * as React from "react";

const schema = SchemaBuilder.fieldRequired(leaf.number);
const schema = SchemaBuilder.required(leaf.number);

export interface ICounterProps {
title: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function buildTreeSchema(
!fields.has(nodePropertyField),
0x712 /* name collision for nodePropertyField */,
);
fields.set(nodePropertyField, SchemaBuilder.fieldRequired(nodePropertySchema));
fields.set(nodePropertyField, SchemaBuilder.required(nodePropertySchema));
}
const fieldsObject = mapToObject(fields);
cache.treeSchema = builder.struct(typeid, fieldsObject);
Expand Down Expand Up @@ -312,7 +312,7 @@ const builtinBuilder = new SchemaBuilder({
// to be put into one library like this.
export const nodePropertySchema = builtinBuilder.map(
nodePropertyType,
SchemaBuilder.fieldOptional(Any),
builtinBuilder.optional(Any),
);
const builtinLibrary = builtinBuilder.finalize();

Expand Down
4 changes: 2 additions & 2 deletions experimental/dds/tree2/src/domains/testRecursiveDomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const builder = new SchemaBuilder({ scope: "Test Recursive Domain", libraries: [
*/
export const recursiveStruct = builder.structRecursive("struct", {
recursive: FieldSchema.createUnsafe(FieldKinds.optional, [() => recursiveStruct]),
number: SchemaBuilder.fieldRequired(leaf.number),
number: leaf.number,
});

// Some related information in https://github.com/microsoft/TypeScript/issues/55758.
Expand All @@ -35,7 +35,7 @@ fixRecursiveReference(recursiveReference);
*/
export const recursiveStruct2 = builder.struct("struct2", {
recursive: FieldSchema.create(FieldKinds.optional, [recursiveReference]),
number: SchemaBuilder.fieldRequired(leaf.number),
number: leaf.number,
});

type _0 = requireFalse<isAny<typeof recursiveStruct2>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,11 +366,11 @@ export class LazyMap<TSchema extends MapSchema>
// TODO: when appropriate add setter that delegates to field kind specific setter.
// public set(key: FieldKey, content: FlexibleFieldContent<TSchema["mapFields"]>): void {
// const field = this.get(key);
// if (field.is(SchemaBuilder.fieldOptional(...this.schema.mapFields.allowedTypes))) {
// if (field.is(SchemaBuilder.optional(this.schema.mapFields.allowedTypes))) {
// field.setContent(content);
// } else {
// assert(
// field.is(SchemaBuilder.fieldSequence(...this.schema.mapFields.allowedTypes)),
// field.is(SchemaBuilder.sequence(this.schema.mapFields.allowedTypes)),
// "unexpected map field kind",
// );
// // TODO: fix merge semantics.
Expand Down
77 changes: 57 additions & 20 deletions experimental/dds/tree2/src/feature-libraries/schemaBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,38 +185,75 @@ export class SchemaBuilder<

/**
* Define a schema for an {@link OptionalField}.
* Shorthand or passing `FieldKinds.optional` to {@link FieldSchema}.
* @remarks
* Shorthand or passing `FieldKinds.optional` to {@link FieldSchema.create}.
*
* This method is also available as an instance method on {@link SchemaBuilder}
*/
public static fieldOptional<const T extends AllowedTypes>(
...allowedTypes: T
): FieldSchema<typeof FieldKinds.optional, T> {
return FieldSchema.create(FieldKinds.optional, allowedTypes);
public static optional<const T extends ImplicitAllowedTypes>(
allowedTypes: T,
): FieldSchema<typeof FieldKinds.optional, NormalizeAllowedTypes<T>> {
return FieldSchema.create(FieldKinds.optional, normalizeAllowedTypes(allowedTypes));
}

/**
* Define a schema for a {@link RequiredField}.
* Shorthand or passing `FieldKinds.required` to {@link FieldSchema}.
* Define a schema for an {@link OptionalField}.
* @remarks
* Shorthand or passing `FieldKinds.optional` to {@link FieldSchema.create}.
*
* @privateRemarks
* TODO: Consider adding even shorter syntax where:
* - AllowedTypes can be used as a FieldSchema (Or SchemaBuilder takes a default field kind).
* - A TreeSchema can be used as AllowedTypes in the non-polymorphic case.
* Since this creates a {@link FieldSchema} (and not a {@link TreeSchema}), the resulting schema is structurally typed, and not impacted by the {@link SchemaBuilderBase.scope}:
* therefore this method is the same as the static version.
*/
public static fieldRequired<const T extends AllowedTypes>(
...allowedTypes: T
): FieldSchema<typeof FieldKinds.required, T> {
return FieldSchema.create(FieldKinds.required, allowedTypes);
public readonly optional = SchemaBuilder.optional;

/**
* Define a schema for an {@link RequiredField}.
* @remarks
* Shorthand or passing `FieldKinds.required` to {@link FieldSchema.create}.
*
* This method is also available as an instance method on {@link SchemaBuilder}
*/
public static required<const T extends ImplicitAllowedTypes>(
allowedTypes: T,
): FieldSchema<typeof FieldKinds.required, NormalizeAllowedTypes<T>> {
return FieldSchema.create(FieldKinds.required, normalizeAllowedTypes(allowedTypes));
}

/**
* Define a schema for a {@link Sequence} field.
* Define a schema for a {@link RequiredField}.
* @remarks
* Shorthand or passing `FieldKinds.required` to {@link FieldSchema.create}.
* Note that `FieldKinds.required` is the current default field kind, so APIs accepting {@link ImplicitFieldSchema}
* can be passed the `allowedTypes` and will implicitly wrap it up in a {@link RequiredField}.
*
* Since this creates a {@link FieldSchema} (and not a {@link TreeSchema}), the resulting schema is structurally typed, and not impacted by the {@link SchemaBuilderBase.scope}:
* therefor this method is the same as the static version.
*/
public readonly required = SchemaBuilder.required;

/**
* Define a schema for a {@link Sequence}.
* @remarks
* Shorthand or passing `FieldKinds.sequence` to {@link FieldSchema.create}.
*
* This method is also available as an instance method on {@link SchemaBuilder}
*/
public static fieldSequence<const T extends AllowedTypes>(
...t: T
): FieldSchema<typeof FieldKinds.sequence, T> {
return FieldSchema.create(FieldKinds.sequence, t);
public static sequence<const T extends ImplicitAllowedTypes>(
allowedTypes: T,
): FieldSchema<typeof FieldKinds.sequence, NormalizeAllowedTypes<T>> {
return FieldSchema.create(FieldKinds.sequence, normalizeAllowedTypes(allowedTypes));
}

/**
* Define a schema for a {@link Sequence}.
* @remarks
* Shorthand or passing `FieldKinds.sequence` to {@link FieldSchema.create}.
*
* Since this creates a {@link FieldSchema} (and not a {@link TreeSchema}), the resulting schema is structurally typed, and not impacted by the {@link SchemaBuilderBase.scope}:
* therefor this method is the same as the static version.
*/
public readonly sequence = SchemaBuilder.sequence;

/**
* Produce a TypedSchemaCollection which captures the content added to this builder, any additional SchemaLibraries that were added to it and a root field.
* Can be used with schematize to provide schema aware access to document content.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export class SchemaBuilderBase<TScope extends string, TName extends number | str
private finalized: boolean = false;
private readonly treeSchema: Map<TreeSchemaIdentifier, TreeSchema> = new Map();
private readonly adapters: Adapters = {};
/**
* Prefix appended to the identifiers of all {@link TreeSchema} produced by this builder.
*/
public readonly scope: TScope;

/**
Expand All @@ -42,7 +45,7 @@ export class SchemaBuilderBase<TScope extends string, TName extends number | str
public readonly name: string;

/**
* @param scope - Prefix appended to the identifiers to all {@link TreeSchema} produced by this builder.
* @param scope - Prefix appended to the identifiers of all {@link TreeSchema} produced by this builder.
* Use of [Reverse domain name notation](https://en.wikipedia.org/wiki/Reverse_domain_name_notation) or a UUIDv4 is recommended to avoid collisions.
* @param name - Name used to refer to this builder in error messages. Has no impact on the actual generated schema. Defaults to scope.
* @param lint - Optional configuration for "linting". See {@link SchemaLintConfiguration}. Currently defaults to enabling all lints.
Expand Down
6 changes: 3 additions & 3 deletions experimental/dds/tree2/src/test/cursorTestSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ const leafNumber = schemaBuilder.leaf("number", ValueSchema.Number);
export const emptySchema = schemaBuilder.struct("Empty Struct", {});
const emptySchema2 = schemaBuilder.struct("Empty Struct 2", {});
const emptySchema3 = schemaBuilder.struct("Empty Struct 3", {});
export const mapSchema = schemaBuilder.map("Map", SchemaBuilder.fieldSequence(Any));
export const mapSchema = schemaBuilder.map("Map", SchemaBuilder.sequence(Any));
// Struct with fixed shape
export const structSchema = schemaBuilder.struct("struct", {
child: SchemaBuilder.fieldRequired(leafNumber),
child: leafNumber,
});

export const testTreeSchema = schemaBuilder.toDocumentSchema(SchemaBuilder.fieldSequence(Any));
export const testTreeSchema = schemaBuilder.toDocumentSchema(SchemaBuilder.sequence(Any));

export const testTrees: readonly (readonly [string, JsonableTree])[] = [
["minimal", { type: emptySchema.name }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function bench(
const schemaCollection = new SchemaBuilder({
scope: "JsonCursor benchmark",
libraries: [jsonSchema],
}).toDocumentSchema(SchemaBuilder.fieldOptional(...jsonRoot));
}).toDocumentSchema(SchemaBuilder.optional(jsonRoot));
const schema = new InMemoryStoredSchemaRepository(schemaCollection);
for (const { name, getJson, dataConsumer } of data) {
describe(name, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ import { polygonTree, testData } from "./uniformChunkTestData";

const builder = new SchemaBuilder({ scope: "chunkTree", libraries: [leaf.library] });
const empty = builder.struct("empty", {});
const valueField = SchemaBuilder.fieldRequired(leaf.number);
const valueField = SchemaBuilder.required(leaf.number);
const structValue = builder.struct("structValue", { x: valueField });
const optionalField = SchemaBuilder.fieldOptional(leaf.number);
const optionalField = builder.optional(leaf.number);
const structOptional = builder.struct("structOptional", { x: optionalField });
const schema = builder.finalize();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe("schemaBasedEncoding", () => {
return onlyTypeShape;
},
},
SchemaBuilder.fieldRequired(minimal),
SchemaBuilder.required(minimal),
cache,
);
// This is expected since this case should be optimized to just encode the inner shape.
Expand All @@ -98,7 +98,7 @@ describe("schemaBasedEncoding", () => {
return onlyTypeShape;
},
},
SchemaBuilder.fieldRequired(minimal, leaf.number),
SchemaBuilder.required([minimal, leaf.number]),
cache,
);
// There are multiple choices about how this case should be optimized, but the current implementation does this:
Expand All @@ -120,7 +120,7 @@ describe("schemaBasedEncoding", () => {
return onlyTypeShape;
},
},
SchemaBuilder.fieldSequence(minimal),
SchemaBuilder.sequence(minimal),
cache,
);
// There are multiple choices about how this case should be optimized, but the current implementation does this:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ describe("ContextuallyTyped", () => {
it("applyTypesFromContext omits empty fields", () => {
const builder = new SchemaBuilder({ scope: "applyTypesFromContext" });
const numberSchema = builder.leaf("number", ValueSchema.Number);
const numberSequence = SchemaBuilder.fieldSequence(numberSchema);
const numberSequence = SchemaBuilder.sequence(numberSchema);
const numbersObject = builder.struct("numbers", { numbers: numberSequence });
const schema = builder.toDocumentSchema(numberSequence);
const mapTree = applyTypesFromContext({ schema }, new Set([numbersObject.name]), {
Expand All @@ -107,7 +107,7 @@ describe("ContextuallyTyped", () => {
it("applyTypesFromContext omits empty primary fields", () => {
const builder = new SchemaBuilder({ scope: "applyTypesFromContext" });
const numberSchema = builder.leaf("number", ValueSchema.Number);
const numberSequence = SchemaBuilder.fieldSequence(numberSchema);
const numberSequence = SchemaBuilder.sequence(numberSchema);
const primaryObject = builder.struct("numbers", { [EmptyKey]: numberSequence });
const schema = builder.toDocumentSchema(numberSequence);
const mapTree = applyTypesFromContext({ schema }, new Set([primaryObject.name]), []);
Expand All @@ -120,12 +120,10 @@ describe("ContextuallyTyped", () => {
const builder = new SchemaBuilder({ scope: "cursorFromContextualData" });
const generatedSchema = builder.leaf("generated", ValueSchema.String);
const nodeSchema = builder.struct("node", {
foo: SchemaBuilder.fieldRequired(generatedSchema),
foo: generatedSchema,
});

const nodeSchemaData = builder.toDocumentSchema(
SchemaBuilder.fieldOptional(nodeSchema),
);
const nodeSchemaData = builder.toDocumentSchema(builder.optional(nodeSchema));
const contextualData: ContextuallyTypedNodeDataObject = {};

const generatedField = [
Expand Down Expand Up @@ -154,13 +152,11 @@ describe("ContextuallyTyped", () => {
const generatedSchema = builder.leaf("generated", ValueSchema.String);

const nodeSchema = builder.structRecursive("node", {
foo: SchemaBuilder.fieldRequired(generatedSchema),
foo: builder.required(generatedSchema),
child: FieldSchema.createUnsafe(FieldKinds.optional, [() => nodeSchema]),
});

const nodeSchemaData = builder.toDocumentSchema(
SchemaBuilder.fieldOptional(nodeSchema),
);
const nodeSchemaData = builder.toDocumentSchema(builder.optional(nodeSchema));
const contextualData: ContextuallyTypedNodeDataObject = { child: {} };

const generatedField = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@ const builder = new SchemaBuilder({ scope: "EditableTree Node Keys", libraries:
const stringSchema = builder.leaf("string", ValueSchema.String);
const childNodeSchema = builder.struct("ChildNode", {
...nodeKeyField,
name: SchemaBuilder.fieldRequired(stringSchema),
name: stringSchema,
});

const parentNodeSchema = builder.struct("ParentNode", {
...nodeKeyField,
children: SchemaBuilder.fieldSequence(childNodeSchema),
children: builder.sequence(childNodeSchema),
});
const rootField = SchemaBuilder.fieldRequired(parentNodeSchema);
const schema = builder.toDocumentSchema(rootField);
const schema = builder.toDocumentSchema(parentNodeSchema);

// TODO: this can probably be removed once daesun's stuff goes in
function addKey(view: NodeKeyManager, key: LocalNodeKey): { [nodeKeyFieldKey]: StableNodeKey } {
Expand Down
Loading

0 comments on commit 310345c

Please sign in to comment.