diff --git a/www/utils/packages/docs-generator/src/classes/helpers/knowledge-base-factory.ts b/www/utils/packages/docs-generator/src/classes/helpers/knowledge-base-factory.ts index a30fde0e6f..ccc537cc3c 100644 --- a/www/utils/packages/docs-generator/src/classes/helpers/knowledge-base-factory.ts +++ b/www/utils/packages/docs-generator/src/classes/helpers/knowledge-base-factory.ts @@ -546,6 +546,78 @@ class KnowledgeBaseFactory { return `Whether the ${singular(options.parentName)} was deleted.` }, }, + { + exact: "$eq", + template: `Filter by an exact match.`, + }, + { + exact: "$ne", + template: `Filter by values not equal to this parameter.`, + }, + { + exact: "$in", + template: `Filter by values in this array.`, + }, + { + exact: "$nin", + template: `Filter by values not in this array.`, + }, + { + exact: "$not", + template: `Filter by values not matching the conditions in this parameter.`, + }, + { + exact: "$gt", + template: `Filter by values greater than this parameter. Useful for numbers and dates only.`, + }, + { + exact: "$gte", + template: `Filter by values greater than or equal to this parameter. Useful for numbers and dates only.`, + }, + { + exact: "$lt", + template: `Filter by values less than this parameter. Useful for numbers and dates only.`, + }, + { + exact: "$lte", + template: `Filter by values less than or equal to this parameter. Useful for numbers and dates only.`, + }, + { + exact: "$like", + template: "Apply a `like` filter. Useful for strings only.", + }, + { + exact: "$re", + template: "Apply a regex filter. Useful for strings only.", + }, + { + exact: "$ilike", + template: + "Apply a case-insensitive `like` filter. Useful for strings only.", + }, + { + exact: "$fulltext", + template: "Filter to apply on full-text properties.", + }, + { + exact: "$overlap", + template: + "Filter arrays that have overlapping values with this parameter.", + }, + { + exact: "$contains", + template: + "Filter arrays that contain some of the values of this parameter.", + }, + { + exact: "$contained", + template: "Filter arrays that contain all values of this parameter.", + }, + { + exact: "$exists", + template: + "Filter by whether a value for this parameter exists (not `null`).", + }, { pattern: /.*/, template(str, options) { diff --git a/www/utils/packages/docs-generator/src/classes/helpers/schema-factory.ts b/www/utils/packages/docs-generator/src/classes/helpers/schema-factory.ts index c15155e4ac..c84adf2a3e 100644 --- a/www/utils/packages/docs-generator/src/classes/helpers/schema-factory.ts +++ b/www/utils/packages/docs-generator/src/classes/helpers/schema-factory.ts @@ -9,24 +9,6 @@ class SchemaFactory { * The pre-defined schemas. */ private schemas: Record = { - BigNumberInput: { - type: "string", - }, - BigNumber: { - type: "string", - }, - created_at: { - type: "string", - format: "date-time", - }, - updated_at: { - type: "string", - format: "date-time", - }, - deleted_at: { - type: "string", - format: "date-time", - }, $and: { type: "array", description: @@ -43,6 +25,29 @@ class SchemaFactory { type: "object", }, }, + BigNumberInput: { + type: "string", + }, + BigNumber: { + type: "string", + }, + } + /** + * Schemas used only for response types. + */ + private schemasForResponse: Record = { + created_at: { + type: "string", + format: "date-time", + }, + updated_at: { + type: "string", + format: "date-time", + }, + deleted_at: { + type: "string", + format: "date-time", + }, } /** @@ -54,13 +59,21 @@ class SchemaFactory { */ public tryGetSchema( name: string, - additionalData?: Partial + additionalData?: Partial, + type: "request" | "response" | "all" = "all" ): OpenApiSchema | undefined { - if (!Object.hasOwn(this.schemas, name)) { + const schemasFactory = + type === "response" + ? this.mergeSchemas(this.schemasForResponse, this.schemas) + : this.cloneSchema(this.schemas) + const key = Object.hasOwn(schemasFactory, name) + ? name + : additionalData?.title || "" + if (!Object.hasOwn(schemasFactory, key)) { return } - let schema = Object.assign({}, this.schemas[name]) + let schema = Object.assign({}, schemasFactory[key]) if (additionalData) { schema = Object.assign(schema, additionalData) @@ -68,6 +81,19 @@ class SchemaFactory { return schema } + + private mergeSchemas( + main: Record, + other: Record + ): Record { + return Object.assign(this.cloneSchema(main), this.cloneSchema(other)) + } + + private cloneSchema( + schema: Record + ): Record { + return JSON.parse(JSON.stringify(schema)) + } } export default SchemaFactory diff --git a/www/utils/packages/docs-generator/src/classes/kinds/oas.ts b/www/utils/packages/docs-generator/src/classes/kinds/oas.ts index 86caccc806..706dede7ae 100644 --- a/www/utils/packages/docs-generator/src/classes/kinds/oas.ts +++ b/www/utils/packages/docs-generator/src/classes/kinds/oas.ts @@ -978,6 +978,7 @@ class OasKindGenerator extends FunctionKindGenerator { itemType: propertyType, title: property.getName(), descriptionOptions, + context: "request", }), }) ) @@ -999,6 +1000,7 @@ class OasKindGenerator extends FunctionKindGenerator { rawParentName: this.checker.typeToString(requestTypeArguments[0]), }, zodObjectTypeName: zodObjectTypeName, + context: "request", }) // If function is a GET function, add the type parameter to the @@ -1105,6 +1107,7 @@ class OasKindGenerator extends FunctionKindGenerator { typeReferenceNode: node.parameters[1].type, itemType: responseTypeArguments[0], }), + context: "response", }) } } @@ -1126,6 +1129,7 @@ class OasKindGenerator extends FunctionKindGenerator { allowedChildren, disallowedChildren, zodObjectTypeName, + ...rest }: { /** * The TypeScript type. @@ -1159,6 +1163,10 @@ class OasKindGenerator extends FunctionKindGenerator { * generated type name. */ zodObjectTypeName?: string + /** + * Whether the type is in a request / response + */ + context?: "request" | "response" }): OpenApiSchema { if (level > this.MAX_LEVEL) { return {} @@ -1183,7 +1191,8 @@ class OasKindGenerator extends FunctionKindGenerator { { title: title || typeAsString, description, - } + }, + rest.context ) if (schemaFromFactory) { @@ -1204,16 +1213,19 @@ class OasKindGenerator extends FunctionKindGenerator { }) return { type: "string", + description, enum: enumMembers, } - case itemType.isLiteral(): + case itemType.isLiteral() || typeAsString === "RegExp": + const isString = + itemType.flags === ts.TypeFlags.StringLiteral || + typeAsString === "RegExp" return { - type: - itemType.flags === ts.TypeFlags.StringLiteral - ? "string" - : itemType.flags === ts.TypeFlags.NumberLiteral - ? "number" - : "boolean", + type: isString + ? "string" + : itemType.flags === ts.TypeFlags.NumberLiteral + ? "number" + : "boolean", title: title || typeAsString, description, format: this.getSchemaTypeFormat({ @@ -1268,6 +1280,7 @@ class OasKindGenerator extends FunctionKindGenerator { parentName: title || descriptionOptions?.parentName, } : undefined, + ...rest, }), } case itemType.isUnion(): @@ -1279,39 +1292,57 @@ class OasKindGenerator extends FunctionKindGenerator { if (allLiteral) { return { type: "string", + description, enum: (itemType as ts.UnionType).types.map( (unionType) => (unionType as ts.LiteralType).value ), } } + + const oneOfItems = this.removeStringRegExpTypeOverlaps( + (itemType as ts.UnionType).types + ).map((unionType) => + this.typeToSchema({ + itemType: unionType, + // not incrementing considering the + // current schema isn't actually a + // schema + level, + title, + descriptionOptions, + ...rest, + }) + ) + + if (oneOfItems.length === 1) { + return oneOfItems[0] + } + return { - oneOf: (itemType as ts.UnionType).types.map((unionType) => - this.typeToSchema({ - itemType: unionType, - // not incrementing considering the - // current schema isn't actually a - // schema - level, - title, - descriptionOptions, - }) - ), + oneOf: oneOfItems, } case itemType.isIntersection(): + const allOfItems = this.removeStringRegExpTypeOverlaps( + (itemType as ts.IntersectionType).types + ).map((intersectionType) => { + return this.typeToSchema({ + itemType: intersectionType, + // not incrementing considering the + // current schema isn't actually a + // schema + level, + title, + descriptionOptions, + ...rest, + }) + }) + + if (allOfItems.length === 1) { + return allOfItems[0] + } + return { - allOf: (itemType as ts.IntersectionType).types.map( - (intersectionType) => { - return this.typeToSchema({ - itemType: intersectionType, - // not incrementing considering the - // current schema isn't actually a - // schema - level, - title, - descriptionOptions, - }) - } - ), + allOf: allOfItems, } case typeAsString.startsWith("Pick"): const pickTypeArgs = @@ -1332,6 +1363,7 @@ class OasKindGenerator extends FunctionKindGenerator { level, descriptionOptions, allowedChildren: pickedProperties, + ...rest, }) case typeAsString.startsWith("Omit"): const omitTypeArgs = @@ -1352,6 +1384,7 @@ class OasKindGenerator extends FunctionKindGenerator { level, descriptionOptions, disallowedChildren: omitProperties, + ...rest, }) case typeAsString.startsWith("Partial"): const typeArg = @@ -1368,6 +1401,7 @@ class OasKindGenerator extends FunctionKindGenerator { descriptionOptions, disallowedChildren, allowedChildren, + ...rest, }) // remove all required items @@ -1405,6 +1439,7 @@ class OasKindGenerator extends FunctionKindGenerator { typeStr: property.name, parentName: title || descriptionOptions?.parentName, }, + ...rest, }) if (isDeleteResponse && property.name === "object") { @@ -2034,6 +2069,23 @@ class OasKindGenerator extends FunctionKindGenerator { fnText.includes(indicator) ) } + + private removeStringRegExpTypeOverlaps(types: ts.Type[]): ts.Type[] { + return types.filter((itemType) => { + // remove overlapping string / regexp types + if (this.checker.typeToString(itemType) === "RegExp") { + const hasString = types.some((t) => { + return ( + t.flags === ts.TypeFlags.String || + t.flags === ts.TypeFlags.StringLiteral + ) + }) + return !hasString + } + + return true + }) + } } export default OasKindGenerator