@seamapi/blueprint
Version:
Build tools for the Seam API using this blueprint.
1,426 lines (1,414 loc) • 78.9 kB
JavaScript
'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",
"client_session_with_customer",
"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-batch-keys": zod.z.array(zod.z.string()).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) => 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 {
if (!["event", "action_attempt"].includes(resourceType)) {
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",
client_session_with_customer: "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 validResourceTypes = extractValidResourceTypes(
openapi.components.schemas
);
const context = {
codeSampleDefinitions,
resourceSampleDefinitions,
formatCode,
schemas,
validActionAttemptTypes,
validResourceTypes
};
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
);
const events = await createEvents(openapiSchemas, routes, context);
assertSeamPathsAreUndocumented({
routes,
namespaces,
resources,
events,
actionAttempts
});
assertDocumentedEndpointsDontReferenceUndocumentedResources({
routes,
resources
});
return {
title: openapi.info.title,
routes,
namespaces,
resources,
pagination: createPagination(pagination, openapiSchemas),
events,
actionAttempts
};
};
var isSeamPath = (path) => path === "/seam" || path.startsWith("/seam/");
var assertSeamPathsAreUndocumented = ({
routes,
namespaces,
resources,
events,
actionAttempts
}) => {
const offenders = [
...routes.flatMap((route) => {
const routeOffenders = isSeamPath(route.path) && !route.isUndocumented ? [`route ${route.path}`] : [];
const endpointOffenders = route.endpoints.flatMap(
(endpoint) => isSeamPath(endpoint.path) && !endpoint.isUndocumented ? [`endpoint ${endpoint.path}`] : []
);
return [...routeOffenders, ...endpointOffenders];
}),
...namespaces.flatMap(
(namespace) => isSeamPath(namespace.path) && !namespace.isUndocumented ? [`namespace ${namespace.path}`] : []
),
...resources.flatMap(
(resource) => isSeamPath(resource.routePath) && !resource.isUndocumented ? [`resource ${resource.routePath}`] : []
),
...events.flatMap(
(event) => isSeamPath(event.routePath) && !event.isUndocumented ? [`event ${event.routePath}`] : []
),
...actionAttempts.flatMap(
(actionAttempt) => isSeamPath(actionAttempt.routePath) && !actionAttempt.isUndocumented ? [`action_attempt ${actionAttempt.routePath}`] : []
)
];
if (offenders.length > 0) {
throw new Error(
`All /seam entries must be marked undocumented. Found: ${offenders.join(", ")}`
);
}
};
var assertDocumentedEndpointsDontReferenceUndocumentedResources = ({
routes,
resources
}) => {
const undocumentedResourceTypes = new Set(
resources.filter((r) => r.isUndocumented).map((r) => r.resourceType)
);
const offenders = [];
for (const route of routes) {
for (const endpoint of route.endpoints) {
if (endpoint.isUndocumented) continue;
if (endpoint.response.responseType === "void") continue;
if (!("resourceType" in endpoint.response)) continue;
const { resourceType } = endpoint.response;
if (undocumentedResourceTypes.has(resourceType)) {
offenders.push(
`${endpoint.path} references undocumented resource '${resourceType}'`
);
}
}
}
if (offenders.length > 0) {
throw new Error(
`Documented endpoints must not reference undocumented resources. Found:
${offenders.join("\n")}`
);
}
};
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 extractValidResourceTypes = (schemas) => {
return Object.keys(schemas);
};
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) => 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) {
throw new Error(`No valid post operation found for ${path}`);
}
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 deprecationMessage = parsedOperation["x-deprecated"];
const isDeprecated = deprecationMessage.length > 0;
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 = [
...new Set(
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",
"client_session_with_customer",
"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 for ${path}`);
}
if (methods.