import type { OpenApiSpec, ResourceConfig, FieldConfig } from "../types"; import { extractFields } from "./field-config"; import { extractRelationships } from "./relationship-config"; function detectPagination(pathObj: any): { limitParam: string; offsetParam: string; defaultLimit: number } | null { const params = pathObj?.get?.parameters ?? []; const limit = params.find((p: any) => p.in === "query" && p.name === "limit"); const offset = params.find((p: any) => p.in === "query" && p.name === "offset"); if (limit && offset) { return { limitParam: "limit", offsetParam: "offset", defaultLimit: limit.schema.default, }; } return null; } function hasOperation(pathObj: any, method: string): boolean { return !!pathObj?.[method]; } function sortFields(fields: FieldConfig[]): FieldConfig[] { return [...fields].sort((a, b) => { const orderDiff = a.order - b.order; if (orderDiff !== 0) return orderDiff; return a.name.localeCompare(b.name); }); } function formatDisplayName(name: string): string { return name.split(/[-_]/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" "); } const SSE_RECEIVED_FIELD: FieldConfig = { name: "_received_at", label: "Received", description: "Timestamp when the event was received", type: "string", format: "date-time", order: 0, hidden: {}, filterable: false, sortable: true, readOnly: true, required: false, isArray: false, }; export function buildResourceConfigs(spec: OpenApiSpec): ResourceConfig[] { const schemas = spec.components?.schemas ?? {}; const paths = spec.paths ?? {}; const configs: ResourceConfig[] = []; for (const [schemaName, schema] of Object.entries(schemas)) { if (!schema || typeof schema !== "object") continue; const resourceName = schema["x-resource"]; if (!resourceName || typeof resourceName !== "string") continue; const resourcePath = `/${resourceName}`; const itemPath = `${resourcePath}/{id}`; const collectionPathObj = paths[resourcePath]; const itemPathObj = paths[itemPath]; const fields = extractFields(schemaName, schema, schemas); const relationships = extractRelationships(schema, schemas); const hasSSE = collectionPathObj?.get?.["x-sse"] === true; const resource: ResourceConfig = { name: resourceName, schemaName, displayName: formatDisplayName(resourceName), path: resourcePath, primaryKey: schema["x-primary-key"], displayFormat: schema["x-display-format"], listColumns: schema["x-list-columns"], fields, orderedFields: sortFields(fields), operations: { list: hasOperation(collectionPathObj, "get"), get: hasOperation(itemPathObj, "get"), create: hasOperation(collectionPathObj, "post"), update: hasOperation(itemPathObj, "put") || hasOperation(itemPathObj, "patch"), delete: hasOperation(itemPathObj, "delete"), }, updateMethod: hasOperation(itemPathObj, "patch") && !hasOperation(itemPathObj, "put") ? "patch" : "put", pagination: detectPagination(collectionPathObj), relationships, streaming: hasSSE || undefined, }; if (hasSSE) { resource.operations = { list: true, get: false, create: false, update: false, delete: false }; resource.updateMethod = "put"; resource.pagination = null; resource.relationships = []; resource.fields = [SSE_RECEIVED_FIELD, ...fields.map((f) => ({ ...f, readOnly: true }))]; resource.orderedFields = sortFields(resource.fields); resource.listColumns = ["_received_at", ...resource.listColumns]; resource.primaryKey = "_received_at"; } configs.push(resource); } return configs; }