catalog-display.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /**
  2. * Shared utility for extracting catalog data for display in the UI.
  3. * Used by both the demo and playground components.
  4. */
  5. export interface CatalogField {
  6. name: string;
  7. type: string;
  8. }
  9. export interface CatalogComponentInfo {
  10. name: string;
  11. description: string;
  12. props: CatalogField[];
  13. slots: string[];
  14. events: string[];
  15. }
  16. export interface CatalogActionInfo {
  17. name: string;
  18. description: string;
  19. params: CatalogField[];
  20. }
  21. export interface CatalogDisplayData {
  22. components: CatalogComponentInfo[];
  23. actions: CatalogActionInfo[];
  24. }
  25. /**
  26. * Extract field names and types from a Zod schema object.
  27. * Supports both Zod v3 and v4 shape formats.
  28. */
  29. export function extractFields(zodObj: unknown): CatalogField[] {
  30. if (!zodObj) return [];
  31. try {
  32. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  33. const obj = zodObj as any;
  34. // Zod v4: shape is a plain object; Zod v3: shape is via _def.shape()
  35. const shape =
  36. typeof obj.shape === "object"
  37. ? obj.shape
  38. : typeof obj._def?.shape === "function"
  39. ? obj._def.shape()
  40. : typeof obj._def?.shape === "object"
  41. ? obj._def.shape
  42. : null;
  43. if (!shape) return [];
  44. return Object.entries(shape).map(([name, schema]) => {
  45. let type = "unknown";
  46. try {
  47. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  48. const s = schema as any;
  49. const typeName: string = s?._zod?.def?.type ?? s?._def?.typeName ?? "";
  50. if (typeName.includes("string")) type = "string";
  51. else if (typeName.includes("number")) type = "number";
  52. else if (typeName.includes("boolean")) type = "boolean";
  53. else if (typeName.includes("array")) type = "array";
  54. else if (typeName.includes("enum")) {
  55. const values = s?._zod?.def?.values ?? s?._def?.values;
  56. type = Array.isArray(values) ? values.join(" | ") : "enum";
  57. } else if (typeName.includes("union")) type = "union";
  58. else if (typeName.includes("nullable")) {
  59. const inner = s?._zod?.def?.innerType ?? s?._def?.innerType;
  60. const innerName: string =
  61. inner?._zod?.def?.type ?? inner?._def?.typeName ?? "";
  62. if (innerName.includes("string")) type = "string?";
  63. else if (innerName.includes("number")) type = "number?";
  64. else if (innerName.includes("boolean")) type = "boolean?";
  65. else if (innerName.includes("array")) type = "array?";
  66. else if (innerName.includes("enum")) {
  67. const values = inner?._zod?.def?.values ?? inner?._def?.values;
  68. type = Array.isArray(values) ? `(${values.join(" | ")})?` : "enum?";
  69. } else type = "optional";
  70. }
  71. } catch {
  72. // ignore
  73. }
  74. return { name, type };
  75. });
  76. } catch {
  77. return [];
  78. }
  79. }
  80. /**
  81. * Extract display data from a catalog's raw data.
  82. * Parses component definitions and action definitions into a
  83. * structured format suitable for rendering in the UI.
  84. */
  85. export function buildCatalogDisplayData(
  86. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  87. rawCatalogData: any,
  88. ): CatalogDisplayData {
  89. const components = Object.entries(rawCatalogData.components ?? {})
  90. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  91. .map(([name, def]: [string, any]) => ({
  92. name,
  93. description: (def.description as string) ?? "",
  94. props: extractFields(def.props),
  95. slots: (def.slots as string[]) ?? [],
  96. events: (def.events as string[]) ?? [],
  97. }))
  98. .sort((a, b) => a.name.localeCompare(b.name));
  99. const actions = Object.entries(rawCatalogData.actions ?? {})
  100. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  101. .map(([name, def]: [string, any]) => ({
  102. name,
  103. description: (def.description as string) ?? "",
  104. params: extractFields(def.params),
  105. }))
  106. .sort((a, b) => a.name.localeCompare(b.name));
  107. return { components, actions };
  108. }