@fluidframework/tree-agent 2.72.0 → 2.73.0

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/tree-agent.alpha.api.md +88 -17
  3. package/dist/agent.d.ts +5 -31
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +19 -38
  6. package/dist/agent.js.map +1 -1
  7. package/dist/alpha.d.ts +12 -3
  8. package/dist/api.d.ts +38 -20
  9. package/dist/api.d.ts.map +1 -1
  10. package/dist/api.js.map +1 -1
  11. package/dist/index.d.ts +4 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/prompt.d.ts +2 -2
  16. package/dist/prompt.d.ts.map +1 -1
  17. package/dist/prompt.js +4 -4
  18. package/dist/prompt.js.map +1 -1
  19. package/dist/propertyBinding.d.ts +106 -0
  20. package/dist/propertyBinding.d.ts.map +1 -0
  21. package/dist/propertyBinding.js +64 -0
  22. package/dist/propertyBinding.js.map +1 -0
  23. package/dist/subtree.d.ts +8 -9
  24. package/dist/subtree.d.ts.map +1 -1
  25. package/dist/subtree.js +21 -21
  26. package/dist/subtree.js.map +1 -1
  27. package/dist/typeGeneration.d.ts.map +1 -1
  28. package/dist/typeGeneration.js +78 -21
  29. package/dist/typeGeneration.js.map +1 -1
  30. package/dist/utils.d.ts +1 -7
  31. package/dist/utils.d.ts.map +1 -1
  32. package/dist/utils.js +17 -3
  33. package/dist/utils.js.map +1 -1
  34. package/lib/agent.d.ts +5 -31
  35. package/lib/agent.d.ts.map +1 -1
  36. package/lib/agent.js +17 -35
  37. package/lib/agent.js.map +1 -1
  38. package/lib/alpha.d.ts +12 -3
  39. package/lib/api.d.ts +38 -20
  40. package/lib/api.d.ts.map +1 -1
  41. package/lib/api.js.map +1 -1
  42. package/lib/index.d.ts +4 -3
  43. package/lib/index.d.ts.map +1 -1
  44. package/lib/index.js +1 -1
  45. package/lib/index.js.map +1 -1
  46. package/lib/prompt.d.ts +2 -2
  47. package/lib/prompt.d.ts.map +1 -1
  48. package/lib/prompt.js +4 -4
  49. package/lib/prompt.js.map +1 -1
  50. package/lib/propertyBinding.d.ts +106 -0
  51. package/lib/propertyBinding.d.ts.map +1 -0
  52. package/lib/propertyBinding.js +59 -0
  53. package/lib/propertyBinding.js.map +1 -0
  54. package/lib/subtree.d.ts +8 -9
  55. package/lib/subtree.d.ts.map +1 -1
  56. package/lib/subtree.js +21 -21
  57. package/lib/subtree.js.map +1 -1
  58. package/lib/typeGeneration.d.ts.map +1 -1
  59. package/lib/typeGeneration.js +78 -21
  60. package/lib/typeGeneration.js.map +1 -1
  61. package/lib/utils.d.ts +1 -7
  62. package/lib/utils.d.ts.map +1 -1
  63. package/lib/utils.js +17 -3
  64. package/lib/utils.js.map +1 -1
  65. package/package.json +10 -10
  66. package/src/agent.ts +27 -69
  67. package/src/api.ts +56 -29
  68. package/src/index.ts +16 -4
  69. package/src/prompt.ts +6 -6
  70. package/src/propertyBinding.ts +181 -0
  71. package/src/subtree.ts +26 -29
  72. package/src/typeGeneration.ts +90 -24
  73. package/src/utils.ts +21 -15
