prototypey.org - atproto lexicon typescript toolkit - mirror https://github.com/tylersayshi/prototypey

Add support for object-level descriptions in lx.object()

authored by Miguel Batres and committed by tyler a7e332d3 8174da97

+97 -10
+23 -9
packages/prototypey/core/lib.ts
··· 205 } 206 >; 207 208 type RequiredKeys<T> = { 209 [K in keyof T]: T[K] extends { required: true } ? K : never; 210 }[keyof T]; ··· 217 * Resulting object schema with required and nullable fields extracted. 218 * @see https://atproto.com/specs/lexicon#object 219 */ 220 - type ObjectResult<T extends ObjectProperties> = { 221 type: "object"; 222 /** Property definitions */ 223 properties: { ··· 230 : { required: UnionToTuple<RequiredKeys<T>> }) & 231 ([NullableKeys<T>] extends [never] 232 ? {} 233 - : { nullable: UnionToTuple<NullableKeys<T>> }); 234 235 /** 236 * Map of parameter names to their lexicon item definitions. ··· 529 * Creates an object type with defined properties. 530 * @see https://atproto.com/specs/lexicon#object 531 */ 532 - object<T extends ObjectProperties>(options: T): ObjectResult<T> { 533 - const required = Object.keys(options).filter( 534 - (key) => "required" in options[key] && options[key].required, 535 ); 536 - const nullable = Object.keys(options).filter( 537 - (key) => "nullable" in options[key] && options[key].nullable, 538 ); 539 const result: Record<string, unknown> = { 540 type: "object", 541 - properties: options, 542 }; 543 if (required.length > 0) { 544 result.required = required; ··· 546 if (nullable.length > 0) { 547 result.nullable = nullable; 548 } 549 - return result as ObjectResult<T>; 550 }, 551 /** 552 * Creates a params type for query string parameters.
··· 205 } 206 >; 207 208 + /** 209 + * Object-level options (not property-level). 210 + * @see https://atproto.com/specs/lexicon#object 211 + */ 212 + type ObjectOptions = { 213 + /** Human-readable description of the object */ 214 + description?: string; 215 + }; 216 + 217 type RequiredKeys<T> = { 218 [K in keyof T]: T[K] extends { required: true } ? K : never; 219 }[keyof T]; ··· 226 * Resulting object schema with required and nullable fields extracted. 227 * @see https://atproto.com/specs/lexicon#object 228 */ 229 + type ObjectResult<T extends ObjectProperties, O extends ObjectOptions = {}> = { 230 type: "object"; 231 /** Property definitions */ 232 properties: { ··· 239 : { required: UnionToTuple<RequiredKeys<T>> }) & 240 ([NullableKeys<T>] extends [never] 241 ? {} 242 + : { nullable: UnionToTuple<NullableKeys<T>> }) & 243 + O; 244 245 /** 246 * Map of parameter names to their lexicon item definitions. ··· 539 * Creates an object type with defined properties. 540 * @see https://atproto.com/specs/lexicon#object 541 */ 542 + object<T extends ObjectProperties, O extends ObjectOptions>( 543 + properties: T, 544 + options?: O, 545 + ): ObjectResult<T, O> { 546 + const required = Object.keys(properties).filter( 547 + (key) => "required" in properties[key] && properties[key].required, 548 ); 549 + const nullable = Object.keys(properties).filter( 550 + (key) => "nullable" in properties[key] && properties[key].nullable, 551 ); 552 const result: Record<string, unknown> = { 553 type: "object", 554 + properties, 555 + ...options, 556 }; 557 if (required.length > 0) { 558 result.required = required; ··· 560 if (nullable.length > 0) { 561 result.nullable = nullable; 562 } 563 + return result as ObjectResult<T, O>; 564 }, 565 /** 566 * Creates a params type for query string parameters.
+1 -1
packages/prototypey/core/tests/from-json-infer.test.ts
··· 1220 1221 attest(lexicon["~infer"]).type.toString.snap(`{ 1222 $type: "app.bsky.actor.profile" 1223 - displayName?: string | undefined 1224 description?: string | undefined 1225 }`); 1226 });
··· 1220 1221 attest(lexicon["~infer"]).type.toString.snap(`{ 1222 $type: "app.bsky.actor.profile" 1223 description?: string | undefined 1224 + displayName?: string | undefined 1225 }`); 1226 });
+73
packages/prototypey/core/tests/primitives.test.ts
··· 183 }); 184 }); 185 186 test("lx.token() with interaction event", () => { 187 const result = lx.token( 188 "Request that less content like the given feed item be shown in the feed",
··· 183 }); 184 }); 185 186 + test("lx.object() basic", () => { 187 + const result = lx.object({ 188 + name: lx.string(), 189 + }); 190 + expect(result).toEqual({ 191 + type: "object", 192 + properties: { 193 + name: { type: "string" }, 194 + }, 195 + }); 196 + }); 197 + 198 + test("lx.object() with description", () => { 199 + const result = lx.object( 200 + { 201 + enabled: lx.boolean({ 202 + default: true, 203 + description: "Whether this feature is enabled.", 204 + }), 205 + }, 206 + { 207 + description: "Configuration options for the feature.", 208 + }, 209 + ); 210 + expect(result).toEqual({ 211 + type: "object", 212 + description: "Configuration options for the feature.", 213 + properties: { 214 + enabled: { 215 + type: "boolean", 216 + default: true, 217 + description: "Whether this feature is enabled.", 218 + }, 219 + }, 220 + }); 221 + }); 222 + 223 + test("lx.object() with required and description", () => { 224 + const result = lx.object( 225 + { 226 + id: lx.string({ required: true }), 227 + name: lx.string(), 228 + }, 229 + { description: "User profile object" }, 230 + ); 231 + expect(result).toEqual({ 232 + type: "object", 233 + description: "User profile object", 234 + properties: { 235 + id: { type: "string", required: true }, 236 + name: { type: "string" }, 237 + }, 238 + required: ["id"], 239 + }); 240 + }); 241 + 242 + test("lx.object() with nullable and description", () => { 243 + const result = lx.object( 244 + { 245 + bio: lx.string({ nullable: true }), 246 + }, 247 + { description: "Optional profile fields" }, 248 + ); 249 + expect(result).toEqual({ 250 + type: "object", 251 + description: "Optional profile fields", 252 + properties: { 253 + bio: { type: "string", nullable: true }, 254 + }, 255 + nullable: ["bio"], 256 + }); 257 + }); 258 + 259 test("lx.token() with interaction event", () => { 260 const result = lx.token( 261 "Request that less content like the given feed item be shown in the feed",