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

tree2: Cleanup creation of field schema #17699

Merged
merged 3 commits into from
Oct 10, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 23 additions & 27 deletions api-report/tree2.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -681,10 +681,11 @@ interface Fields {
}

// @alpha @sealed
export class FieldSchema<out Kind extends FieldKind = FieldKind, const out Types = AllowedTypes> {
constructor(kind: Kind, allowedTypes: Types);
export class FieldSchema<out Kind extends FieldKind = FieldKind, const out Types extends Unenforced<AllowedTypes> = AllowedTypes> {
// (undocumented)
readonly allowedTypes: Types;
static create<Kind extends FieldKind, const Types extends AllowedTypes>(kind: Kind, allowedTypes: Types): FieldSchema<Kind, Types>;
static createUnsafe<Kind extends FieldKind, const Types>(kind: Kind, allowedTypes: Types): FieldSchema<Kind, Types>;
static readonly empty: FieldSchema<Forbidden, readonly []>;
equals(other: FieldSchema): boolean;
// (undocumented)
Expand Down Expand Up @@ -933,8 +934,6 @@ declare namespace InternalTypedSchemaTypes {
MapSchemaSpecification,
LeafSchemaSpecification,
MapFieldSchema,
RecursiveTreeSchemaSpecification,
RecursiveTreeSchema,
FlexList,
FlexListToNonLazyArray,
ConstantFlexListToNonLazyArray,
Expand Down Expand Up @@ -1457,7 +1456,7 @@ interface NodeKeyField extends TreeField {

// @alpha
export const nodeKeyField: {
__n_id__: FieldSchema<NodeKeyFieldKind, [TreeSchema<"com.fluidframework.nodeKey.NodeKey", {
__n_id__: FieldSchema<NodeKeyFieldKind, readonly [TreeSchema<"com.fluidframework.nodeKey.NodeKey", {
leafValue: ValueSchema.String;
}>]>;
};
Expand Down Expand Up @@ -1667,8 +1666,8 @@ export function recordDependency(dependent: ObservingDependent | undefined, depe
// @alpha (undocumented)
const recursiveStruct: TreeSchema<"Test Recursive Domain.struct", {
structFields: {
recursive: FieldSchema<Optional, [() => TreeSchema<"Test Recursive Domain.struct", any>]>;
number: FieldSchema<Required_2, [TreeSchema<"com.fluidframework.leaf.number", {
readonly recursive: FieldSchema<Optional, readonly [() => TreeSchema<"Test Recursive Domain.struct", any>]>;
readonly number: FieldSchema<Required_2, readonly [TreeSchema<"com.fluidframework.leaf.number", {
leafValue: import("..").ValueSchema.Number;
}>]>;
};
Expand All @@ -1677,19 +1676,13 @@ leafValue: import("..").ValueSchema.Number;
// @alpha (undocumented)
const recursiveStruct2: TreeSchema<"Test Recursive Domain.struct2", {
structFields: {
readonly recursive: FieldSchema<Optional, [() => TreeSchema<"Test Recursive Domain.struct2", any>]>;
readonly number: FieldSchema<Required_2, [TreeSchema<"com.fluidframework.leaf.number", {
readonly recursive: FieldSchema<Optional, readonly [() => TreeSchema<"Test Recursive Domain.struct2", any>]>;
readonly number: FieldSchema<Required_2, readonly [TreeSchema<"com.fluidframework.leaf.number", {
leafValue: import("..").ValueSchema.Number;
}>]>;
};
}>;

// @alpha
type RecursiveTreeSchema = unknown;

// @alpha
type RecursiveTreeSchemaSpecification = unknown;

// @alpha
type _RecursiveTrick = never;

Expand Down Expand Up @@ -1773,37 +1766,37 @@ export { SchemaAware }

// @alpha @sealed
export class SchemaBuilder<TScope extends string = string, TName extends number | string = string> extends SchemaBuilderBase<TScope, TName> {
fieldNode<Name extends TName, T extends ImplicitFieldSchema>(name: Name, fieldSchema: T): TreeSchema<`${TScope}.${Name}`, {
fieldNode<Name extends TName, const T extends ImplicitFieldSchema>(name: Name, fieldSchema: T): TreeSchema<`${TScope}.${Name}`, {
structFields: {
[""]: NormalizeField_2<T, DefaultFieldKind>;
};
}>;
fieldNodeRecursive<Name extends TName, T>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
fieldNodeRecursive<Name extends TName, const T extends Unenforced<ImplicitFieldSchema>>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
structFields: {
[""]: T;
};
}>;
static fieldOptional<T extends AllowedTypes>(...allowedTypes: T): FieldSchema<typeof FieldKinds.optional, T>;
static fieldRequired<T extends AllowedTypes>(...allowedTypes: T): FieldSchema<typeof FieldKinds.required, T>;
static fieldSequence<T extends AllowedTypes>(...t: T): FieldSchema<typeof FieldKinds.sequence, T>;
leaf<Name extends TName, T extends ValueSchema>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
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;
}>;
map<Name extends TName, T extends ImplicitFieldSchema>(name: Name, fieldSchema: T): TreeSchema<`${TScope}.${Name}`, {
map<Name extends TName, const T extends ImplicitFieldSchema>(name: Name, fieldSchema: T): TreeSchema<`${TScope}.${Name}`, {
mapFields: NormalizeField_2<T, DefaultFieldKind>;
}>;
mapRecursive<Name extends TName, T>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
mapRecursive<Name extends TName, const T extends Unenforced<ImplicitFieldSchema>>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
mapFields: T;
}>;
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>;
};
}>;
structRecursive<Name extends TName, T>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
structRecursive<Name extends TName, const T extends Unenforced<RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, {
structFields: T;
}>;
toDocumentSchema<TSchema extends ImplicitFieldSchema>(root: TSchema): TypedSchemaCollection<NormalizeField_2<TSchema, DefaultFieldKind>>;
toDocumentSchema<const TSchema extends ImplicitFieldSchema>(root: TSchema): TypedSchemaCollection<NormalizeField_2<TSchema, DefaultFieldKind>>;
}

// @alpha
Expand All @@ -1817,7 +1810,7 @@ export class SchemaBuilderBase<TScope extends string, TName extends number | str
// (undocumented)
protected addNodeSchema<T extends TreeSchema<string, any>>(schema: T): void;
static field<Kind extends FieldKind, T extends AllowedTypes>(kind: Kind, ...allowedTypes: T): FieldSchema<Kind, T>;
static fieldRecursive<Kind extends FieldKind, T extends FlexList<RecursiveTreeSchema>>(kind: Kind, ...allowedTypes: T): FieldSchema<Kind, T>;
static fieldRecursive<Kind extends FieldKind, T extends FlexList<Unenforced<TreeSchema>>>(kind: Kind, ...allowedTypes: T): FieldSchema<Kind, T>;
finalize(): SchemaLibrary;
readonly name: string;
// (undocumented)
Expand Down Expand Up @@ -2103,7 +2096,7 @@ export interface TreeNode extends Tree<TreeSchema> {
}

// @alpha
export class TreeSchema<Name extends string = string, T extends RecursiveTreeSchemaSpecification = TreeSchemaSpecification> {
export class TreeSchema<Name extends string = string, T extends Unenforced<TreeSchemaSpecification> = TreeSchemaSpecification> {
constructor(builder: Named<string>, name: Name, info: T);
// (undocumented)
readonly builder: Named<string>;
Expand Down Expand Up @@ -2289,6 +2282,9 @@ TName extends infer S & TreeSchemaIdentifier ? S : string
// @alpha
type UnbrandList<T extends unknown[], B> = T extends [infer Head, ...infer Tail] ? [Unbrand<Head, B>, ...UnbrandList<Tail, B>] : [];

// @alpha
export type Unenforced<_DesiredExtendsConstraint> = unknown;

// @alpha
type UntypedApi<Mode extends ApiMode> = {
[ApiMode.Editable]: UntypedTree;
Expand Down
4 changes: 2 additions & 2 deletions experimental/dds/tree2/src/domains/json/jsonDomainSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ export const jsonRoot = [() => jsonObject, () => jsonArray, ...jsonPrimitives] a
*/
export const jsonObject = builder.mapRecursive(
"Object",
new FieldSchema(FieldKinds.optional, jsonRoot),
FieldSchema.createUnsafe(FieldKinds.optional, jsonRoot),
);

/**
* @alpha
*/
export const jsonArray = builder.fieldNodeRecursive(
"Array",
new FieldSchema(FieldKinds.sequence, jsonRoot),
FieldSchema.createUnsafe(FieldKinds.sequence, jsonRoot),
);

/**
Expand Down
4 changes: 2 additions & 2 deletions experimental/dds/tree2/src/domains/nodeKey/nodeKeySchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import { assert } from "@fluidframework/core-utils";
import { ValueSchema } from "../../core";
import {
SchemaBuilder,
nodeKeyFieldKey,
FieldKinds,
nodeKeyTreeIdentifier,
SchemaBuilderInternal,
FieldSchema,
} from "../../feature-libraries";

const builder = new SchemaBuilderInternal({ scope: "com.fluidframework.nodeKey" });
Expand All @@ -35,7 +35,7 @@ assert(nodeKeyTreeSchema.name === nodeKeyTreeIdentifier, "mismatched identifiers
* @alpha
*/
export const nodeKeyField = {
[nodeKeyFieldKey]: SchemaBuilder.field(FieldKinds.nodeKey, nodeKeyTreeSchema),
[nodeKeyFieldKey]: FieldSchema.create(FieldKinds.nodeKey, [nodeKeyTreeSchema]),
};

/**
Expand Down
6 changes: 3 additions & 3 deletions experimental/dds/tree2/src/domains/testRecursiveDomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Currently we do not have tooling in place to test this in our test suite, and exporting these types here is a temporary crutch to aid in diagnosing this issue.
*/

import { AllowedTypes, FieldKinds, SchemaBuilder } from "../feature-libraries";
import { AllowedTypes, FieldKinds, FieldSchema, SchemaBuilder } from "../feature-libraries";
import { areSafelyAssignable, isAny, requireFalse, requireTrue } from "../util";
import * as leaf from "./leafDomain";

Expand All @@ -20,7 +20,7 @@ const builder = new SchemaBuilder({ scope: "Test Recursive Domain", libraries: [
* @alpha
*/
export const recursiveStruct = builder.structRecursive("struct", {
recursive: SchemaBuilder.fieldRecursive(FieldKinds.optional, () => recursiveStruct),
recursive: FieldSchema.createUnsafe(FieldKinds.optional, [() => recursiveStruct]),
number: SchemaBuilder.fieldRequired(leaf.number),
});

Expand All @@ -34,7 +34,7 @@ fixRecursiveReference(recursiveReference);
* @alpha
*/
export const recursiveStruct2 = builder.struct("struct2", {
recursive: SchemaBuilder.field(FieldKinds.optional, recursiveReference),
recursive: FieldSchema.create(FieldKinds.optional, [recursiveReference]),
number: SchemaBuilder.fieldRequired(leaf.number),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export abstract class LazyTree<TSchema extends TreeSchema = TreeSchema>
// Additionally this approach makes it possible for a user to take an EditableTree node, get its parent, check its schema, down cast based on that, then edit that detached field (ex: removing the node in it).
// This MIGHT work properly with existing merge resolution logic (it must keep client in sync and be unable to violate schema), but this either needs robust testing or to be explicitly banned (error before s3ending the op).
// Issues like replacing a node in the a removed sequenced then undoing the remove could easily violate schema if not everything works exactly right!
fieldSchema = new FieldSchema(FieldKinds.sequence, [Any]);
fieldSchema = FieldSchema.create(FieldKinds.sequence, [Any]);
}
} else {
cursor.exitField();
Expand Down Expand Up @@ -471,7 +471,7 @@ export abstract class LazyStruct<TSchema extends StructSchema>
assert(field instanceof LazyNodeKeyField, "unexpected node key field");
// TODO: ideally we would do something like this, but that adds dependencies we can't have here:
// assert(
// field.is(new FieldSchema(FieldKinds.nodeKey, [nodeKeyTreeSchema])),
// field.is(FieldSchema.create(FieldKinds.nodeKey, [nodeKeyTreeSchema])),
// "invalid node key field",
// );

Expand Down
1 change: 1 addition & 0 deletions experimental/dds/tree2/src/feature-libraries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export {
bannedFieldNames,
fieldApiPrefixes,
validateStructFieldName,
Unenforced,
} from "./typed-schema";

export { SchemaBuilderBase, SchemaLibrary } from "./schemaBuilderBase";
Expand Down
49 changes: 28 additions & 21 deletions experimental/dds/tree2/src/feature-libraries/schemaBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import { ValueSchema } from "../core";
import { Assume, RestrictiveReadonlyRecord, transformObjectMap } from "../util";
import { SchemaBuilderBase } from "./schemaBuilderBase";
import { FieldKinds } from "./default-field-kinds";
import { AllowedTypes, TreeSchema, FieldSchema, Any, TypedSchemaCollection } from "./typed-schema";
import {
AllowedTypes,
TreeSchema,
FieldSchema,
Any,
TypedSchemaCollection,
Unenforced,
} from "./typed-schema";
import { FieldKind } from "./modular-schema";

// TODO: tests and examples for this file
Expand Down Expand Up @@ -66,14 +73,14 @@ export class SchemaBuilder<
* Same as `struct` but with less type safety and works for recursive objects.
* Reduced type safety is a side effect of a workaround for a TypeScript limitation.
*
* See note on {@link InternalTypedSchemaTypes#RecursiveTreeSchema} for details.
* See {@link Unenforced} for details.
*
* TODO: Make this work with ImplicitFieldSchema.
*/
public structRecursive<Name extends TName, T>(
name: Name,
t: T,
): TreeSchema<`${TScope}.${Name}`, { structFields: T }> {
public structRecursive<
Name extends TName,
const T extends Unenforced<RestrictiveReadonlyRecord<string, ImplicitFieldSchema>>,
>(name: Name, t: T): TreeSchema<`${TScope}.${Name}`, { structFields: T }> {
return this.struct(
name,
t as unknown as RestrictiveReadonlyRecord<string, ImplicitFieldSchema>,
Expand All @@ -83,7 +90,7 @@ export class SchemaBuilder<
/**
* Define (and add to this library) a {@link TreeSchema} for a {@link MapNode}.
*/
public map<Name extends TName, T extends ImplicitFieldSchema>(
public map<Name extends TName, const T extends ImplicitFieldSchema>(
name: Name,
fieldSchema: T,
): TreeSchema<`${TScope}.${Name}`, { mapFields: NormalizeField<T, DefaultFieldKind> }> {
Expand All @@ -98,11 +105,11 @@ export class SchemaBuilder<
* Same as `map` but with less type safety and works for recursive objects.
* Reduced type safety is a side effect of a workaround for a TypeScript limitation.
*
* See note on {@link InternalTypedSchemaTypes#RecursiveTreeSchema} for details.
* See {@link Unenforced} for details.
*
* TODO: Make this work with ImplicitFieldSchema.
*/
public mapRecursive<Name extends TName, T>(
public mapRecursive<Name extends TName, const T extends Unenforced<ImplicitFieldSchema>>(
name: Name,
t: T,
): TreeSchema<`${TScope}.${Name}`, { mapFields: T }> {
Expand All @@ -121,7 +128,7 @@ export class SchemaBuilder<
* TODO: Write and link document outlining field vs node data model and the separation of concerns related to that.
* TODO: Maybe find a better name for this.
*/
public fieldNode<Name extends TName, T extends ImplicitFieldSchema>(
public fieldNode<Name extends TName, const T extends ImplicitFieldSchema>(
name: Name,
fieldSchema: T,
): TreeSchema<
Expand All @@ -139,11 +146,11 @@ export class SchemaBuilder<
* Same as `fieldNode` but with less type safety and works for recursive objects.
* Reduced type safety is a side effect of a workaround for a TypeScript limitation.
*
* See note on {@link InternalTypedSchemaTypes#RecursiveTreeSchema} for details.
* See {@link Unenforced} for details.
*
* TODO: Make this work with ImplicitFieldSchema.
*/
public fieldNodeRecursive<Name extends TName, T>(
public fieldNodeRecursive<Name extends TName, const T extends Unenforced<ImplicitFieldSchema>>(
name: Name,
t: T,
): TreeSchema<`${TScope}.${Name}`, { structFields: { [""]: T } }> {
Expand All @@ -167,7 +174,7 @@ export class SchemaBuilder<
* TODO: Maybe ban undefined from allowed values here.
* TODO: Decide and document how unwrapping works for non-primitive terminals.
*/
public leaf<Name extends TName, T extends ValueSchema>(
public leaf<Name extends TName, const T extends ValueSchema>(
name: Name,
t: T,
): TreeSchema<`${TScope}.${Name}`, { leafValue: T }> {
Expand All @@ -180,10 +187,10 @@ export class SchemaBuilder<
* Define a schema for an {@link OptionalField}.
* Shorthand or passing `FieldKinds.optional` to {@link FieldSchema}.
*/
public static fieldOptional<T extends AllowedTypes>(
public static fieldOptional<const T extends AllowedTypes>(
...allowedTypes: T
): FieldSchema<typeof FieldKinds.optional, T> {
return new FieldSchema(FieldKinds.optional, allowedTypes);
return FieldSchema.create(FieldKinds.optional, allowedTypes);
}

/**
Expand All @@ -195,19 +202,19 @@ export class SchemaBuilder<
* - 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.
*/
public static fieldRequired<T extends AllowedTypes>(
public static fieldRequired<const T extends AllowedTypes>(
...allowedTypes: T
): FieldSchema<typeof FieldKinds.required, T> {
return new FieldSchema(FieldKinds.required, allowedTypes);
return FieldSchema.create(FieldKinds.required, allowedTypes);
}

/**
* Define a schema for a {@link Sequence} field.
*/
public static fieldSequence<T extends AllowedTypes>(
public static fieldSequence<const T extends AllowedTypes>(
...t: T
): FieldSchema<typeof FieldKinds.sequence, T> {
return new FieldSchema(FieldKinds.sequence, t);
return FieldSchema.create(FieldKinds.sequence, t);
}

/**
Expand All @@ -218,7 +225,7 @@ export class SchemaBuilder<
*
* May only be called once after adding content to builder is complete.
*/
public toDocumentSchema<TSchema extends ImplicitFieldSchema>(
public toDocumentSchema<const TSchema extends ImplicitFieldSchema>(
root: TSchema,
): TypedSchemaCollection<NormalizeField<TSchema, DefaultFieldKind>> {
return this.toDocumentSchemaInternal(normalizeField(root, DefaultFieldKind));
Expand Down Expand Up @@ -273,7 +280,7 @@ export function normalizeField<TSchema extends ImplicitFieldSchema, TDefault ext
return schema as NormalizeField<TSchema, TDefault>;
}
const allowedTypes = normalizeAllowedTypes(schema);
return new FieldSchema(defaultKind, allowedTypes) as unknown as NormalizeField<
return FieldSchema.create(defaultKind, allowedTypes) as unknown as NormalizeField<
TSchema,
TDefault
>;
Expand Down