UNPKG

@seamapi/blueprint

Version:

Build tools for the Seam API using this blueprint.

1,417 lines (1,404 loc) 72.7 kB
'use strict'; var zod = require('zod'); var changeCase = require('change-case'); // src/lib/blueprint.ts // src/lib/openapi/find-common-openapi-schema-properties.ts function findCommonOpenapiSchemaProperties(schemas) { const firstSchema = schemas[0]; if (schemas.length === 0 || firstSchema?.properties == null) { return {}; } return Object.entries(firstSchema.properties).reduce((commonProps, [propKey, propValue]) => { const isPropInAllSchemas = schemas.every( (schema) => Object.keys(schema.properties ?? {}).includes(propKey) ); if (!isPropInAllSchemas) { return commonProps; } if ("enum" in propValue) { const mergedEnumValues = schemas.reduce((allEnums, schema) => { const enumValues = schema.properties?.[propKey]?.enum ?? []; return [.../* @__PURE__ */ new Set([...allEnums, ...enumValues])]; }, []); return { ...commonProps, [propKey]: { ...propValue, enum: mergedEnumValues } }; } return { ...commonProps, [propKey]: propValue }; }, {}); } // src/lib/openapi/flatten-openapi-schema.ts var flattenOpenapiSchema = (schema) => { if ("allOf" in schema && Array.isArray(schema.allOf)) { return flattenAllOfSchema(schema); } if ("oneOf" in schema && Array.isArray(schema.oneOf)) { return flattenOneOfSchema(schema); } if (schema.type === "object" && schema.properties != null) { const flattenedProperties = {}; for (const [propKey, propSchema] of Object.entries(schema.properties)) { flattenedProperties[propKey] = flattenOpenapiSchema(propSchema); } return { ...schema, properties: flattenedProperties }; } if (schema.type === "array" && schema.items != null) { return { ...schema, items: flattenOpenapiSchema(schema.items) }; } return schema; }; var flattenAllOfSchema = (schema) => { const flattenedSchema = { type: "object", properties: {}, required: [], ...schema?.description != null && { description: schema.description }, ...schema["x-property-groups"] != null && { "x-property-groups": schema["x-property-groups"] } }; const flattenedSubschemas = schema.allOf.map(flattenOpenapiSchema); for (const flattenedSubschema of flattenedSubschemas) { if (flattenedSubschema.properties != null) { flattenedSchema.properties = { ...flattenedSchema.properties, ...flattenedSubschema.properties }; } if (flattenedSubschema.required != null && Array.isArray(flattenedSubschema.required)) { flattenedSchema.required = Array.from( /* @__PURE__ */ new Set([...flattenedSchema.required, ...flattenedSubschema.required]) ); } } for (const [propKey, propSchema] of Object.entries( flattenedSchema.properties )) { if ("enum" in propSchema && Array.isArray(propSchema.enum)) { const enumValues = /* @__PURE__ */ new Set(); for (const subschema of flattenedSubschemas) { const enumProp = subschema.properties?.[propKey]; if (enumProp != null && "enum" in enumProp && Array.isArray(enumProp.enum)) { enumProp.enum.forEach((val) => enumValues.add(val)); } } flattenedSchema.properties[propKey] = { ...propSchema, enum: Array.from(enumValues) }; } } return flattenedSchema; }; var flattenOneOfSchema = (schema) => { const baseFlattenedSchema = { ...schema?.description != null && { description: schema.description } }; const flattenedSubschemas = schema.oneOf.map(flattenOpenapiSchema); if (flattenedSubschemas.every( (s) => s.type === "string" && Array.isArray(s.enum) )) { const mergedEnums = Array.from( new Set(flattenedSubschemas.flatMap((s) => s.enum ?? [])) ); return { ...baseFlattenedSchema, type: "string", enum: mergedEnums }; } else { let mergedProperties = {}; const requiredFieldsLists = []; for (const flattenedSubschema of flattenedSubschemas) { if (flattenedSubschema.properties != null) { mergedProperties = { ...mergedProperties, ...flattenedSubschema.properties }; } if (flattenedSubschema.required != null && Array.isArray(flattenedSubschema.required)) { requiredFieldsLists.push(flattenedSubschema.required); } else { requiredFieldsLists.push([]); } } for (const [propKey, propSchema] of Object.entries(mergedProperties)) { if ("enum" in propSchema && Array.isArray(propSchema.enum)) { const enumValues = /* @__PURE__ */ new Set(); for (const subschema of flattenedSubschemas) { const enumProp = subschema.properties?.[propKey]; if (enumProp != null && "enum" in enumProp && Array.isArray(enumProp.enum)) { enumProp.enum.forEach((val) => enumValues.add(val)); } } mergedProperties[propKey] = { ...propSchema, enum: Array.from(enumValues) }; } } let commonRequiredFields = []; if (requiredFieldsLists.length > 0) { commonRequiredFields = requiredFieldsLists.reduce( (commonRequired, currentRequiredFields) => commonRequired.filter( (field) => currentRequiredFields.includes(field) ) ); } return { ...baseFlattenedSchema, type: "object", properties: mergedProperties, required: commonRequiredFields }; } }; var ParameterSchema = zod.z.object({ name: zod.z.string(), in: zod.z.enum(["query", "header", "path", "cookie"]), description: zod.z.string().default(""), required: zod.z.boolean().default(false), schema: zod.z.object({ type: zod.z.string(), format: zod.z.string().optional() }).optional(), deprecated: zod.z.boolean().default(false), "x-undocumented": zod.z.string().default(""), "x-deprecated": zod.z.string().default(""), "x-draft": zod.z.string().default("") }); var ResponseSchema = zod.z.record( zod.z.string(), zod.z.object({ description: zod.z.string(), content: zod.z.record( zod.z.string(), zod.z.object({ schema: zod.z.object({ $ref: zod.z.string().optional(), type: zod.z.string().optional(), items: zod.z.object({ $ref: zod.z.string() }).optional() }) }) ).optional() }) ); var AuthMethodSchema = zod.z.enum([ "api_key", "client_session", "console_session_token_with_workspace", "console_session_token_without_workspace", "pat_with_workspace", "pat_without_workspace", "publishable_key", "unknown" ]).catch("unknown"); var OpenapiOperationSchema = zod.z.object({ operationId: zod.z.string(), summary: zod.z.string().optional(), description: zod.z.string().default(""), parameters: zod.z.array(ParameterSchema).optional(), requestBody: zod.z.object({ content: zod.z.record( zod.z.string(), zod.z.object({ schema: zod.z.object({ $ref: zod.z.string().optional(), type: zod.z.string().optional() }) }) ) }).optional(), responses: ResponseSchema, security: zod.z.array(zod.z.record(zod.z.string().pipe(AuthMethodSchema), zod.z.array(zod.z.never()))).default([]), deprecated: zod.z.boolean().default(false), "x-response-key": zod.z.string().nullable().optional(), "x-title": zod.z.string().default(""), "x-undocumented": zod.z.string().default(""), "x-deprecated": zod.z.string().default(""), "x-draft": zod.z.string().default(""), "x-action-attempt-type": zod.z.string().optional() }); var EnumValueSchema = zod.z.object({ description: zod.z.string().default(""), undocumented: zod.z.string().default(""), deprecated: zod.z.string().default(""), draft: zod.z.string().default("") }); var PropertyGroupSchema = zod.z.record( zod.z.string(), zod.z.object({ name: zod.z.string() }) ).default({}); var VariantGroupSchema = PropertyGroupSchema; var commonPropertyFields = { description: zod.z.string().default(""), deprecated: zod.z.boolean().default(false), default: zod.z.any().optional(), "x-undocumented": zod.z.string().default(""), "x-deprecated": zod.z.string().default(""), "x-draft": zod.z.string().default(""), "x-property-group-key": zod.z.string().default(""), "x-property-groups": PropertyGroupSchema, "x-variant-groups": VariantGroupSchema }; var PropertySchema = zod.z.union([ zod.z.object({ type: zod.z.enum(["string", "number", "integer", "boolean", "array", "object"]), ...commonPropertyFields, enum: zod.z.array(zod.z.string().or(zod.z.boolean())).optional(), "x-enums": zod.z.record(zod.z.string(), EnumValueSchema).optional(), format: zod.z.string().optional(), $ref: zod.z.string().optional() }), zod.z.object({ oneOf: zod.z.array(zod.z.lazy(() => PropertySchema)), ...commonPropertyFields, discriminator: zod.z.object({ propertyName: zod.z.string() }).optional() }), zod.z.object({ allOf: zod.z.array(zod.z.lazy(() => PropertySchema)), ...commonPropertyFields }) ]); var ResourceSchema = zod.z.object({ type: zod.z.literal("object"), properties: zod.z.record(zod.z.string(), PropertySchema), required: zod.z.array(zod.z.string()).default([]), description: zod.z.string().default(""), "x-route-path": zod.z.string().default(""), "x-undocumented": zod.z.string().default(""), "x-deprecated": zod.z.string().default(""), "x-draft": zod.z.string().default(""), "x-property-groups": PropertyGroupSchema, "x-variant-groups": VariantGroupSchema }); var EventResourceSchema = zod.z.object({ "x-route-path": zod.z.string().default(""), discriminator: zod.z.object({ propertyName: zod.z.string() }), oneOf: zod.z.array(ResourceSchema) }); var LiteralSchema = zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]); var JsonSchema = zod.z.lazy( () => zod.z.union([LiteralSchema, zod.z.array(JsonSchema), zod.z.record(JsonSchema)]) ); // src/lib/samples/json.ts var createJsonResponse = ({ response, title }, context) => { const { endpoint } = context; if (endpoint.response.responseType === "void") { return JSON.stringify({}); } const { responseKey } = endpoint.response; const data = response?.body?.[responseKey]; if (data == null) { throw new Error(`Missing ${responseKey} for '${title}'`); } return JSON.stringify(data); }; var createJsonResourceData = ({ properties }, _context) => { return JSON.stringify(properties); }; // src/lib/samples/csharp.ts var createCsharpRequest = ({ request }, _context) => { const parts = request.path.split("/").slice(1); const requestArgs = formatCsharpArgs(request.parameters); return `seam.${parts.map((p) => changeCase.pascalCase(p)).join(".")}(${requestArgs})`; }; var formatCsharpArgs = (jsonParams) => Object.entries(jsonParams).map(([key, value]) => { const formattedValue = formatCsharpValue(key, value); return `${changeCase.camelCase(key)}: ${formattedValue}`; }).join(", "); var formatCsharpValue = (key, value) => { if (value == null) return "null"; if (typeof value === "boolean") return value.toString(); if (typeof value === "number") return value.toString(); if (typeof value === "string") return `"${value}"`; if (Array.isArray(value)) { return formatCsharpArray(key, value); } if (typeof value === "object") { return formatCsharpObject(value); } throw new Error(`Unsupported type: ${typeof value}`); }; var formatCsharpArray = (key, value) => { if (value.length === 0) { return "new object[] { }"; } const formattedItems = value.map((v) => formatCsharpValue(key, v)); const item = value[0]; if (item == null) { throw new Error(`Null value in response array for '${key}'`); } const arrayType = isPrimitiveValue(item) ? getPrimitiveTypeName(item) : "object"; return `new ${arrayType}[] { ${formattedItems.join(", ")}} `; }; var isPrimitiveValue = (value) => value !== null && typeof value !== "object"; var getPrimitiveTypeName = (value) => { switch (typeof value) { case "string": return "string"; case "number": return "float"; case "boolean": return "bool"; default: throw new Error(`Unsupported type: ${typeof value}`); } }; var formatCsharpObject = (value) => { if (Object.keys(value).length === 0) { return "new { }"; } const formattedEntries = Object.entries(value).map( ([objKey, val]) => `${changeCase.camelCase(objKey)} = ${formatCsharpValue(objKey, val)}` ).join(", "); return `new { ${formattedEntries} }`; }; var createCsharpResponse = createJsonResponse; // src/lib/samples/curl.ts var BASE_URL = "https://connect.getseam.com"; var createCurlRequest = ({ request }, _context) => { const method = "POST"; const url = `${BASE_URL}${request.path}`; let curlCommand = `curl --include --request ${method} "${url}" \\ `; curlCommand += ' --header "Authorization: Bearer $SEAM_API_KEY"'; const params = request.parameters; const hasParams = Object.keys(params).length > 0; if (hasParams) { curlCommand += " \\\n"; curlCommand += " --json @- << EOF\n"; curlCommand += JSON.stringify(request.parameters, null, 2); curlCommand += "\nEOF"; } return curlCommand; }; var createCurlResponse = ({ response, title }, context) => { const { endpoint } = context; if (endpoint.response.responseType === "void") { return JSON.stringify({}); } const { responseKey } = endpoint.response; const data = response?.body?.[responseKey]; if (data == null) { throw new Error(`Missing ${responseKey} for '${title}'`); } return JSON.stringify({ [responseKey]: data }); }; // src/lib/samples/format.ts var formatCodeRecords = async (code, context) => { const entries = Object.entries(code); const formattedEntries = await Promise.all( entries.map(async (entry) => { if (entry == null) throw new Error("Unexpected null code entry"); return await formatCodeEntry(entry, context); }) ); return Object.fromEntries(formattedEntries); }; var formatCodeEntry = async ([key, code], { formatCode }) => { if (code == null) throw new Error(`Unexpected null in code object for ${key}`); const [request, response] = await Promise.all([ await formatCode(code.request, code.request_syntax), await formatCode(code.response, code.response_syntax) ]); return [ key, { ...code, request, response } ]; }; var formatResourceRecords = async (resource, context) => { const entries = Object.entries(resource); const formattedEntries = await Promise.all( entries.map(async (entry) => { if (entry == null) throw new Error("Unexpected null resource entry"); return await formatResourceEntry(entry, context); }) ); return Object.fromEntries(formattedEntries); }; var formatResourceEntry = async ([key, resource], { formatCode }) => { if (resource == null) { throw new Error(`Unexpected null in resource object for ${key}`); } const resourceData = await formatCode( resource.resource_data, resource.resource_data_syntax ); return [ key, { ...resource, resource_data: resourceData } ]; }; var defaultGoPackageName = "api"; var goPackageBasePath = "github.com/seamapi/go"; var createGoRequest = ({ request }, _context) => { const isReqWithParams = Object.keys(request.parameters).length !== 0; const goPackageName = getGoPackageName(request.path); const goSdkImports = generateImports({ goPackageName, isReqWithParams }); const requestStructName = getRequestStructName(request.path); const formattedArgs = formatGoRequestArgs(request.parameters, { goPackageName, requestStructName }); const goSdkRequestArgs = `context.Background()${isReqWithParams ? `, ${goPackageName}.${requestStructName}{ ${formattedArgs}, }` : ""}`; const pathParts = request.path.split("/"); return `package main ${goSdkImports} func main() { client${pathParts.map((p) => changeCase.pascalCase(p)).join(".")}(${isReqWithParams ? "\n" : ""}${goSdkRequestArgs}, ) } `.trim(); }; var getGoPackageName = (path) => { if (!isPathNested(path)) { return defaultGoPackageName; } const firstPathPart = path.split("/").slice(1)[1]; if (firstPathPart == null) { throw new Error(`Invalid path: missing second part in "${path}"`); } return firstPathPart.replace(/_/g, ""); }; var isPathNested = (path) => path.split("/").slice(1).length > 2; var generateImports = ({ goPackageName, isReqWithParams }) => { const imports = []; if (isReqWithParams) { const defaultPackageImport = `import ${defaultGoPackageName} "${goPackageBasePath}"`; imports.push(defaultPackageImport); } if (goPackageName !== defaultGoPackageName && isReqWithParams) { const nestedPackageImport = `import ${goPackageName} "${goPackageBasePath}/${goPackageName}"`; imports.push(nestedPackageImport); } return imports.join("\n"); }; var getRequestStructName = (path) => { const requestStructNameSuffix = "Request"; return isPathNested(path) ? `${changeCase.pascalCase(removeUntilSecondSlash(path))}${requestStructNameSuffix}` : `${changeCase.pascalCase(path)}${requestStructNameSuffix}`; }; var removeUntilSecondSlash = (str) => str.replace(/^\/[^/]*/, ""); var formatGoRequestArgs = (jsonParams, context) => Object.entries(jsonParams).map(([paramKey, paramValue]) => { const formattedValue = formatGoRequestArgValue( paramKey, paramValue, context ); return `${changeCase.pascalCase(paramKey)}: ${formattedValue}`; }).join(",\n"); var formatGoRequestArgValue = (key, value, context) => { if (value == null) return "nil"; if (typeof value === "string") return `${defaultGoPackageName}.String("${value}")`; if (typeof value === "boolean") return `${defaultGoPackageName}.Bool(${value})`; if (typeof value === "number") return `${defaultGoPackageName}.Float64(${value})`; if (Array.isArray(value)) { return formatGoRequestArrayValue(key, value, context); } if (typeof value === "object") { return formatGoRequestObjectValue(key, value, context); } throw new Error(`Unsupported type: ${typeof value}`); }; var formatGoRequestArrayValue = (key, value, context) => { if (value.length === 0) { return "nil"; } const formattedItems = value.map( (v) => formatGoRequestArgValue(key, v, context) ); const item = value[0]; if (item == null) { throw new Error(`Null value in response array for '${key}'`); } const { goPackageName, requestStructName } = context; const arrayType = isPrimitiveValue2(item) ? getPrimitiveTypeName2(item) : `${goPackageName}.${changeCase.pascalCase(`${requestStructName} ${key}`)}Item`; return `[${value.length}]${arrayType}{${formattedItems.join(", ")}}`; }; var isPrimitiveValue2 = (value) => typeof value !== "object" && value !== null; var getPrimitiveTypeName2 = (value) => { switch (typeof value) { case "string": return "string"; case "number": return "float64"; case "boolean": return "bool"; } }; var formatGoRequestObjectValue = (key, value, context) => { if (Object.keys(value).length === 0) { return "struct{}{}"; } const formattedEntries = Object.entries(value).map( ([objKey, val]) => `${changeCase.pascalCase(objKey)}: ${formatGoRequestArgValue(objKey, val, context)}` ).join(", "); const { goPackageName, requestStructName } = context; return `${goPackageName}.${changeCase.pascalCase(`${requestStructName} ${key}`)}{${formattedEntries}}`; }; var createGoResponse = ({ response, title }, context) => { const { endpoint } = context; if (endpoint.response.responseType === "void") return "nil"; const { responseKey, resourceType } = endpoint.response; const responseValue = response?.body?.[responseKey]; if (responseValue == null) { throw new Error(`Missing ${responseKey} for '${title}'`); } const responseResourceGoStructName = changeCase.pascalCase(resourceType); return Array.isArray(responseValue) ? formatGoArrayResponse(responseValue, responseResourceGoStructName, title) : formatGoResponse(responseValue, responseResourceGoStructName); }; var formatGoArrayResponse = (responseArray, responseResourceGoStructName, title) => { const formattedItems = responseArray.map((v) => { if (v == null) { throw new Error(`Null value in response array for '${title}'`); } return formatGoResponse(v, responseResourceGoStructName); }).join(", "); return `[]${defaultGoPackageName}.${responseResourceGoStructName}{${formattedItems}}`; }; var formatGoResponse = (responseParams, responseResourceGoStructName) => { const params = formatGoResponseParams( responseParams, responseResourceGoStructName ); return `${defaultGoPackageName}.${responseResourceGoStructName}{${params}}`; }; var formatGoResponseParams = (jsonParams, responseResourceGoStructName) => Object.entries(jsonParams).map(([paramKey, paramValue]) => { const formattedValue = formatGoResponseParamValue( { key: paramKey, value: paramValue, propertyChain: [] }, responseResourceGoStructName ); return `${changeCase.pascalCase(paramKey)}: ${formattedValue}`; }).join(", "); var formatGoResponseParamValue = ({ key, value, propertyChain }, responseResourceGoStructName) => { if (value === null) return "nil"; if (typeof value === "boolean") return value.toString(); if (typeof value === "number") return value.toString(); if (typeof value === "string") return `"${value}"`; if (Array.isArray(value)) { return formatGoResponseArrayValue( { key, value, propertyChain }, responseResourceGoStructName ); } if (typeof value === "object") { return formatGoResponseObjectValue( { key, value, propertyChain }, responseResourceGoStructName ); } throw new Error(`Unsupported type: ${typeof value}`); }; var formatGoResponseArrayValue = ({ key, value, propertyChain }, responseResourceGoStructName) => { if (value.length === 0) { return "nil"; } const rawItem = value[0]; if (rawItem == null) { throw new Error(`Null value in response array for '${key}'`); } const updatedPropertyChain = [...propertyChain, key]; const formattedItems = value.map( (v) => formatGoResponseParamValue( { key, value: v, propertyChain: updatedPropertyChain }, responseResourceGoStructName ) ); if (isPrimitiveValue2(rawItem)) { const arrayType = getPrimitiveTypeName2(rawItem); return `[]${arrayType}{${formattedItems.join(", ")}}`; } else { const structName = getStructName( updatedPropertyChain, responseResourceGoStructName ); return `[]${structName}{${formattedItems.join(", ")}}`; } }; var formatGoResponseObjectValue = ({ key, value, propertyChain }, responseResourceGoStructName) => { if (Object.keys(value).length === 0) { return "struct{}{}"; } const updatedPropertyChain = [...propertyChain, key]; const structName = getStructName( updatedPropertyChain, responseResourceGoStructName ); const formattedEntries = Object.entries(value).map(([objKey, val]) => { const formattedValue = formatGoResponseParamValue( { key: objKey, value: val, propertyChain: updatedPropertyChain }, responseResourceGoStructName ); return `${changeCase.pascalCase(objKey)}: ${formattedValue}`; }).join(", "); return `${defaultGoPackageName}.${structName}{${formattedEntries}}`; }; var getStructName = (propertyChain, responseResourceGoStructName) => { const fullPropertyChain = [responseResourceGoStructName, ...propertyChain]; return changeCase.pascalCase(fullPropertyChain.join("_")); }; var createJavaRequest = ({ request }, _context) => { const pathParts = request.path.split("/").slice(1); const requestBuilderOptions = { path: request.path, parameters: request.parameters }; const clientArgs = createJavaRequestBuilder(requestBuilderOptions); return `seam.${pathParts.map((p) => changeCase.camelCase(p)).join("().")}(${clientArgs});`; }; var createJavaRequestBuilder = ({ path, parameters }) => { const requestBuilderName = getRequestBuilderName(path); const isReqWithParams = Object.keys(parameters).length !== 0; if (!isReqWithParams) return ""; const formattedParams = formatJavaArgs(parameters); return `${requestBuilderName}.builder()${formattedParams}.build()`; }; var getRequestBuilderName = (path) => { const requestBuilderNameSuffix = "Request"; const pathParts = path.split("/").slice(1); return isPathNested2(pathParts) ? `${changeCase.pascalCase(pathParts.slice(1).join("_"))}${requestBuilderNameSuffix}` : `${changeCase.pascalCase(path)}${requestBuilderNameSuffix}`; }; var isPathNested2 = (pathParts) => pathParts.length > 2; var formatJavaArgs = (jsonParams) => Object.entries(jsonParams).map(([paramKey, paramValue]) => { const formattedValue = formatJavaValue(paramValue); return `.${changeCase.camelCase(paramKey)}(${formattedValue})`; }).join("\n"); var formatJavaValue = (value) => { if (value === null) return "null"; if (typeof value === "boolean") return value.toString(); if (typeof value === "number") return value.toString(); if (typeof value === "string") return `"${value}"`; if (Array.isArray(value)) { const formattedItems = value.map(formatJavaValue).join(", "); return `List.of(${formattedItems})`; } if (typeof value === "object") { const formattedEntries = Object.entries(value).map(([key, val]) => `"${key}", ${formatJavaValue(val)}`).join(", "); return `Map.of(${formattedEntries})`; } throw new Error(`Unsupported type: ${typeof value}`); }; var createJavaResponse = createJsonResponse; var createJavascriptRequest = ({ request }, _context) => { const parts = request.path.split("/"); const isWithoutParams = Object.keys(request.parameters).length === 0; const formattedParams = isWithoutParams ? "" : JSON.stringify(request.parameters); return `await seam${parts.map((p) => changeCase.camelCase(p)).join(".")}(${formattedParams})`; }; var createJavascriptResponse = createJsonResponse; var createPhpRequest = ({ request }, _context) => { const parts = request.path.split("/"); const requestParams = Object.entries(request.parameters).map(([key, value]) => `${key}: ${formatPhpValue(value)}`).join(","); return `<?php $seam${parts.map((p) => changeCase.snakeCase(p)).join("->")}(${requestParams})`; }; var createPhpResponse = ({ response, title }, context) => { const { endpoint } = context; if (endpoint.response.responseType === "void") return "null"; const { responseKey } = endpoint.response; const responseValue = response?.body?.[responseKey]; if (responseValue == null) { throw new Error(`Missing ${responseKey} for '${title}'`); } const formattedResponse = Array.isArray(responseValue) ? formatPhpArrayResponse(responseValue, title) : formatPhpResponse(responseValue); return `<?php ${formattedResponse}`; }; var formatPhpArrayResponse = (responseArray, title) => { const formattedItems = responseArray.map((item) => { if (item == null) { throw new Error(`Null value in response array for '${title}'`); } return formatPhpResponse(item); }).join(",\n"); return `[${formattedItems}]`; }; var formatPhpResponse = (responseParams) => { const values = Object.entries(responseParams).map( ([paramKey, paramValue]) => `"${changeCase.snakeCase(paramKey)}" => ${formatPhpValue(paramValue)}` ); return `[${values.join(",")}]`; }; var formatPhpValue = (value) => { if (value == null) return "null"; if (typeof value === "boolean") return value ? "true" : "false"; if (typeof value === "number") return value.toString(); if (typeof value === "string") return `"${value}"`; if (Array.isArray(value)) { const formattedItems = value.map(formatPhpValue).join(", "); return `[${formattedItems}]`; } if (typeof value === "object") { const formattedEntries = Object.entries(value).map(([key, val]) => `"${changeCase.snakeCase(key)}" => ${formatPhpValue(val)}`).join(", "); return `[${formattedEntries}]`; } throw new Error(`Unsupported type: ${typeof value}`); }; var responseKeyToPythonResourceNameMap = { event: "SeamEvent" }; var createPythonRequest = ({ request }, _context) => { const parts = request.path.split("/"); const params = formatPythonArgs(request.parameters); return `seam${parts.map((p) => changeCase.snakeCase(p)).join(".")}(${params})`; }; var formatPythonArgs = (jsonParams) => Object.entries(jsonParams).map(([paramKey, paramValue]) => { const formattedValue = paramValue == null ? "None" : JSON.stringify(paramValue); return `${changeCase.snakeCase(paramKey)}=${formattedValue}`; }).join(", "); var createPythonResponse = ({ response, title }, context) => { const { endpoint } = context; if (endpoint.response.responseType === "void") return "None"; const { responseKey, resourceType } = endpoint.response; const responseValue = response?.body?.[responseKey]; if (responseValue == null) { throw new Error(`Missing ${responseKey} for '${title}'`); } const responsePythonClassName = changeCase.pascalCase( responseKeyToPythonResourceNameMap[resourceType] ?? resourceType ); return Array.isArray(responseValue) ? formatPythonArrayResponse(responseValue, responsePythonClassName, title) : formatPythonResponse(responseValue, responsePythonClassName); }; var formatPythonArrayResponse = (responseArray, responsePythonClassName, title) => { const formattedItems = responseArray.map((v) => { if (v == null) { throw new Error(`Null value in response array for '${title}'`); } return formatPythonResponse(v, responsePythonClassName); }).join(", "); return `[${formattedItems}]`; }; var formatPythonResponse = (responseParams, responsePythonClassName) => { const params = formatPythonArgs(responseParams); return `${responsePythonClassName}(${params})`; }; var createRubyRequest = ({ request }, _context) => { const parts = request.path.split("/"); const params = Object.entries(request.parameters).map(([key, value]) => `${changeCase.snakeCase(key)}: ${formatRubyValue(value)}`).join(", "); return `seam${parts.map((p) => changeCase.snakeCase(p)).join(".")}(${params})`; }; var formatRubyValue = (value) => value == null ? "nil" : JSON.stringify(value); var createRubyResponse = ({ response, title }, context) => { const { endpoint } = context; if (endpoint.response.responseType === "void") return "nil"; const { responseKey } = endpoint.response; const responseValue = response?.body?.[responseKey]; if (responseValue == null) { throw new Error(`Missing ${responseKey} for '${title}'`); } return Array.isArray(responseValue) ? formatRubyArrayResponse(responseValue, title) : formatRubyResponse(responseValue); }; var formatRubyArrayResponse = (responseArray, title) => { const formattedItems = responseArray.map((item) => { if (item == null) { throw new Error(`Null value in response array for '${title}'`); } return formatRubyResponse(item); }).join(",\n"); return `[${formattedItems}]`; }; var formatRubyResponse = (responseParams) => { const values = Object.entries(responseParams).map( ([paramKey, paramValue]) => `"${changeCase.snakeCase(paramKey)}" => ${formatRubyValue(paramValue)}` ); return `{${values.join(",")}}`; }; var createSeamCliRequest = ({ request }, _context) => { const parts = request.path.split("/"); const requestParams = Object.entries(request.parameters).map(([key, value]) => `--${key} ${JSON.stringify(value)}`).join(" "); return `seam${parts.map((p) => changeCase.kebabCase(p)).join(" ")} ${requestParams}`; }; var createSeamCliResponse = createJsonResponse; var createSeamCliResourceData = createJsonResourceData; var SdkNameSchema = zod.z.enum([ "javascript", "python", "php", "ruby", "seam_cli", "go", "java", "csharp", "curl" ]); var SyntaxNameSchema = zod.z.enum([ "javascript", "json", "python", "php", "ruby", "bash", "go", "java", "csharp" ]); // src/lib/samples/code-sample.ts var CodeSampleDefinitionSchema = zod.z.object({ title: zod.z.string().trim().min(1), description: zod.z.string().trim().min(1), request: zod.z.object({ path: zod.z.string().startsWith("/").regex( /^[a-z_/]+$/, "Can only contain the lowercase letters a-z, underscores, and forward slashes." ), parameters: zod.z.record(zod.z.string().min(1), JsonSchema).optional().default({}) }), response: zod.z.object({ body: zod.z.record(zod.z.string().min(1), JsonSchema).nullable() }) }); var CodeSchema = zod.z.record( SdkNameSchema, zod.z.object({ title: zod.z.string().min(1), sdkName: SdkNameSchema, request: zod.z.string(), response: zod.z.string(), request_syntax: SyntaxNameSchema, response_syntax: SyntaxNameSchema }) ); CodeSampleDefinitionSchema.extend({ code: CodeSchema }); var createCodeSample = async (codeSampleDefinition, context) => { const isVoidResponse = context.endpoint.response.responseType === "void"; const code = { javascript: { title: "JavaScript", sdkName: "javascript", request: createJavascriptRequest(codeSampleDefinition), response: isVoidResponse ? "// void" : createJavascriptResponse(codeSampleDefinition, context), request_syntax: "javascript", response_syntax: isVoidResponse ? "javascript" : "json" }, python: { title: "Python", sdkName: "python", request: createPythonRequest(codeSampleDefinition), response: createPythonResponse(codeSampleDefinition, context), request_syntax: "python", response_syntax: "python" }, ruby: { title: "Ruby", sdkName: "ruby", request: createRubyRequest(codeSampleDefinition), response: createRubyResponse(codeSampleDefinition, context), request_syntax: "ruby", response_syntax: "ruby" }, php: { title: "PHP", sdkName: "php", request: createPhpRequest(codeSampleDefinition), response: createPhpResponse(codeSampleDefinition, context), request_syntax: "php", response_syntax: "php" }, seam_cli: { title: "Seam CLI", sdkName: "seam_cli", request: createSeamCliRequest(codeSampleDefinition), response: createSeamCliResponse(codeSampleDefinition, context), request_syntax: "bash", response_syntax: "json" }, go: { title: "Go", sdkName: "go", request: createGoRequest(codeSampleDefinition), response: createGoResponse(codeSampleDefinition, context), request_syntax: "go", response_syntax: "go" }, java: { title: "Java", sdkName: "java", request: createJavaRequest(codeSampleDefinition), response: createJavaResponse(codeSampleDefinition, context), request_syntax: "java", response_syntax: "json" }, csharp: { title: "C#", sdkName: "csharp", request: createCsharpRequest(codeSampleDefinition), response: createCsharpResponse(codeSampleDefinition, context), request_syntax: "csharp", response_syntax: "json" }, curl: { title: "cURL", sdkName: "curl", request: createCurlRequest(codeSampleDefinition), response: createCurlResponse(codeSampleDefinition, context), request_syntax: "bash", response_syntax: "json" } }; return { ...codeSampleDefinition, code: await formatCodeRecords(code, context) }; }; var ResourceSampleDefinitionSchema = zod.z.object({ title: zod.z.string().trim().min(1), description: zod.z.string().trim().min(1), resource_type: zod.z.string().trim().min(1), properties: zod.z.record(zod.z.string().min(1), JsonSchema) }); var ResourceSchema2 = zod.z.record( SdkNameSchema, zod.z.object({ title: zod.z.string().min(1), resource_data: zod.z.string(), resource_data_syntax: SyntaxNameSchema }) ); ResourceSampleDefinitionSchema.extend({ resource: ResourceSchema2 }); var createResourceSample = async (resourceSampleDefinition, context) => { const resourceType = resourceSampleDefinition.resource_type; const schema = toPartialZodSchema(context.schemas[resourceType]); if (schema != null) { const { success, error } = schema.safeParse( resourceSampleDefinition.properties ); if (!success) { throw new Error( `Invalid properties for resource sample definition of type '${resourceType}': ${error.message}` ); } } else { console.warn(`Missing Zod schema for resource ${resourceType}.`); } const resource = { seam_cli: { title: "Seam CLI", resource_data: createSeamCliResourceData( resourceSampleDefinition), resource_data_syntax: "json" } }; return { ...resourceSampleDefinition, resource: await formatResourceRecords(resource, context) }; }; var toPartialZodSchema = (input) => { if (typeof input !== "object") return null; if (input == null) return null; if ("deepPartial" in input) return input; return null; }; // src/lib/seam.ts var mapOpenapiToSeamAuthMethod = (method) => { const AUTH_METHOD_MAPPING = { api_key: "api_key", pat_with_workspace: "personal_access_token", pat_without_workspace: "personal_access_token", console_session_token_with_workspace: "console_session_token", console_session_token_without_workspace: "console_session_token", client_session: "client_session_token", publishable_key: "publishable_key" }; return AUTH_METHOD_MAPPING[method]; }; // src/lib/blueprint.ts var paginationResponseKey = "pagination"; var TypesModuleSchema = zod.z.object({ codeSampleDefinitions: zod.z.array(CodeSampleDefinitionSchema).default([]), resourceSampleDefinitions: zod.z.array(ResourceSampleDefinitionSchema).default([]), // TODO: Import and use openapi zod schema here openapi: zod.z.any(), schemas: zod.z.record(zod.z.string(), zod.z.unknown()).optional().default({}) }); var createBlueprint = async (typesModule, { formatCode = async (content) => content } = {}) => { const { schemas, codeSampleDefinitions, resourceSampleDefinitions } = TypesModuleSchema.parse(typesModule); const openapi = typesModule.openapi; const validActionAttemptTypes = extractValidActionAttemptTypes( openapi.components.schemas ); const context = { codeSampleDefinitions, resourceSampleDefinitions, formatCode, schemas, validActionAttemptTypes }; const routes = await createRoutes(openapi.paths, context); const namespaces = createNamespaces(routes); const pagination = openapi.components.schemas[paginationResponseKey]; const openapiSchemas = Object.fromEntries( Object.entries(openapi.components.schemas).filter( ([k]) => k !== paginationResponseKey ) ); const resources = await createResources(openapiSchemas, routes, context); const actionAttempts = await createActionAttempts( openapi.components.schemas, routes, context ); return { title: openapi.info.title, routes, namespaces, resources, pagination: createPagination(pagination), events: await createEvents(openapiSchemas, routes, context), actionAttempts }; }; var extractValidActionAttemptTypes = (schemas) => { const actionAttemptSchema = schemas["action_attempt"]; if (actionAttemptSchema == null || typeof actionAttemptSchema !== "object" || !("oneOf" in actionAttemptSchema) || !Array.isArray(actionAttemptSchema.oneOf)) { return []; } const processedActionAttemptTypes = /* @__PURE__ */ new Set(); actionAttemptSchema.oneOf.forEach((schema) => { const actionType = schema.properties?.["action_type"]?.enum?.[0]; if (typeof actionType === "string") { processedActionAttemptTypes.add(actionType); } }); return Array.from(processedActionAttemptTypes); }; var createRoutes = async (paths, context) => { const routeMap = /* @__PURE__ */ new Map(); const pathEntries = Object.entries(paths); for (const [path, pathItem] of pathEntries) { const namespacePath = getNamespacePath(path, paths); const route = await createRoute(namespacePath, path, pathItem, context); const existingRoute = routeMap.get(route.path); if (existingRoute != null) { existingRoute.endpoints.push(...route.endpoints); continue; } routeMap.set(route.path, route); } const routes = Array.from(routeMap.values()); return routes.map(addIsDeprecatedToRoute).map(addIsUndocumentedToRoute).map(addIsDraftToRoute); }; var getNamespacePath = (path, paths) => { const namespace = []; const pathParts = path.split("/").filter((path2) => Boolean(path2)); const pathKeys = Object.keys(paths); for (const [index, part] of pathParts.entries()) { if (namespace.length !== index) { continue; } const endpoints = pathKeys.filter((key) => { return new RegExp(`^/${[...namespace, part].join("/")}/\\w+$`).test(key); }); if (endpoints.length === 0) { namespace.push(part); } } if (namespace.length === 0) { return null; } return `/${namespace.join("/")}`; }; var createRoute = async (namespacePath, path, pathItem, context) => { const pathParts = path.split("/"); const routePath = `/${pathParts.slice(1, -1).join("/")}`; const name = pathParts.at(-2); if (name == null) { throw new Error(`Could not resolve name for route at ${path}`); } const endpoint = await createEndpoint(path, pathItem, context); return { path: routePath, name, namespacePath, endpoints: endpoint != null ? [endpoint] : [], parentPath: getParentPath(routePath), isUndocumented: false, isDeprecated: false, isDraft: false }; }; var addIsDeprecatedToRoute = (route) => ({ ...route, isDeprecated: route.endpoints.every((endpoint) => endpoint.isDeprecated) }); var addIsUndocumentedToRoute = (route) => ({ ...route, isUndocumented: route.endpoints.every((endpoint) => endpoint.isUndocumented) }); var addIsDraftToRoute = (route) => ({ ...route, isDraft: route.endpoints.every((endpoint) => endpoint.isDraft) }); var createNamespaces = (routes) => { const namespacePaths = [ ...new Set( routes.flatMap((r) => r.namespacePath == null ? [] : [r.namespacePath]) ) ]; return namespacePaths.map((path) => { const namespaceRoutes = routes.filter((r) => r.namespacePath === path); const pathParts = path.split("/"); const name = pathParts.at(-1); if (name == null) { throw new Error(`Could not resolve name for route at ${path}`); } const isDeprecated = namespaceRoutes.every((r) => r.isDeprecated); const isUndocumented = namespaceRoutes.every((r) => r.isUndocumented); const isDraft = namespaceRoutes.every((r) => r.isDraft); return { name, path, parentPath: getParentPath(path), isDeprecated, isUndocumented, isDraft }; }); }; var createEndpoint = async (path, pathItem, context) => { const validMethods = ["GET", "POST", "PUT", "DELETE", "PATCH"]; const validOperations = Object.entries(pathItem).filter( ([method, operation2]) => validMethods.includes(method.toUpperCase()) && typeof operation2 === "object" && operation2 !== null ); const supportedMethods = validOperations.map( ([method]) => method.toUpperCase() ); const validOperation = validOperations.find(([m]) => m === "post"); if (validOperation == null) { console.warn(`No valid post operation found for ${path}`); return null; } const [_, operation] = validOperation; return await createEndpointFromOperation( supportedMethods, operation, path, context ); }; var createEndpointFromOperation = async (methods, operation, path, context) => { const pathParts = path.split("/"); const endpointPath = `/${pathParts.slice(1).join("/")}`; const name = pathParts.at(-1); if (name == null) { throw new Error(`Could not resolve name for endpoint at ${path}`); } const parsedOperation = OpenapiOperationSchema.parse(operation, { path: pathParts }); const title = parsedOperation["x-title"]; const description = normalizeDescription(parsedOperation.description); const isUndocumented = parsedOperation["x-undocumented"].length > 0; const undocumentedMessage = parsedOperation["x-undocumented"]; const isDeprecated = parsedOperation.deprecated; const deprecationMessage = parsedOperation["x-deprecated"]; const isDraft = parsedOperation["x-draft"].length > 0; const draftMessage = parsedOperation["x-draft"]; const request = createRequest(methods, operation, path); const { hasPagination, ...response } = createResponse( operation, path, context ); const operationAuthMethods = parsedOperation.security.map( (securitySchema) => { const [authMethod = ""] = Object.keys(securitySchema); return authMethod; } ); const endpointAuthMethods = operationAuthMethods.map(mapOpenapiToSeamAuthMethod).filter((authMethod) => authMethod != null); const workspaceScope = getWorkspaceScope(operationAuthMethods); const endpoint = { title, name, path: endpointPath, parentPath: getParentPath(endpointPath), description, isDeprecated, deprecationMessage, isUndocumented, undocumentedMessage, isDraft, draftMessage, response, request, hasPagination, authMethods: endpointAuthMethods, workspaceScope }; return { ...endpoint, codeSamples: await Promise.all( context.codeSampleDefinitions.filter(({ request: request2 }) => request2.path === endpointPath).map( async (codeSampleDefinition) => await createCodeSample(codeSampleDefinition, { endpoint, formatCode: context.formatCode }) ) ) }; }; var getWorkspaceScope = (authMethods) => { const hasWorkspaceUnscoped = authMethods.some( (method) => method.endsWith("_without_workspace") ); const workspaceScopedAuthMethods = [ "api_key", "client_session", "console_session_token_with_workspace", "pat_with_workspace", "publishable_key" ]; const hasWorkspaceScoped = authMethods.some( (method) => workspaceScopedAuthMethods.includes(method) ); const hasNoAuthMethods = !hasWorkspaceUnscoped && !hasWorkspaceScoped; if (hasNoAuthMethods) return "none"; const hasOnlyUnscopedAuth = hasWorkspaceUnscoped && !hasWorkspaceScoped; if (hasOnlyUnscopedAuth) return "none"; const hasBothScopedAndUnscoped = hasWorkspaceUnscoped && hasWorkspaceScoped; if (hasBothScopedAndUnscoped) return "optional"; const hasOnlyScopedAuth = !hasWorkspaceUnscoped && hasWorkspaceScoped; if (hasOnlyScopedAuth) return "required"; return "none"; }; var createRequest = (methods, operation, path) => { if (methods.length === 0) { console.warn("At least one HTTP method should be specified"); } if (methods.length > 2) { console.warn("More than two methods detected. Was this intended?"); } if (!methods.includes("POST")) { console.warn(`POST method is missing for ${path}`); } const semanticMethod = getSemanticMethod(methods); const preferredMethod = getPreferredMethod(methods, semanticMethod, operation); return { methods, semanticMethod, preferredMethod, parameters: createRequestBody(operation, path) }; }; var createRequestBody = (operation, path) => { if (!("requestBody" in operation) || operation.requestBody === void 0) { return []; } const requestBody = operation.requestBody; const jsonSchema = requestBody.content?.["application/json"]?.schema; if (jsonSchema == null) return []; const flattenedSchema = flattenOpenapiSchema(jsonSchema); if (flattenedSchema.type !== "object" || flattenedSchema.properties == null) { return []; } return createParameters( flattenedSchema.properties, path, flattenedSchema.required ); }; var createParameters = (properties, path, requiredParameters = []) => { return Object.entries(properties).map(([name, property]) => { if (property.type === "array" && "items" in property && "discriminator" in property.items) { return [name, property]; } return [name, flattenOpenapiSchema(property)]; }).filter(([name, property]) => { if (property.type == null) { console.warn( `The ${name} property for ${path} will not be documented since it does not define a type.` ); return false; } return true; }).map( ([name, property]) => createParameter(name, property, path, requiredParameters) ); }; var createParameter = (name, property, path, requiredParameters) => { const parsedProp = PropertySchema.parse(property, { path: [...path.split("/"), name] }); const baseParam = { name, description: normalizeDescription(String(parsedProp.description ?? "")), isRequired: requiredParameters.includes(name), isDeprecated: parsedProp["x-deprecated"].length > 0, deprecationMessage: parsedProp["x-deprecated"], isUndocumented: parsedProp["x-undocumented"].length > 0, undocumentedMessage: parsedProp["x-undocumented"], isDraft: parsedProp["x-draft"].length > 0, draftMessage: parsedProp["x-draft"], hasDefault: "default" in parsedProp }; if (baseParam.hasDefault) { baseParam.default = parsedProp.default; } switch (parsedProp.type) { case "string": if (parsedProp.enum !== void 0) { return { ...baseParam, format: "enum", jsonType: "string", values: parsedProp.enum.map((value) => { const enumValue = parsedProp["x-enums"]?.[String(value)]; if (parsedProp["x-enums"] != null && enumValue == null) { throw new Error( `Missing enum value definition in x-enums for "${String(value)}"` ); } return { name: String(value),