@@ -0,0 +1,181 @@
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
+ [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>;
47
+ }[keyof T];
48
+
49
+ /**
50
+ * Type to enforce `readOnly: true` for readonly properties.
51
+ * @alpha
52
+ */
53
+ export type ReadOnlyRequirement<TObj, K extends keyof TObj> = {
54
+ [P in K]-?: P extends ReadonlyKeys<TObj> ? { readOnly: true } : { readOnly?: false };
55
+ }[K];
56
+
57
+ /**
58
+ * Emits compile-time error when there is a type mismatch.
59
+ * @alpha
60
+ */
61
+ export type TypeMatchOrError<Expected, Received> = [Received] extends [Expected]
62
+ ? unknown
63
+ : {
64
+ __error__: "Zod schema value type does not match the property's declared type";
65
+ expected: Expected;
66
+ received: Received;
67
+ };
68
+
69
+ /**
70
+ * A property definition class that describes the structure of the property
71
+ * @alpha
72
+ */
73
+ export class PropertyDef {
74
+ public constructor(
75
+ public readonly name: string,
76
+ public readonly description: string | undefined,
77
+ public readonly schema: ZodTypeAny,
78
+ public readonly readOnly: boolean,
79
+ ) {}
80
+ }
81
+
82
+ /**
83
+ * An interface for exposing properties of schema classes to an agent.
84
+ * @alpha
85
+ */
86
+ export interface ExposedProperties {
87
+ exposeProperty<
88
+ S extends BindableSchema & Ctor,
89
+ K extends string & ExposableKeys<InstanceType<S>>,
90
+ TZ extends ZodTypeAny,
91
+ >(
92
+ schema: S,
93
+ name: K,
94
+ def: { schema: TZ; description?: string } & ReadOnlyRequirement<InstanceType<S>, K> &
95
+ TypeMatchOrError<InstanceType<S>[K], ZodInfer<TZ>>,
96
+ ): void;
97
+
98
+ instanceOf<T extends TreeNodeSchemaClass>(
99
+ schema: T,
100
+ ): ZodType<InstanceType<T>, ZodTypeDef, InstanceType<T>>;
101
+ }
102
+
103
+ /**
104
+ * An interface that SharedTree schema classes should implement to expose their properties to the LLM.
105
+ *
106
+ * @remarks
107
+ * The `getExposedProperties` free function will cause the method here to be called on the class passed to it.
108
+ *
109
+ * @privateremarks
110
+ * Implementing this interface correctly seems tricky?
111
+ * To actually implement it in a way that satisfies TypeScript,
112
+ * classes need to declare both a static version and an instance version of the method
113
+ * (the instance one can just delegate to the static one).
114
+ *
115
+ * @alpha
116
+ */
117
+ export interface IExposedProperties {
118
+ [exposePropertiesSymbol]?(properties: ExposedProperties): void;
119
+ }
120
+
121
+ class ExposedPropertiesI implements ExposedProperties {
122
+ private readonly properties: Record<string, PropertyDef> = {};
123
+ private readonly referencedTypes = new Set<TreeNodeSchema>();
124
+
125
+ public constructor(private readonly schemaClass: BindableSchema) {}
126
+
127
+ public exposeProperty<
128
+ S extends BindableSchema & Ctor,
129
+ K extends string & ExposableKeys<InstanceType<S>>,
130
+ TZ extends ZodTypeAny,
131
+ >(
132
+ schema: S,
133
+ name: K,
134
+ def: { schema: TZ; description?: string } & ReadOnlyRequirement<InstanceType<S>, K> &
135
+ TypeMatchOrError<InstanceType<S>[K], ZodInfer<TZ>>,
136
+ ): void {
137
+ if (schema !== this.schemaClass) {
138
+ throw new Error('Must expose properties on the "this" schema class');
139
+ }
140
+ this.properties[name] = new PropertyDef(
141
+ name,
142
+ def.description,
143
+ def.schema,
144
+ def.readOnly === true,
145
+ );
146
+ }
147
+
148
+ public instanceOf<T extends TreeNodeSchemaClass>(
149
+ schema: T,
150
+ ): ZodType<InstanceType<T>, ZodTypeDef, InstanceType<T>> {
151
+ this.referencedTypes.add(schema);
152
+ return instanceOf(schema);
153
+ }
154
+
155
+ public static getExposedProperties(schemaClass: BindableSchema): {
156
+ properties: Record<string, PropertyDef>;
157
+ referencedTypes: Set<TreeNodeSchema>;
158
+ } {
159
+ const exposed = new ExposedPropertiesI(schemaClass);
160
+ const extractable = schemaClass as unknown as IExposedProperties;
161
+ if (extractable[exposePropertiesSymbol] !== undefined) {
162
+ extractable[exposePropertiesSymbol](exposed);
163
+ }
164
+ return {
165
+ properties: exposed.properties,
166
+ referencedTypes: exposed.referencedTypes,
167
+ };
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Get the exposed properties of a schema class.
173
+ * @param schemaClass - The schema class to extract properties from.
174
+ * @returns A record of property names and their corresponding Zod types.
175
+ */
176
+ export function getExposedProperties(schemaClass: BindableSchema): {
177
+ properties: Record<string, PropertyDef>;
178
+ referencedTypes: Set<TreeNodeSchema>;
179
+ } {
180
+ return ExposedPropertiesI.getExposedProperties(schemaClass);
181
+ }
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 "./utils.js";
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 | UnsafeUnknownSchema> {
32
- public constructor(
33
- private readonly viewOrNode: TreeView<TRoot> | (ReadableField<TRoot> & TreeNode),
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.viewOrNode instanceof TreeNode
42
- ? (TreeAlpha.branch(this.viewOrNode) ?? fail("Node cannot be raw."))
43
- : this.viewOrNode;
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.viewOrNode instanceof TreeNode ? this.viewOrNode : this.viewOrNode.root;
42
+ return this.viewOrTree instanceof TreeNode ? this.viewOrTree : this.viewOrTree.root;
48
43
  }
49
44
 
50
- public set field(value: TreeFieldFromImplicitField<ReadSchema<TRoot>>) {
51
- if (this.viewOrNode instanceof TreeNode) {
52
- const parent = Tree.parent(this.viewOrNode);
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.viewOrNode) as string;
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.viewOrNode) as string;
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.viewOrNode) as string;
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.viewOrNode) as number;
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.viewOrNode.root = value;
97
+ this.viewOrTree.root = value;
103
98
  }
