Skip to main content
Glama

Bucket Feature Flags MCP Server

Official
by reflagcom
json.ts6.12 kB
export type JSONPrimitive = | number | string | boolean | null | JSONPrimitive[] | { [key: string]: JSONPrimitive }; export type PrimitiveAST = { kind: "primitive"; type: string }; export type ArrayAST = { kind: "array"; elementType: TypeAST }; export type ObjectAST = { kind: "object"; properties: { key: string; type: TypeAST; optional: boolean }[]; }; export type UnionAST = { kind: "union"; types: TypeAST[] }; // Type AST to represent TypeScript types export type TypeAST = PrimitiveAST | ArrayAST | ObjectAST | UnionAST; // Convert JSON value to TypeAST export function toTypeAST(value: JSONPrimitive, path: string[] = []): TypeAST { if (value === null) return { kind: "primitive", type: "null" }; if (Array.isArray(value)) { if (value.length === 0) { return { kind: "array", elementType: { kind: "primitive", type: "any" }, }; } // Process all elements in the array instead of just the first one const elementTypes = value.map((item, index) => toTypeAST(item, [...path, index.toString()]), ); return { kind: "array", elementType: mergeTypeASTs(elementTypes), }; } if (typeof value === "object") { return { kind: "object", properties: Object.entries(value).map(([key, val]) => ({ key, type: toTypeAST(val, [...path, key]), optional: false, })), }; } return { kind: "primitive", type: typeof value }; } // Merge multiple TypeASTs into one export function mergeTypeASTs(types: TypeAST[]): TypeAST { if (types.length === 0) return { kind: "primitive", type: "any" }; if (types.length === 1) return types[0]; // Group ASTs by kind const byKind = { union: types.filter((t) => t.kind === "union"), primitive: types.filter((t) => t.kind === "primitive"), array: types.filter((t) => t.kind === "array"), object: types.filter((t) => t.kind === "object"), }; // Create a union for mixed kinds const hasMixedKinds = byKind.union.length > 0 || // If we have any unions, treat it as mixed kinds (byKind.primitive.length > 0 && (byKind.array.length > 0 || byKind.object.length > 0)) || (byKind.array.length > 0 && byKind.object.length > 0); if (hasMixedKinds) { // If there are existing unions, flatten them into the current union if (byKind.union.length > 0) { // Flatten existing unions and collect types by category const flattenedTypes: TypeAST[] = []; const objectsToMerge: ObjectAST[] = [...byKind.object]; const arraysToMerge: ArrayAST[] = [...byKind.array]; // Add primitives directly flattenedTypes.push(...byKind.primitive); // Process union types for (const unionType of byKind.union) { for (const type of unionType.types) { if (type.kind === "object") { objectsToMerge.push(type); } else if (type.kind === "array") { arraysToMerge.push(type); } else { flattenedTypes.push(type); } } } // Merge objects and arrays if they exist if (objectsToMerge.length > 0) { flattenedTypes.push(mergeTypeASTs(objectsToMerge)); } if (arraysToMerge.length > 0) { flattenedTypes.push(mergeTypeASTs(arraysToMerge)); } return { kind: "union", types: flattenedTypes }; } return { kind: "union", types }; } // Handle primitives if (byKind.primitive.length === types.length) { const uniqueTypes = [...new Set(byKind.primitive.map((p) => p.type))]; return uniqueTypes.length === 1 ? { kind: "primitive", type: uniqueTypes[0] } : { kind: "union", types: uniqueTypes.map((type) => ({ kind: "primitive", type })), }; } // Merge arrays if (byKind.array.length === types.length) { return { kind: "array", elementType: mergeTypeASTs(byKind.array.map((a) => a.elementType)), }; } // Merge objects if (byKind.object.length === types.length) { // Get all unique property keys const allKeys = [ ...new Set( byKind.object.flatMap((obj) => obj.properties.map((p) => p.key)), ), ]; // Merge properties with same keys const mergedProperties = allKeys.map((key) => { const props = byKind.object .map((obj) => obj.properties.find((p) => p.key === key)) .filter((obj) => !!obj); return { key, type: mergeTypeASTs(props.map((p) => p.type)), optional: byKind.object.some( (obj) => !obj.properties.some((p) => p.key === key), ), }; }); return { kind: "object", properties: mergedProperties }; } // Fallback return { kind: "primitive", type: "any" }; } // Stringify TypeAST to TypeScript type declaration export function stringifyTypeAST(ast: TypeAST, nestLevel = 0): string { const indent = " ".repeat(nestLevel * 2); const nextIndent = " ".repeat((nestLevel + 1) * 2); switch (ast.kind) { case "primitive": return ast.type; case "array": return `(${stringifyTypeAST(ast.elementType, nestLevel)})[]`; case "object": if (ast.properties.length === 0) return "{}"; return `{\n${ast.properties .map(({ key, optional, type }) => { return `${nextIndent}${quoteKey(key)}${optional ? "?" : ""}: ${stringifyTypeAST( type, nestLevel + 1, )}`; }) .join(",\n")}\n${indent}}`; case "union": if (ast.types.length === 0) return "any"; if (ast.types.length === 1) return stringifyTypeAST(ast.types[0], nestLevel); return ast.types .map((type) => stringifyTypeAST(type, nestLevel)) .join(" | "); } } export function quoteKey(key: string): string { return /[^a-zA-Z0-9_]/.test(key) || /^[0-9]/.test(key) ? `"${key}"` : key; } // Convert JSON array to TypeScript type export function JSONToType(json: JSONPrimitive[]): string | null { if (!json.length) return null; return stringifyTypeAST(mergeTypeASTs(json.map((item) => toTypeAST(item)))); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/reflagcom/bucket-javascript-sdk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server