@fluidframework/tree-agent 2.72.0 → 2.74.0-365691
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/api-report/tree-agent.alpha.api.md +89 -20
- package/dist/agent.d.ts +5 -31
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +21 -38
- package/dist/agent.js.map +1 -1
- package/dist/alpha.d.ts +12 -3
- package/dist/api.d.ts +38 -20
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/methodBinding.d.ts +1 -3
- package/dist/methodBinding.d.ts.map +1 -1
- package/dist/methodBinding.js.map +1 -1
- package/dist/prompt.d.ts +2 -2
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +4 -4
- package/dist/prompt.js.map +1 -1
- package/dist/propertyBinding.d.ts +106 -0
- package/dist/propertyBinding.d.ts.map +1 -0
- package/dist/propertyBinding.js +64 -0
- package/dist/propertyBinding.js.map +1 -0
- package/dist/subtree.d.ts +8 -9
- package/dist/subtree.d.ts.map +1 -1
- package/dist/subtree.js +21 -21
- package/dist/subtree.js.map +1 -1
- package/dist/typeGeneration.d.ts.map +1 -1
- package/dist/typeGeneration.js +78 -21
- package/dist/typeGeneration.js.map +1 -1
- package/dist/utils.d.ts +1 -7
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +19 -5
- package/dist/utils.js.map +1 -1
- package/lib/agent.d.ts +5 -31
- package/lib/agent.d.ts.map +1 -1
- package/lib/agent.js +19 -35
- package/lib/agent.js.map +1 -1
- package/lib/alpha.d.ts +12 -3
- package/lib/api.d.ts +38 -20
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js.map +1 -1
- package/lib/index.d.ts +4 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/methodBinding.d.ts +1 -3
- package/lib/methodBinding.d.ts.map +1 -1
- package/lib/methodBinding.js.map +1 -1
- package/lib/prompt.d.ts +2 -2
- package/lib/prompt.d.ts.map +1 -1
- package/lib/prompt.js +4 -4
- package/lib/prompt.js.map +1 -1
- package/lib/propertyBinding.d.ts +106 -0
- package/lib/propertyBinding.d.ts.map +1 -0
- package/lib/propertyBinding.js +59 -0
- package/lib/propertyBinding.js.map +1 -0
- package/lib/subtree.d.ts +8 -9
- package/lib/subtree.d.ts.map +1 -1
- package/lib/subtree.js +21 -21
- package/lib/subtree.js.map +1 -1
- package/lib/typeGeneration.d.ts.map +1 -1
- package/lib/typeGeneration.js +78 -21
- package/lib/typeGeneration.js.map +1 -1
- package/lib/utils.d.ts +1 -7
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +19 -5
- package/lib/utils.js.map +1 -1
- package/package.json +10 -10
- package/src/agent.ts +29 -69
- package/src/api.ts +56 -29
- package/src/index.ts +16 -4
- package/src/methodBinding.ts +2 -2
- package/src/prompt.ts +6 -6
- package/src/propertyBinding.ts +182 -0
- package/src/subtree.ts +26 -29
- package/src/typeGeneration.ts +90 -24
- package/src/utils.ts +23 -17
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
+
import type { TreeNodeSchema, TreeNodeSchemaClass } from "@fluidframework/tree";
|
|
8
|
+
import type { ZodType, ZodTypeAny, ZodTypeDef, infer as ZodInfer } from "zod";
|
|
9
|
+
|
|
10
|
+
import type { BindableSchema, Ctor } from "./methodBinding.js";
|
|
11
|
+
import { instanceOf } from "./utils.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A symbol used to expose properties to the LLM.
|
|
15
|
+
* @alpha
|
|
16
|
+
*/
|
|
17
|
+
export const exposePropertiesSymbol: unique symbol = Symbol.for(
|
|
18
|
+
"@fluidframework/tree-agent/exposeProperties",
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Set of property keys from `T` that are not methods.
|
|
23
|
+
* @alpha
|
|
24
|
+
*/
|
|
25
|
+
export type ExposableKeys<T> = {
|
|
26
|
+
[K in keyof T]?: T[K] extends (...args: any[]) => any ? never : K;
|
|
27
|
+
}[keyof T];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Type-level equality test used as a helper to evaluate readonly keys.
|
|
31
|
+
* - If X and Y are the same type, it evaluates to A.
|
|
32
|
+
* - If X and Y are different, it evaluates to B.
|
|
33
|
+
* @alpha
|
|
34
|
+
*/
|
|
35
|
+
export type IfEquals<X, Y, A = true, B = false> = (<T>() => T extends X ? 1 : 2) extends <
|
|
36
|
+
T,
|
|
37
|
+
>() => T extends Y ? 1 : 2
|
|
38
|
+
? A
|
|
39
|
+
: B;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Produces a union of keys of `T` which are readonly.
|
|
43
|
+
* @alpha
|
|
44
|
+
*/
|
|
45
|
+
export type ReadonlyKeys<T> = {
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
|
|
47
|
+
[P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>;
|
|
48
|
+
}[keyof T];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Type to enforce `readOnly: true` for readonly properties.
|
|
52
|
+
* @alpha
|
|
53
|
+
*/
|
|
54
|
+
export type ReadOnlyRequirement<TObj, K extends keyof TObj> = {
|
|
55
|
+
[P in K]-?: P extends ReadonlyKeys<TObj> ? { readOnly: true } : { readOnly?: false };
|
|
56
|
+
}[K];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Emits compile-time error when there is a type mismatch.
|
|
60
|
+
* @alpha
|
|
61
|
+
*/
|
|
62
|
+
export type TypeMatchOrError<Expected, Received> = [Received] extends [Expected]
|
|
63
|
+
? unknown
|
|
64
|
+
: {
|
|
65
|
+
__error__: "Zod schema value type does not match the property's declared type";
|
|
66
|
+
expected: Expected;
|
|
67
|
+
received: Received;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A property definition class that describes the structure of the property
|
|
72
|
+
* @alpha
|
|
73
|
+
*/
|
|
74
|
+
export class PropertyDef {
|
|
75
|
+
public constructor(
|
|
76
|
+
public readonly name: string,
|
|
77
|
+
public readonly description: string | undefined,
|
|
78
|
+
public readonly schema: ZodTypeAny,
|
|
79
|
+
public readonly readOnly: boolean,
|
|
80
|
+
) {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* An interface for exposing properties of schema classes to an agent.
|
|
85
|
+
* @alpha
|
|
86
|
+
*/
|
|
87
|
+
export interface ExposedProperties {
|
|
88
|
+
exposeProperty<
|
|
89
|
+
S extends BindableSchema & Ctor,
|
|
90
|
+
K extends string & ExposableKeys<InstanceType<S>>,
|
|
91
|
+
TZ extends ZodTypeAny,
|
|
92
|
+
>(
|
|
93
|
+
schema: S,
|
|
94
|
+
name: K,
|
|
95
|
+
def: { schema: TZ; description?: string } & ReadOnlyRequirement<InstanceType<S>, K> &
|
|
96
|
+
TypeMatchOrError<InstanceType<S>[K], ZodInfer<TZ>>,
|
|
97
|
+
): void;
|
|
98
|
+
|
|
99
|
+
instanceOf<T extends TreeNodeSchemaClass>(
|
|
100
|
+
schema: T,
|
|
101
|
+
): ZodType<InstanceType<T>, ZodTypeDef, InstanceType<T>>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* An interface that SharedTree schema classes should implement to expose their properties to the LLM.
|
|
106
|
+
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* The `getExposedProperties` free function will cause the method here to be called on the class passed to it.
|
|
109
|
+
*
|
|
110
|
+
* @privateremarks
|
|
111
|
+
* Implementing this interface correctly seems tricky?
|
|
112
|
+
* To actually implement it in a way that satisfies TypeScript,
|
|
113
|
+
* classes need to declare both a static version and an instance version of the method
|
|
114
|
+
* (the instance one can just delegate to the static one).
|
|
115
|
+
*
|
|
116
|
+
* @alpha
|
|
117
|
+
*/
|
|
118
|
+
export interface IExposedProperties {
|
|
119
|
+
[exposePropertiesSymbol]?(properties: ExposedProperties): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class ExposedPropertiesI implements ExposedProperties {
|
|
123
|
+
private readonly properties: Record<string, PropertyDef> = {};
|
|
124
|
+
private readonly referencedTypes = new Set<TreeNodeSchema>();
|
|
125
|
+
|
|
126
|
+
public constructor(private readonly schemaClass: BindableSchema) {}
|
|
127
|
+
|
|
128
|
+
public exposeProperty<
|
|
129
|
+
S extends BindableSchema & Ctor,
|
|
130
|
+
K extends string & ExposableKeys<InstanceType<S>>,
|
|
131
|
+
TZ extends ZodTypeAny,
|
|
132
|
+
>(
|
|
133
|
+
schema: S,
|
|
134
|
+
name: K,
|
|
135
|
+
def: { schema: TZ; description?: string } & ReadOnlyRequirement<InstanceType<S>, K> &
|
|
136
|
+
TypeMatchOrError<InstanceType<S>[K], ZodInfer<TZ>>,
|
|
137
|
+
): void {
|
|
138
|
+
if (schema !== this.schemaClass) {
|
|
139
|
+
throw new Error('Must expose properties on the "this" schema class');
|
|
140
|
+
}
|
|
141
|
+
this.properties[name] = new PropertyDef(
|
|
142
|
+
name,
|
|
143
|
+
def.description,
|
|
144
|
+
def.schema,
|
|
145
|
+
def.readOnly === true,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public instanceOf<T extends TreeNodeSchemaClass>(
|
|
150
|
+
schema: T,
|
|
151
|
+
): ZodType<InstanceType<T>, ZodTypeDef, InstanceType<T>> {
|
|
152
|
+
this.referencedTypes.add(schema);
|
|
153
|
+
return instanceOf(schema);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public static getExposedProperties(schemaClass: BindableSchema): {
|
|
157
|
+
properties: Record<string, PropertyDef>;
|
|
158
|
+
referencedTypes: Set<TreeNodeSchema>;
|
|
159
|
+
} {
|
|
160
|
+
const exposed = new ExposedPropertiesI(schemaClass);
|
|
161
|
+
const extractable = schemaClass as unknown as IExposedProperties;
|
|
162
|
+
if (extractable[exposePropertiesSymbol] !== undefined) {
|
|
163
|
+
extractable[exposePropertiesSymbol](exposed);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
properties: exposed.properties,
|
|
167
|
+
referencedTypes: exposed.referencedTypes,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get the exposed properties of a schema class.
|
|
174
|
+
* @param schemaClass - The schema class to extract properties from.
|
|
175
|
+
* @returns A record of property names and their corresponding Zod types.
|
|
176
|
+
*/
|
|
177
|
+
export function getExposedProperties(schemaClass: BindableSchema): {
|
|
178
|
+
properties: Record<string, PropertyDef>;
|
|
179
|
+
referencedTypes: Set<TreeNodeSchema>;
|
|
180
|
+
} {
|
|
181
|
+
return ExposedPropertiesI.getExposedProperties(schemaClass);
|
|
182
|
+
}
|
package/src/subtree.ts
CHANGED
|
@@ -11,45 +11,40 @@ import type {
|
|
|
11
11
|
TreeFieldFromImplicitField,
|
|
12
12
|
TreeMapNode,
|
|
13
13
|
TreeArrayNode,
|
|
14
|
-
TreeNodeSchema,
|
|
15
14
|
} from "@fluidframework/tree";
|
|
16
15
|
import { TreeAlpha } from "@fluidframework/tree/alpha";
|
|
17
16
|
import type {
|
|
18
|
-
UnsafeUnknownSchema,
|
|
19
17
|
ReadableField,
|
|
20
18
|
TreeRecordNode,
|
|
21
|
-
ReadSchema,
|
|
22
19
|
TreeBranchAlpha,
|
|
23
20
|
} from "@fluidframework/tree/alpha";
|
|
24
21
|
|
|
25
22
|
import { getNodeOnBranch } from "./getNodeOnBranch.js";
|
|
26
|
-
import type { TreeView } from "./
|
|
23
|
+
import type { TreeView, ViewOrTree } from "./api.js";
|
|
27
24
|
|
|
28
25
|
/**
|
|
29
26
|
* Wraps either a {@link TreeView} or a {@link TreeNode} and provides a common interface over them.
|
|
30
27
|
*/
|
|
31
|
-
export class Subtree<TRoot extends ImplicitFieldSchema
|
|
32
|
-
public constructor(
|
|
33
|
-
|
|
34
|
-
) {
|
|
35
|
-
if (viewOrNode instanceof TreeNode && TreeAlpha.branch(viewOrNode) === undefined) {
|
|
28
|
+
export class Subtree<TRoot extends ImplicitFieldSchema> {
|
|
29
|
+
public constructor(public readonly viewOrTree: ViewOrTree<TRoot>) {
|
|
30
|
+
if (viewOrTree instanceof TreeNode && TreeAlpha.branch(viewOrTree) === undefined) {
|
|
36
31
|
throw new UsageError("The provided node must belong to a branch.");
|
|
37
32
|
}
|
|
38
33
|
}
|
|
39
34
|
|
|
40
35
|
public get branch(): TreeBranchAlpha {
|
|
41
|
-
return this.
|
|
42
|
-
? (TreeAlpha.branch(this.
|
|
43
|
-
: this.
|
|
36
|
+
return this.viewOrTree instanceof TreeNode
|
|
37
|
+
? (TreeAlpha.branch(this.viewOrTree) ?? fail("Node cannot be raw."))
|
|
38
|
+
: this.viewOrTree;
|
|
44
39
|
}
|
|
45
40
|
|
|
46
41
|
public get field(): ReadableField<TRoot> {
|
|
47
|
-
return this.
|
|
42
|
+
return this.viewOrTree instanceof TreeNode ? this.viewOrTree : this.viewOrTree.root;
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
public set field(value: TreeFieldFromImplicitField<
|
|
51
|
-
if (this.
|
|
52
|
-
const parent = Tree.parent(this.
|
|
45
|
+
public set field(value: TreeFieldFromImplicitField<TRoot>) {
|
|
46
|
+
if (this.viewOrTree instanceof TreeNode) {
|
|
47
|
+
const parent = Tree.parent(this.viewOrTree);
|
|
53
48
|
if (parent === undefined) {
|
|
54
49
|
// In general, this is not a correct cast, but we know that the root of the branch at least allows the type of `value`
|
|
55
50
|
const view = this.branch as TreeView<TRoot>;
|
|
@@ -58,12 +53,12 @@ export class Subtree<TRoot extends ImplicitFieldSchema | UnsafeUnknownSchema> {
|
|
|
58
53
|
const schema = Tree.schema(parent);
|
|
59
54
|
switch (schema.kind) {
|
|
60
55
|
case NodeKind.Object: {
|
|
61
|
-
const key = Tree.key(this.
|
|
56
|
+
const key = Tree.key(this.viewOrTree) as string;
|
|
62
57
|
(parent as unknown as Record<string, unknown>)[key] = value;
|
|
63
58
|
break;
|
|
64
59
|
}
|
|
65
60
|
case NodeKind.Record: {
|
|
66
|
-
const key = Tree.key(this.
|
|
61
|
+
const key = Tree.key(this.viewOrTree) as string;
|
|
67
62
|
if (value === undefined) {
|
|
68
63
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
69
64
|
delete (parent as TreeRecordNode)[key];
|
|
@@ -73,7 +68,7 @@ export class Subtree<TRoot extends ImplicitFieldSchema | UnsafeUnknownSchema> {
|
|
|
73
68
|
break;
|
|
74
69
|
}
|
|
75
70
|
case NodeKind.Map: {
|
|
76
|
-
const key = Tree.key(this.
|
|
71
|
+
const key = Tree.key(this.viewOrTree) as string;
|
|
77
72
|
if (value === undefined) {
|
|
78
73
|
(parent as TreeMapNode).delete(key);
|
|
79
74
|
} else {
|
|
@@ -82,7 +77,7 @@ export class Subtree<TRoot extends ImplicitFieldSchema | UnsafeUnknownSchema> {
|
|
|
82
77
|
break;
|
|
83
78
|
}
|
|
84
79
|
case NodeKind.Array: {
|
|
85
|
-
const index = Tree.key(this.
|
|
80
|
+
const index = Tree.key(this.viewOrTree) as number;
|
|
86
81
|
const arrayNode = parent as TreeArrayNode;
|
|
87
82
|
if (value === undefined) {
|
|
88
83
|
arrayNode.removeAt(index);
|
|
@@ -99,26 +94,28 @@ export class Subtree<TRoot extends ImplicitFieldSchema | UnsafeUnknownSchema> {
|
|
|
99
94
|
}
|
|
100
95
|
}
|
|
101
96
|
} else {
|
|
102
|
-
this.
|
|
97
|
+
this.viewOrTree.root = value;
|
|
103
98
|
}
|
|
104
99
|
}
|
|
105
100
|
|
|
106
|
-
public get schema():
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
public get schema(): TRoot {
|
|
102
|
+
return (
|
|
103
|
+
this.viewOrTree instanceof TreeNode
|
|
104
|
+
? Tree.schema(this.viewOrTree)
|
|
105
|
+
: this.viewOrTree.schema
|
|
106
|
+
) as TRoot;
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
public fork(): Subtree<TRoot> {
|
|
113
|
-
if (this.
|
|
114
|
-
const branch = TreeAlpha.branch(this.
|
|
110
|
+
if (this.viewOrTree instanceof TreeNode) {
|
|
111
|
+
const branch = TreeAlpha.branch(this.viewOrTree) ?? fail("Node cannot be raw.");
|
|
115
112
|
const node =
|
|
116
|
-
getNodeOnBranch(this.
|
|
113
|
+
getNodeOnBranch(this.viewOrTree, branch.fork()) ??
|
|
117
114
|
fail("Expected node to be on new fork.");
|
|
118
115
|
|
|
119
116
|
return new Subtree<TRoot>(node);
|
|
120
117
|
} else {
|
|
121
|
-
return new Subtree<TRoot>(this.
|
|
118
|
+
return new Subtree<TRoot>(this.viewOrTree.fork());
|
|
122
119
|
}
|
|
123
120
|
}
|
|
124
121
|
}
|
package/src/typeGeneration.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { z, type ZodTypeAny } from "zod";
|
|
|
27
27
|
|
|
28
28
|
import type { BindableSchema } from "./methodBinding.js";
|
|
29
29
|
import { FunctionWrapper, getExposedMethods } from "./methodBinding.js";
|
|
30
|
+
import { getExposedProperties } from "./propertyBinding.js";
|
|
30
31
|
import {
|
|
31
32
|
fail,
|
|
32
33
|
getOrCreate,
|
|
@@ -83,6 +84,11 @@ export function generateEditTypesForPrompt(
|
|
|
83
84
|
allSchemas.add(t);
|
|
84
85
|
objectTypeSchemas.add(t);
|
|
85
86
|
}
|
|
87
|
+
const exposedProperties = getExposedProperties(n);
|
|
88
|
+
for (const t of exposedProperties.referencedTypes) {
|
|
89
|
+
allSchemas.add(t);
|
|
90
|
+
objectTypeSchemas.add(t);
|
|
91
|
+
}
|
|
86
92
|
}
|
|
87
93
|
},
|
|
88
94
|
});
|
|
@@ -131,27 +137,39 @@ function generateEditTypes(
|
|
|
131
137
|
};
|
|
132
138
|
}
|
|
133
139
|
|
|
134
|
-
function
|
|
140
|
+
function getBoundMembersForBindable(bindableSchema: BindableSchema): {
|
|
135
141
|
referencedTypes: Set<TreeNodeSchema>;
|
|
136
|
-
|
|
142
|
+
members: [string, ZodTypeAny][];
|
|
137
143
|
} {
|
|
138
|
-
const
|
|
144
|
+
const memberTypes: [string, ZodTypeAny][] = [];
|
|
145
|
+
|
|
139
146
|
const methods = getExposedMethods(bindableSchema);
|
|
140
147
|
for (const [name, method] of Object.entries(methods.methods)) {
|
|
141
148
|
const zodFunction = z.instanceof(FunctionWrapper);
|
|
142
149
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
|
|
143
150
|
(zodFunction as any).method = method;
|
|
144
|
-
|
|
151
|
+
memberTypes.push([name, zodFunction]);
|
|
145
152
|
}
|
|
146
|
-
|
|
153
|
+
const props = getExposedProperties(bindableSchema);
|
|
154
|
+
for (const [name, prop] of Object.entries(props.properties)) {
|
|
155
|
+
const schema = prop.schema;
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
|
|
157
|
+
(schema as any).property = prop;
|
|
158
|
+
memberTypes.push([name, prop.schema]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
members: memberTypes,
|
|
163
|
+
referencedTypes: new Set([...props.referencedTypes, ...methods.referencedTypes]),
|
|
164
|
+
};
|
|
147
165
|
}
|
|
148
166
|
|
|
149
|
-
function
|
|
167
|
+
function getBoundMembers(
|
|
150
168
|
definition: string,
|
|
151
169
|
bindableSchemas: Map<string, BindableSchema>,
|
|
152
170
|
): [string, ZodTypeAny][] {
|
|
153
|
-
const bindableSchema = bindableSchemas.get(definition) ?? fail("
|
|
154
|
-
return
|
|
171
|
+
const bindableSchema = bindableSchemas.get(definition) ?? fail("unknown definition");
|
|
172
|
+
return getBoundMembersForBindable(bindableSchema).members;
|
|
155
173
|
}
|
|
156
174
|
|
|
157
175
|
function addBindingIntersectionIfNeeded(
|
|
@@ -163,21 +181,68 @@ function addBindingIntersectionIfNeeded(
|
|
|
163
181
|
): ZodTypeAny {
|
|
164
182
|
let zodType = zodTypeBound;
|
|
165
183
|
let description = simpleNodeSchema.metadata?.description ?? "";
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
184
|
+
|
|
185
|
+
const boundMembers = getBoundMembers(definition, bindableSchemas);
|
|
186
|
+
const methods: Record<string, z.ZodTypeAny> = {};
|
|
187
|
+
const properties: Record<string, z.ZodTypeAny> = {};
|
|
188
|
+
|
|
189
|
+
let hasProperties: boolean = false;
|
|
190
|
+
let hasMethods: boolean = false;
|
|
191
|
+
|
|
192
|
+
if (boundMembers.length > 0) {
|
|
193
|
+
for (const [name, zodMember] of boundMembers) {
|
|
194
|
+
const kind =
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
196
|
+
(zodMember as any).method === undefined
|
|
197
|
+
? // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
198
|
+
(zodMember as any).property === undefined
|
|
199
|
+
? "Unknown"
|
|
200
|
+
: "Property"
|
|
201
|
+
: "Method";
|
|
202
|
+
|
|
203
|
+
if (kind === "Unknown") {
|
|
204
|
+
throw new UsageError(`Unrecognized bound member ${name} in schema ${definition}`);
|
|
205
|
+
}
|
|
206
|
+
if (kind === "Method") {
|
|
207
|
+
if (methods[name] !== undefined) {
|
|
208
|
+
throw new UsageError(
|
|
209
|
+
`${kind} ${name} conflicts with field of the same name in schema ${definition}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
methods[name] = zodMember;
|
|
174
213
|
}
|
|
175
|
-
|
|
214
|
+
if (kind === "Property") {
|
|
215
|
+
if (properties[name] !== undefined) {
|
|
216
|
+
throw new UsageError(
|
|
217
|
+
`${kind} ${name} conflicts with field of the same name in schema ${definition}`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
properties[name] = zodMember;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
hasMethods = Object.keys(methods).length > 0 ? true : false;
|
|
224
|
+
hasProperties = Object.keys(properties).length > 0 ? true : false;
|
|
225
|
+
|
|
226
|
+
if (hasMethods) {
|
|
227
|
+
zodType = z.intersection(zodType, z.object(methods));
|
|
228
|
+
}
|
|
229
|
+
if (hasProperties) {
|
|
230
|
+
zodType = z.intersection(zodType, z.object(properties));
|
|
176
231
|
}
|
|
177
|
-
zodType = z.intersection(zodType, z.object(methods));
|
|
178
|
-
const methodNote = `Note: this ${typeString} has custom user-defined methods directly on it.`;
|
|
179
|
-
description = description === "" ? methodNote : `${description} - ${methodNote}`;
|
|
180
232
|
}
|
|
233
|
+
|
|
234
|
+
let note = "";
|
|
235
|
+
if (hasMethods && hasProperties) {
|
|
236
|
+
note = `Note: this ${typeString} has custom user-defined methods and properties directly on it.`;
|
|
237
|
+
} else if (hasMethods) {
|
|
238
|
+
note = `Note: this ${typeString} has custom user-defined methods directly on it.`;
|
|
239
|
+
} else if (hasProperties) {
|
|
240
|
+
note = `Note: this ${typeString} has custom user-defined properties directly on it.`;
|
|
241
|
+
}
|
|
242
|
+
if (note !== "") {
|
|
243
|
+
description = description === "" ? note : `${description} - ${note}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
181
246
|
return zodType.describe(description);
|
|
182
247
|
}
|
|
183
248
|
|
|
@@ -209,14 +274,15 @@ function getOrCreateType(
|
|
|
209
274
|
.filter(([, value]) => value !== undefined),
|
|
210
275
|
);
|
|
211
276
|
|
|
212
|
-
|
|
213
|
-
|
|
277
|
+
for (const [name, zodMember] of getBoundMembers(definition, bindableSchemas)) {
|
|
278
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
279
|
+
const kind = (zodMember as any).method === undefined ? "Property" : "Method";
|
|
214
280
|
if (properties[name] !== undefined) {
|
|
215
281
|
throw new UsageError(
|
|
216
|
-
|
|
282
|
+
`${kind} ${name} conflicts with field of the same name in schema ${definition}`,
|
|
217
283
|
);
|
|
218
284
|
}
|
|
219
|
-
properties[name] =
|
|
285
|
+
properties[name] = zodMember;
|
|
220
286
|
}
|
|
221
287
|
|
|
222
288
|
return (type = z
|
package/src/utils.ts
CHANGED
|
@@ -12,10 +12,8 @@ import { UsageError } from "@fluidframework/telemetry-utils/internal";
|
|
|
12
12
|
import type { ImplicitFieldSchema, TreeNodeSchemaClass } from "@fluidframework/tree";
|
|
13
13
|
import type {
|
|
14
14
|
InsertableContent,
|
|
15
|
-
TreeBranchAlpha,
|
|
16
15
|
TreeNode,
|
|
17
16
|
TreeNodeSchema,
|
|
18
|
-
TreeViewAlpha,
|
|
19
17
|
UnsafeUnknownSchema,
|
|
20
18
|
} from "@fluidframework/tree/alpha";
|
|
21
19
|
import {
|
|
@@ -29,6 +27,7 @@ import { NodeKind, normalizeFieldSchema } from "@fluidframework/tree/internal";
|
|
|
29
27
|
import { z } from "zod";
|
|
30
28
|
|
|
31
29
|
import { FunctionWrapper } from "./methodBinding.js";
|
|
30
|
+
import { PropertyDef } from "./propertyBinding.js";
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
33
|
* Subset of Map interface.
|
|
@@ -86,17 +85,6 @@ export function getOrCreate<K, V>(
|
|
|
86
85
|
return value;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
/**
|
|
90
|
-
* TODO
|
|
91
|
-
* @alpha
|
|
92
|
-
* @privateRemarks This is a subset of the TreeViewAlpha functionality because if take it wholesale, it causes problems with invariance of the generic parameters.
|
|
93
|
-
*/
|
|
94
|
-
export type TreeView<TRoot extends ImplicitFieldSchema | UnsafeUnknownSchema> = Pick<
|
|
95
|
-
TreeViewAlpha<TRoot>,
|
|
96
|
-
"root" | "fork" | "merge" | "rebaseOnto" | "schema" | "events"
|
|
97
|
-
> &
|
|
98
|
-
TreeBranchAlpha;
|
|
99
|
-
|
|
100
88
|
/**
|
|
101
89
|
* TODO
|
|
102
90
|
*/
|
|
@@ -192,7 +180,7 @@ export function isNamedSchema(schemaIdentifier: string): boolean {
|
|
|
192
180
|
return false;
|
|
193
181
|
}
|
|
194
182
|
|
|
195
|
-
return
|
|
183
|
+
return /(?:Array|Map|Record)<\["(.*)"]>/.exec(schemaIdentifier) === null;
|
|
196
184
|
}
|
|
197
185
|
|
|
198
186
|
/**
|
|
@@ -202,7 +190,7 @@ export function isNamedSchema(schemaIdentifier: string): boolean {
|
|
|
202
190
|
*/
|
|
203
191
|
export function unqualifySchema(schemaIdentifier: string): string {
|
|
204
192
|
// Get the unqualified name by removing the scope (everything before the last dot).
|
|
205
|
-
const matches =
|
|
193
|
+
const matches = /[^.]+$/.exec(schemaIdentifier);
|
|
206
194
|
if (matches === null) {
|
|
207
195
|
return schemaIdentifier; // Return the original name if it is unscoped.
|
|
208
196
|
}
|
|
@@ -434,6 +422,24 @@ export function getZodSchemaAsTypeScript(
|
|
|
434
422
|
}
|
|
435
423
|
}
|
|
436
424
|
|
|
425
|
+
function appendBoundProperties(type: z.ZodType): void {
|
|
426
|
+
const property = (type as unknown as { property?: PropertyDef }).property;
|
|
427
|
+
|
|
428
|
+
if (!(property instanceof PropertyDef)) {
|
|
429
|
+
if (type.description !== undefined && type.description !== "") {
|
|
430
|
+
append(` // ${type.description}`);
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (property.readOnly === true) {
|
|
436
|
+
append(" // readonly");
|
|
437
|
+
}
|
|
438
|
+
if (property.description !== undefined && property.description !== "") {
|
|
439
|
+
append(` - ${property.description}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
437
443
|
function appendArrayType(arrayType: z.ZodType) {
|
|
438
444
|
appendType((arrayType._def as z.ZodArrayDef).type, TypePrecedence.Object);
|
|
439
445
|
append("[]");
|
|
@@ -446,6 +452,7 @@ export function getZodSchemaAsTypeScript(
|
|
|
446
452
|
// eslint-disable-next-line prefer-const
|
|
447
453
|
for (let [name, type] of Object.entries((objectType._def as z.ZodObjectDef).shape())) {
|
|
448
454
|
const method = (type as unknown as { method: object | undefined }).method;
|
|
455
|
+
|
|
449
456
|
if (method === undefined || !(method instanceof FunctionWrapper)) {
|
|
450
457
|
append(name);
|
|
451
458
|
if (getTypeKind(type) === z.ZodFirstPartyTypeKind.ZodOptional) {
|
|
@@ -455,8 +462,7 @@ export function getZodSchemaAsTypeScript(
|
|
|
455
462
|
append(": ");
|
|
456
463
|
appendType(type);
|
|
457
464
|
append(";");
|
|
458
|
-
|
|
459
|
-
if (comment !== undefined && comment !== "") append(` // ${comment}`);
|
|
465
|
+
appendBoundProperties(type);
|
|
460
466
|
appendNewLine();
|
|
461
467
|
}
|
|
462
468
|
}
|