104
99
  }
105
100
 
106
- public get schema(): TreeNodeSchema | ReadSchema<TRoot> {
107
- return this.viewOrNode instanceof TreeNode
108
- ? Tree.schema(this.viewOrNode)
109
- : this.viewOrNode.schema;
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.viewOrNode instanceof TreeNode) {
114
- const branch = TreeAlpha.branch(this.viewOrNode) ?? fail("Node cannot be raw.");
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.viewOrNode, branch.fork()) ??
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.viewOrNode.fork());
118
+ return new Subtree<TRoot>(this.viewOrTree.fork());
122
119
  }
123
120
  }
124
121
  }
@@ -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 getBoundMethodsForBindable(bindableSchema: BindableSchema): {
140
+ function getBoundMembersForBindable(bindableSchema: BindableSchema): {
135
141
  referencedTypes: Set<TreeNodeSchema>;
136
- methods: [string, ZodTypeAny][];
142
+ members: [string, ZodTypeAny][];
137
143
  } {
138
- const methodTypes: [string, ZodTypeAny][] = [];
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
- methodTypes.push([name, zodFunction]);
151
+ memberTypes.push([name, zodFunction]);
145
152
  }
146
- return { methods: methodTypes, referencedTypes: methods.referencedTypes };
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 getBoundMethods(
167
+ function getBoundMembers(
150
168
  definition: string,
151
169
  bindableSchemas: Map<string, BindableSchema>,
152
170
  ): [string, ZodTypeAny][] {
153
- const bindableSchema = bindableSchemas.get(definition) ?? fail("Unknown definition");
154
- return getBoundMethodsForBindable(bindableSchema).methods;
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
- const boundMethods = getBoundMethods(definition, bindableSchemas);
167
- if (boundMethods.length > 0) {
168
- const methods: Record<string, z.ZodTypeAny> = {};
169
- for (const [name, zodFunction] of boundMethods) {
170
- if (methods[name] !== undefined) {
171
- throw new UsageError(
172
- `Method ${name} conflicts with field of the same name in schema ${definition}`,
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
- methods[name] = zodFunction;
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
- // Unlike arrays/maps/records, object nodes include methods directly on them rather than using an intersection
213
- for (const [name, zodFunction] of getBoundMethods(definition, bindableSchemas)) {
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
- `Method ${name} conflicts with field of the same name in schema ${definition}`,
282
+ `${kind} ${name} conflicts with field of the same name in schema ${definition}`,
217
283
  );
218
284
  }
219
- properties[name] = zodFunction;
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
  */
@@ -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
- const comment = type.description;
459
- if (comment !== undefined && comment !== "") append(` // ${comment}`);
465
+ appendBoundProperties(type);
460
466
  appendNewLine();
461
467
  }
462
468
  }