diff --git a/docs-util/packages/typedoc-config/_base.js b/docs-util/packages/typedoc-config/_base.js index ee874baf83..aafaa94e69 100644 --- a/docs-util/packages/typedoc-config/_base.js +++ b/docs-util/packages/typedoc-config/_base.js @@ -14,6 +14,7 @@ module.exports = { ), pluginsResolvePath: path.join(pathPrefix, "www"), exclude: [path.join(pathPrefix, "node_modules/**")], + excludeInternal: true, // Uncomment this when debugging // showConfig: true, } diff --git a/docs-util/packages/typedoc-config/extended-tsconfig/tsdoc.json b/docs-util/packages/typedoc-config/extended-tsconfig/tsdoc.json index 06df94198f..e7fc481823 100644 --- a/docs-util/packages/typedoc-config/extended-tsconfig/tsdoc.json +++ b/docs-util/packages/typedoc-config/extended-tsconfig/tsdoc.json @@ -17,6 +17,14 @@ { "tagName": "@apiIgnore", "syntaxKind": "modifier" + }, + { + "tagName": "@mainSignature", + "syntaxKind": "modifier" + }, + { + "tagName": "@docHideSignature", + "syntaxKind": "modifier" } ] } \ No newline at end of file diff --git a/docs-util/packages/typedoc-config/extended-tsconfig/workflows.json b/docs-util/packages/typedoc-config/extended-tsconfig/workflows.json new file mode 100644 index 0000000000..35d3b4171b --- /dev/null +++ b/docs-util/packages/typedoc-config/extended-tsconfig/workflows.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": [ + "../../../../packages/workflows/tsconfig.json" + ] +} \ No newline at end of file diff --git a/docs-util/packages/typedoc-config/workflows.js b/docs-util/packages/typedoc-config/workflows.js new file mode 100644 index 0000000000..199ea0b5db --- /dev/null +++ b/docs-util/packages/typedoc-config/workflows.js @@ -0,0 +1,75 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const path = require("path") +const globalTypedocOptions = require("./_base") + +const pathPrefix = path.join(__dirname, "..", "..", "..") + +module.exports = { + ...globalTypedocOptions, + entryPoints: [ + path.join(pathPrefix, "packages/workflows/src/utils/composer/index.ts"), + ], + out: [path.join(pathPrefix, "www/apps/docs/content/references/workflows")], + tsconfig: path.join(__dirname, "extended-tsconfig", "workflows.json"), + name: "Workflows Reference", + indexTitle: "Workflows Reference", + entryDocument: "index.mdx", + hideInPageTOC: true, + hideBreadcrumbs: true, + formatting: { + "*": { + expandMembers: true, + showCommentsAsHeader: true, + sections: { + member_sources_definedIn: false, + reflection_hierarchy: false, + member_sources_inheritedFrom: false, + member_sources_implementationOf: false, + reflection_implementedBy: false, + member_signature_sources: false, + reflection_callable: false, + reflection_indexable: false, + member_signature_title: false, + member_signature_returns: false, + member_getterSetter: false, + }, + parameterStyle: "component", + parameterComponent: "ParameterTypes", + mdxImports: [ + `import ParameterTypes from "@site/src/components/ParameterTypes"`, + ], + frontmatterData: { + displayed_sidebar: "workflowsSidebar", + }, + }, + "index\\.mdx": { + reflectionGroups: { + Namespaces: false, + Enumerations: false, + Classes: false, + Interfaces: false, + "Type Aliases": false, + Variables: false, + "Enumeration Members": false, + }, + }, + functions: { + maxLevel: 1, + }, + "classes/StepResponse": { + reflectionGroups: { + Properties: false, + }, + }, + transform: { + reflectionGroups: { + "Type Parameters": false, + }, + }, + }, + objectLiteralTypeDeclarationStyle: "component", + mdxOutput: true, + maxLevel: 2, + allReflectionsHaveOwnDocument: true, + excludeExternals: true, +} diff --git a/docs-util/packages/typedoc-plugin-custom/src/index.ts b/docs-util/packages/typedoc-plugin-custom/src/index.ts index 7c4bf33481..8525ebdd94 100644 --- a/docs-util/packages/typedoc-plugin-custom/src/index.ts +++ b/docs-util/packages/typedoc-plugin-custom/src/index.ts @@ -4,6 +4,7 @@ import { load as frontmatterPlugin } from "./frontmatter-plugin" import { load as parseOasSchemaPlugin } from "./parse-oas-schema-plugin" import { load as apiIgnorePlugin } from "./api-ignore" import { load as eslintExamplePlugin } from "./eslint-example" +import { load as signatureModifierPlugin } from "./signature-modifier" export function load(app: Application) { resolveReferencesPluginLoad(app) @@ -11,4 +12,5 @@ export function load(app: Application) { parseOasSchemaPlugin(app) apiIgnorePlugin(app) eslintExamplePlugin(app) + signatureModifierPlugin(app) } diff --git a/docs-util/packages/typedoc-plugin-custom/src/signature-modifier.ts b/docs-util/packages/typedoc-plugin-custom/src/signature-modifier.ts new file mode 100644 index 0000000000..e335ad47e9 --- /dev/null +++ b/docs-util/packages/typedoc-plugin-custom/src/signature-modifier.ts @@ -0,0 +1,21 @@ +import { + Application, + Context, + Converter, + ProjectReflection, + SignatureReflection, +} from "typedoc" + +export function load(app: Application) { + app.converter.on( + Converter.EVENT_CREATE_SIGNATURE, + ( + context: Context, + signature: SignatureReflection | ProjectReflection | undefined + ) => { + if (signature?.comment?.hasModifier("@hideSignature")) { + context.project.removeReflection(signature) + } + } + ) +} diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/returns.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/returns.ts index 2c3845fa44..557b105917 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/returns.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/returns.ts @@ -31,13 +31,13 @@ function getReturnFromType( return "" } - const componentItems = returnReflectionComponentFormatter( - reflection.type, - reflection.project || theme.project, - reflection.comment, - 1, - maxLevel - ) + const componentItems = returnReflectionComponentFormatter({ + reflectionType: reflection.type, + project: reflection.project || theme.project, + comment: reflection.comment, + level: 1, + maxLevel, + }) if (parameterStyle === "component") { return `<${parameterComponent} parameters={${JSON.stringify( diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/signature-title.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/signature-title.ts index 3793ff45e1..0bfebe6f1e 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/signature-title.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/helpers/signature-title.ts @@ -15,7 +15,7 @@ export default function (theme: MarkdownTheme) { theme.getFormattingOptionsForLocation() if (sections && sections.member_signature_title === false) { // only show title if there are more than one signatures - if (!this.parent.signatures || this.parent.signatures?.length <= 1) { + if (!this.parent.signatures || this.parent.signatures.length <= 1) { return "" } } diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.declaration.hbs b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.declaration.hbs index cf79ca1071..2a339b5737 100755 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.declaration.hbs +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.declaration.hbs @@ -12,7 +12,7 @@ {{#if (sectionEnabled "member_declaration_example")}} -{{{example this 3}}} +{{{example this 4}}} {{/if}} @@ -20,7 +20,7 @@ {{#if typeParameters}} -{{titleLevel 3}} Type parameters +{{{titleLevel 4}}} Type Parameters {{#with typeParameters}} @@ -40,7 +40,7 @@ {{#with type.declaration.indexSignature}} -{{titleLevel 3}} Index signature +{{titleLevel 4}} Index signature {{{indexSignatureTitle}}} @@ -58,17 +58,17 @@ {{#if type.declaration.children}} -{{titleLevel 3}} Call signature +{{{titleLevel 4}}} Call signature {{else}} -{{titleLevel 3}} Type declaration +{{{titleLevel 4}}} Type declaration {{/if}} {{#each type.declaration.signatures}} -{{> member.signature showSources=false }} +{{> member.signature showSources=false commentLevel=5 }} {{/each}} @@ -82,7 +82,7 @@ {{#with type.declaration}} -{{titleLevel 3}} Type declaration +{{{titleLevel 4}}} Type declaration {{/with}} diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.getterSetter.hbs b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.getterSetter.hbs index 0657dc1fbd..746961b44c 100755 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.getterSetter.hbs +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.getterSetter.hbs @@ -4,7 +4,7 @@ {{#with getSignature}} -{{> member.signature accessor="get" showSources=true }} +{{> member.signature accessor="get" showSources=true commentLevel=4 }} {{/with}} @@ -18,7 +18,7 @@ {{#with setSignature}} -{{> member.signature accessor="set" showSources=true }} +{{> member.signature accessor="set" showSources=true commentLevel=4 }} {{/with}} diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.hbs b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.hbs index 5062e71e6c..91c0a4f0dd 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.hbs +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.hbs @@ -2,7 +2,7 @@ {{#if name}} -{{titleLevel 3}} {{#ifNamedAnchors}} {{/ifNamedAnchors}}{{ escape name }} +{{titleLevel 4}} {{#ifNamedAnchors}} {{/ifNamedAnchors}}{{ escape name }} {{/if}} @@ -14,7 +14,7 @@ {{#each signatures}} -{{> member.signature showSources=true }} +{{> member.signature showSources=true commentLevel=../commentLevel }} {{/each}} diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.signature.hbs b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.signature.hbs index b8d21bba97..7a50c41def 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.signature.hbs +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/member.signature.hbs @@ -14,10 +14,18 @@ {{/if}} +{{#if (sectionEnabled "member_signature_example")}} + +{{{example this commentLevel}}} + +{{/if}} + {{#if (sectionEnabled "member_signature_typeParameters")}} {{#if typeParameters}} +{{{titleLevel commentLevel}}} Type Parameters + {{#with typeParameters}} {{{typeParameter}}} @@ -28,25 +36,11 @@ {{/if}} -{{#if (sectionEnabled "member_signature_example")}} - -{{{example this 4}}} - -{{/if}} - {{#if (sectionEnabled "member_signature_parameters")}} {{#if parameters}} -{{#if showSources}} - -{{{titleLevel 4}}} Parameters - -{{else}} - -{{{titleLevel 5}}} Parameters - -{{/if}} +{{{titleLevel commentLevel}}} Parameters {{#with parameters}} @@ -62,15 +56,7 @@ {{#if type}} -{{#if showSources}} - -{{{titleLevel 4}}} Returns - -{{else}} - -{{{titleLevel 5}}} Returns - -{{/if}} +{{{titleLevel commentLevel}}} Returns {{#if (sectionEnabled "member_signature_returns")}} @@ -92,7 +78,7 @@ {{#each declaration.signatures}} -{{> member.signature showSources=false }} +{{> member.signature showSources=false commentLevel=commentLevel }} {{/each}} @@ -126,7 +112,7 @@ {{#if hasVisibleComponent}} -{{{comments this false true 4 ..}}} +{{{comments this false true commentLevel ..}}} {{/if}} diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/members.group.hbs b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/members.group.hbs index 89b24733a9..b32fb78432 100755 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/members.group.hbs +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/members.group.hbs @@ -26,7 +26,7 @@ ___ {{#each children}} -{{> member}} +{{> member commentLevel=5}} {{/each}} @@ -54,7 +54,7 @@ ___ {{#each children}} -{{> member}} +{{> member commentLevel=5}} {{/each}} diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/members.hbs b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/members.hbs index 08f7e8134a..c00601347a 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/members.hbs +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/members.hbs @@ -16,7 +16,7 @@ {{#unless hasOwnDocument}} -{{> member}} +{{> member commentLevel=4}} {{/unless}} diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/section-title.hbs b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/section-title.hbs new file mode 100644 index 0000000000..e863067f41 --- /dev/null +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/partials/section-title.hbs @@ -0,0 +1,9 @@ +{{#if showSources}} + +{{{titleLevel 4}}} {{{title}}} + +{{else}} + +{{{titleLevel 5}}} {{{title}}} + +{{/if}} \ No newline at end of file diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/templates/reflection.hbs b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/templates/reflection.hbs index 7ae033c574..6c63ea1f9c 100755 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/templates/reflection.hbs +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/templates/reflection.hbs @@ -94,7 +94,7 @@ ### {{name}} -{{> member.signature showSources=true }} +{{> member.signature showSources=true commentLevel=4 }} {{/each}} diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/templates/reflection.member.hbs b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/templates/reflection.member.hbs index 78129be63b..8b2a6554b2 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/templates/reflection.member.hbs +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/resources/templates/reflection.member.hbs @@ -4,6 +4,6 @@ {{#with model}} -{{> member}} +{{> member showSources=false commentLevel=4}} {{/with}} diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/theme.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/theme.ts index 6a501e56e4..1b739b1efb 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/theme.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/theme.ts @@ -5,7 +5,6 @@ import { PageEvent, ProjectReflection, Reflection, - ReflectionGroup, ReflectionKind, RenderTemplate, Renderer, @@ -359,22 +358,45 @@ export class MarkdownTheme extends Theme { this.location = page.url this.reflection = page.model instanceof DeclarationReflection ? page.model : undefined - const options = this.getFormattingOptionsForLocation() - if (this.reflection && this.reflection.groups) { - // filter out unwanted groups - const tempGroups: ReflectionGroup[] = [] - this.reflection.groups.forEach((reflectionGroup) => { - if ( - !options.reflectionGroups || - !(reflectionGroup.title in options.reflectionGroups) || - options.reflectionGroups[reflectionGroup.title] - ) { - tempGroups.push(reflectionGroup) - } - }) - this.reflection.groups = tempGroups + if ( + page.model instanceof DeclarationReflection || + page.model instanceof ProjectReflection + ) { + this.removeGroups(page.model) } + + if ( + this.reflection instanceof DeclarationReflection && + this.reflection.signatures + ) { + // check if any of its signature has the `@mainSignature` tag + // and if so remove other signatures + const mainSignatureIndex = this.reflection.signatures.findIndex( + (signature) => signature.comment?.hasModifier("@mainSignature") + ) + + if (mainSignatureIndex !== -1) { + const mainSignature = this.reflection.signatures[mainSignatureIndex] + this.reflection.signatures = [mainSignature] + } + } + } + + protected removeGroups(model?: DeclarationReflection | ProjectReflection) { + if (!model?.groups) { + return + } + + const options = this.getFormattingOptionsForLocation() + + model.groups = model.groups.filter((reflectionGroup) => { + return ( + !options.reflectionGroups || + !(reflectionGroup.title in options.reflectionGroups) || + options.reflectionGroups[reflectionGroup.title] + ) + }) } get globalsFile() { diff --git a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/return-reflection-formatter.ts b/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/return-reflection-formatter.ts index 1155fe2852..eb930e1cdf 100644 --- a/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/return-reflection-formatter.ts +++ b/docs-util/packages/typedoc-plugin-markdown-medusa/src/utils/return-reflection-formatter.ts @@ -4,6 +4,7 @@ import { ProjectReflection, ReflectionFlags, SomeType, + TypeParameterReflection, } from "typedoc" import * as Handlebars from "handlebars" import getType from "./type-utils" @@ -14,19 +15,27 @@ import { } from "./reflection-formatter" import { MarkdownTheme } from "../theme" -export function returnReflectionComponentFormatter( - reflectionType: SomeType, - project: ProjectReflection, - comment?: Comment, - level = 1, +type ReturnReflectionComponentFormatterParams = { + reflectionType: SomeType + project: ProjectReflection + comment?: Comment + level: number maxLevel?: number | undefined -): Parameter[] { +} + +export function returnReflectionComponentFormatter({ + reflectionType, + project, + comment, + level = 1, + maxLevel, +}: ReturnReflectionComponentFormatterParams): Parameter[] { const typeName = getType(reflectionType, "object", false, true) const type = getType(reflectionType, "object") const componentItem: Parameter[] = [] + const canRetrieveChildren = level + 1 <= (maxLevel || MarkdownTheme.MAX_LEVEL) if (reflectionType.type === "reference") { - // put type name as a title and its referenced items as children. - if (reflectionType.typeArguments) { + if (reflectionType.typeArguments || reflectionType.refersToTypeParameter) { const parentKey = componentItem.push({ name: "name" in reflectionType ? reflectionType.name : typeName, type, @@ -45,21 +54,30 @@ export function returnReflectionComponentFormatter( featureFlag: Handlebars.helpers.featureFlag(comment), children: [], }) + const typeArgs = reflectionType.typeArguments + ? reflectionType.typeArguments + : "typeParameters" in reflectionType + ? (reflectionType.typeParameters as TypeParameterReflection[]) + : undefined if ( - !isOnlyVoid(reflectionType.typeArguments) && - level + 1 <= (maxLevel || MarkdownTheme.MAX_LEVEL) + typeArgs && + !isOnlyVoid(typeArgs as unknown as SomeType[]) && + canRetrieveChildren ) { - reflectionType.typeArguments.forEach((typeArg) => { - const typeArgComponent = returnReflectionComponentFormatter( - typeArg, - project, - undefined, - level + 1, - maxLevel - ) - if (typeArgComponent.length) { - componentItem[parentKey - 1].children?.push(...typeArgComponent) + typeArgs.forEach((typeArg) => { + const reflectionTypeArg = + typeArg instanceof TypeParameterReflection ? typeArg.type : typeArg + if (!reflectionTypeArg) { + return } + const typeArgComponent = returnReflectionComponentFormatter({ + reflectionType: reflectionTypeArg, + project, + level: level + 1, + maxLevel, + }) + + componentItem[parentKey - 1].children?.push(...typeArgComponent) }) } } else { @@ -107,17 +125,14 @@ export function returnReflectionComponentFormatter( featureFlag: Handlebars.helpers.featureFlag(comment), children: [], }) - if (level + 1 <= (maxLevel || MarkdownTheme.MAX_LEVEL)) { - const elementTypeItem = returnReflectionComponentFormatter( - reflectionType.elementType, + if (canRetrieveChildren) { + const elementTypeItem = returnReflectionComponentFormatter({ + reflectionType: reflectionType.elementType, project, - undefined, - level + 1, - maxLevel - ) - if (elementTypeItem.length) { - componentItem[parentKey - 1].children?.push(...elementTypeItem) - } + level: level + 1, + maxLevel, + }) + componentItem[parentKey - 1].children?.push(...elementTypeItem) } } else if (reflectionType.type === "tuple") { let pushTo: Parameter[] = [] @@ -145,18 +160,15 @@ export function returnReflectionComponentFormatter( } else { pushTo = componentItem } - if (level + 1 <= (maxLevel || MarkdownTheme.MAX_LEVEL)) { + if (canRetrieveChildren) { reflectionType.elements.forEach((element) => { - const elementTypeItem = returnReflectionComponentFormatter( - element, + const elementTypeItem = returnReflectionComponentFormatter({ + reflectionType: element, project, - undefined, - level + 1, - maxLevel - ) - if (elementTypeItem.length) { - pushTo.push(...elementTypeItem) - } + level: level + 1, + maxLevel, + }) + pushTo.push(...elementTypeItem) }) } } else { diff --git a/docs-util/packages/utils/src/get-type-children.ts b/docs-util/packages/utils/src/get-type-children.ts index bfe4b143f7..e3e79dcb25 100644 --- a/docs-util/packages/utils/src/get-type-children.ts +++ b/docs-util/packages/utils/src/get-type-children.ts @@ -65,7 +65,15 @@ export function getTypeChildren( children = getTypeChildren(reflectionType.elementType, project) } - return children + return filterChildren(children) +} + +const REJECTED_CHILDREN_NAMES = ["__type"] + +function filterChildren(children: DeclarationReflection[]) { + return children.filter( + (child) => !REJECTED_CHILDREN_NAMES.includes(child.name) + ) } function removeChild(name: unknown, children: DeclarationReflection[]) { diff --git a/packages/workflows/src/utils/composer/create-step.ts b/packages/workflows/src/utils/composer/create-step.ts index 7e8b0df926..e647ab60d4 100644 --- a/packages/workflows/src/utils/composer/create-step.ts +++ b/packages/workflows/src/utils/composer/create-step.ts @@ -16,10 +16,25 @@ import { } from "./type" import { proxify } from "./helpers/proxy" +/** + * The type of invocation function passed to a step. + * + * @typeParam TInput - The type of the input that the function expects. + * @typeParam TOutput - The type of the output that the function returns. + * @typeParam TCompensateInput - The type of the input that the compensation function expects. + * + * @returns The expected output based on the type parameter `TOutput`. + */ type InvokeFn = ( + /** + * The input of the step. + */ input: { [Key in keyof TInput]: TInput[Key] }, + /** + * The step's context. + */ context: StepExecutionContext ) => | void @@ -32,8 +47,22 @@ type InvokeFn = ( TCompensateInput extends undefined ? TOutput : TCompensateInput >> +/** + * The type of compensation function passed to a step. + * + * @typeParam T - + * The type of the argument passed to the compensation function. If not specified, then it will be the same type as the invocation function's output. + * + * @returns There's no expected type to be returned by the compensation function. + */ type CompensateFn = ( + /** + * The argument passed to the compensation function. + */ input: T | undefined, + /** + * The step's context. + */ context: StepExecutionContext ) => unknown | Promise @@ -56,6 +85,8 @@ interface ApplyStepOptions< } /** + * @internal + * * Internal function to create the invoke and compensate handler for a step. * This is where the inputs and context are passed to the underlying invoke and compensate function. * @@ -152,55 +183,77 @@ function applyStep< } /** - * Function which will create a StepFunction to be used inside a createWorkflow composer function. - * This function will return a function which can be used to bind the step to a workflow. - * The types of the input to be passed to the step function is defined by the generic of the invoke function provided. + * This function creates a {@link StepFunction} that can be used as a step in a workflow constructed by the {@link createWorkflow} function. * - * @param name - * @param invokeFn - * @param compensateFn + * @typeParam TInvokeInput - The type of the expected input parameter to the invocation function. + * @typeParam TInvokeResultOutput - The type of the expected output parameter of the invocation function. + * @typeParam TInvokeResultCompensateInput - The type of the expected input parameter to the compensation function. + * + * @returns A step function to be used in a workflow. * * @example - * ```ts + * import { + * createStep, + * StepResponse, + * StepExecutionContext, + * WorkflowData + * } from "@medusajs/workflows" + * * interface CreateProductInput { * title: string * } * - * interface CreateProductOutput { - * product: { id: string; title: string } - * compensateInput: { - * product_id: string - * } - * } - * * export const createProductStep = createStep( - * "createProductStep", - * async function (input: Step1Input, context: StepExecutionContext): Promise { - * const productService = context.container.resolve("productService") - * const product = await productService.create(input) - * return { - * product, - * compensateInput: { - * product_id: product.id - * } - * } - * }, - * async function (input: { product_id: string }, context: StepExecutionContext) { - * const productService = context.container.resolve("productService") + * "createProductStep", + * async function ( + * input: CreateProductInput, + * context + * ) { + * const productService = context.container.resolve( + * "productService" + * ) + * const product = await productService.create(input) + * return new StepResponse({ + * product + * }, { + * product_id: product.id + * }) + * }, + * async function ( + * input, + * context + * ) { + * const productService = context.container.resolve( + * "productService" + * ) * await productService.delete(input.product_id) - * }) + * } + * ) */ export function createStep< TInvokeInput extends object, TInvokeResultOutput, TInvokeResultCompensateInput >( + /** + * The name of the step. + */ name: string, + /** + * An invocation function that will be executed when the workflow is executed. The function must return an instance of {@link StepResponse}. The constructor of {@link StepResponse} + * accepts the output of the step as a first argument, and optionally as a second argument the data to be passed to the compensation function as a parameter. + */ invokeFn: InvokeFn< TInvokeInput, TInvokeResultOutput, TInvokeResultCompensateInput >, + /** + * A compensation function that's executed if an error occurs in the workflow. It's used to roll-back actions when errors occur. + * It accepts as a parameter the second argument passed to the constructor of the {@link StepResponse} instance returned by the invocation function. If the + * invocation function doesn't pass the second argument to `StepResponse` constructor, the compensation function receives the first argument + * passed to the `StepResponse` constructor instead. + */ compensateFn?: CompensateFn ): StepFunction { const stepName = name ?? invokeFn.name diff --git a/packages/workflows/src/utils/composer/create-workflow.ts b/packages/workflows/src/utils/composer/create-workflow.ts index c9524b5dbc..a2c01e3cdc 100644 --- a/packages/workflows/src/utils/composer/create-workflow.ts +++ b/packages/workflows/src/utils/composer/create-workflow.ts @@ -20,6 +20,53 @@ import { proxify } from "./helpers/proxy" global[SymbolMedusaWorkflowComposerContext] = null +/** + * An exported workflow, which is the type of a workflow constructed by the {@link createWorkflow} function. The exported workflow can be invoked to create + * an executable workflow, optionally within a specified container. So, to execute the workflow, you must invoke the exported workflow, then run the + * `run` method of the exported workflow. + * + * @example + * To execute a workflow: + * + * ```ts + * myWorkflow() + * .run({ + * input: { + * name: "John" + * } + * }) + * .then(({ result }) => { + * console.log(result) + * }) + * ``` + * + * To specify the container of the workflow, you can pass it as an argument to the call of the exported workflow. This is necessary when executing the workflow + * within a Medusa resource such as an API Route or a Subscriber. + * + * For example: + * + * ```ts + * import type { + * MedusaRequest, + * MedusaResponse + * } from "@medusajs/medusa"; + * import myWorkflow from "../../../workflows/hello-world"; + * + * export async function GET( + * req: MedusaRequest, + * res: MedusaResponse + * ) { + * const { result } = await myWorkflow(req.scope) + * .run({ + * input: { + * name: req.query.name as string + * } + * }) + * + * res.send(result) + * } + * ``` + */ type ReturnWorkflow> = { ( container?: LoadedModule[] | MedusaContainer @@ -37,32 +84,58 @@ type ReturnWorkflow> = { } & THooks /** - * Creates a new workflow with the given name and composer function. - * The composer function will compose the workflow by using the step, parallelize and other util functions that - * will allow to define the flow of event of a workflow. + * This function creates a workflow with the provided name and a constructor function. + * The constructor function builds the workflow from steps created by the {@link createStep} function. + * The returned workflow is an exported workflow of type {@link ReturnWorkflow}, meaning it's not executed right away. To execute it, + * invoke the exported workflow, then run its `run` method. * - * @param name - * @param composer + * @typeParam TData - The type of the input passed to the composer function. + * @typeParam TResult - The type of the output returned by the composer function. + * @typeParam THooks - The type of hooks defined in the workflow. + * + * @returns The created workflow. You can later execute the workflow by invoking it, then using its `run` method. * * @example - * ```ts - * import { createWorkflow, WorkflowData } from "@medusajs/workflows" - * import { createProductStep, getProductStep, createPricesStep } from "./steps" + * import { createWorkflow } from "@medusajs/workflows" + * import { MedusaRequest, MedusaResponse, Product } from "@medusajs/medusa" + * import { + * createProductStep, + * getProductStep, + * createPricesStep + * } from "./steps" * - * interface MyWorkflowData { + * interface WorkflowInput { * title: string * } * - * const myWorkflow = createWorkflow("my-workflow", (input: WorkflowData) => { - * // Everything here will be executed and resolved later during the execution. Including the data access. + * const myWorkflow = createWorkflow< + * WorkflowInput, + * Product + * >("my-workflow", (input) => { + * // Everything here will be executed and resolved later + * // during the execution. Including the data access. * - * const product = createProductStep(input) - * const prices = createPricesStep(product) + * const product = createProductStep(input) + * const prices = createPricesStep(product) + * return getProductStep(product.id) + * } + * ) * - * const id = product.id - * return getProductStep(product.id) - * }) - * ``` + * export async function GET( + * req: MedusaRequest, + * res: MedusaResponse + * ) { + * const { result: product } = await myWorkflow(req.scope) + * .run({ + * input: { + * title: "Shirt" + * } + * }) + * + * res.json({ + * product + * }) + * } */ export function createWorkflow< @@ -70,7 +143,15 @@ export function createWorkflow< TResult, THooks extends Record = Record >( + /** + * The name of the workflow. + */ name: string, + /** + * The constructor function that is executed when the `run` method in {@link ReturnWorkflow} is used. + * The function can't be an arrow function or an asynchronus function. It also can't directly manipulate data. + * You'll have to use the {@link transform} function if you need to directly manipulate data. + */ composer: (input: WorkflowData) => | void | WorkflowData diff --git a/packages/workflows/src/utils/composer/helpers/resolve-value.ts b/packages/workflows/src/utils/composer/helpers/resolve-value.ts index 5a74e19448..c5fb131771 100644 --- a/packages/workflows/src/utils/composer/helpers/resolve-value.ts +++ b/packages/workflows/src/utils/composer/helpers/resolve-value.ts @@ -30,6 +30,9 @@ async function resolveProperty(property, transactionContext) { } } +/** + * @internal + */ export async function resolveValue(input, transactionContext) { const unwrapInput = async ( inputTOUnwrap: Record, diff --git a/packages/workflows/src/utils/composer/helpers/step-response.ts b/packages/workflows/src/utils/composer/helpers/step-response.ts index c9d9bf55cb..a0ccaff81e 100644 --- a/packages/workflows/src/utils/composer/helpers/step-response.ts +++ b/packages/workflows/src/utils/composer/helpers/step-response.ts @@ -1,27 +1,64 @@ import { SymbolWorkflowStepResponse } from "./symbol" +/** + * This class is used to create the response returned by a step. A step return its data by returning an instance of `StepResponse`. + * + * @typeParam TOutput - The type of the output of the step. + * @typeParam TCompensateInput - + * The type of the compensation input. If the step doesn't specify any compensation input, then the type of `TCompensateInput` is the same + * as that of `TOutput`. + */ export class StepResponse { readonly #__type = SymbolWorkflowStepResponse readonly #output: TOutput readonly #compensateInput?: TCompensateInput - constructor(output: TOutput, compensateInput?: TCompensateInput) { + /** + * The constructor of the StepResponse + * + * @typeParam TOutput - The type of the output of the step. + * @typeParam TCompensateInput - + * The type of the compensation input. If the step doesn't specify any compensation input, then the type of `TCompensateInput` is the same + * as that of `TOutput`. + */ + constructor( + /** + * The output of the step. + */ + output: TOutput, + /** + * The input to be passed as a parameter to the step's compensation function. If not provided, the `output` will be provided instead. + */ + compensateInput?: TCompensateInput + ) { this.#output = output this.#compensateInput = (compensateInput ?? output) as TCompensateInput } + /** + * @internal + */ get __type() { return this.#__type } + /** + * @internal + */ get output(): TOutput { return this.#output } + /** + * @internal + */ get compensateInput(): TCompensateInput { return this.#compensateInput as TCompensateInput } + /** + * @internal + */ toJSON() { return { __type: this.#__type, diff --git a/packages/workflows/src/utils/composer/hook.ts b/packages/workflows/src/utils/composer/hook.ts index e3aa12b093..8fa91c8028 100644 --- a/packages/workflows/src/utils/composer/hook.ts +++ b/packages/workflows/src/utils/composer/hook.ts @@ -9,7 +9,100 @@ import { WorkflowData, } from "./type" -export function hook(name: string, value: any): WorkflowData { +/** + * + * @ignore + * + * This function allows you to add hooks in your workflow that provide access to some data. Then, consumers of that workflow can add a handler function that performs + * an action with the provided data or modify it. + * + * For example, in a "create product" workflow, you may add a hook after the product is created, providing access to the created product. + * Then, developers using that workflow can hook into that point to access the product, modify its attributes, then return the updated product. + * + * @typeParam TOutput - The expected output of the hook's handler function. + * @returns The output of handler functions of this hook. If there are no handler functions, the output is `undefined`. + * + * @example + * import { + * createWorkflow, + * StepExecutionContext, + * hook, + * transform + * } from "@medusajs/workflows" + * import { + * createProductStep, + * getProductStep, + * createPricesStep + * } from "./steps" + * import { + * MedusaRequest, + * MedusaResponse, + * Product, ProductService + * } from "@medusajs/medusa" + * + * interface WorkflowInput { + * title: string + * } + * + * const myWorkflow = createWorkflow< + * WorkflowInput, + * Product + * >("my-workflow", + * function (input) { + * const product = createProductStep(input) + * + * const hookProduct = hook("createdProductHook", product) + * + * const newProduct = transform({ + * product, + * hookProduct + * }, (input) => { + * return input.hookProduct || input.product + * }) + * + * const prices = createPricesStep(newProduct) + * + * return getProductStep(product.id) + * } + * ) + * + * myWorkflow.createdProductHook( + * async (product, context: StepExecutionContext) => { + * const productService: ProductService = context.container.resolve("productService") + * + * const updatedProduct = await productService.update(product.id, { + * description: "a cool shirt" + * }) + * + * return updatedProduct + * }) + * + * export async function POST( + * req: MedusaRequest, + * res: MedusaResponse + * ) { + * const { result: product } = await myWorkflow(req.scope) + * .run({ + * input: { + * title: req.body.title + * } + * }) + * + * res.json({ + * product + * }) + * } + */ +export function hook( + /** + * The name of the hook. This will be used by the consumer to add a handler method for the hook. + */ + name: string, + /** + * The data that a handler function receives as a parameter. + */ + value: any +): WorkflowData { const hookBinder = ( global[SymbolMedusaWorkflowComposerContext] as CreateWorkflowComposerContext ).hookBinder diff --git a/packages/workflows/src/utils/composer/parallelize.ts b/packages/workflows/src/utils/composer/parallelize.ts index f1cf22a052..fdc8ed50b3 100644 --- a/packages/workflows/src/utils/composer/parallelize.ts +++ b/packages/workflows/src/utils/composer/parallelize.ts @@ -2,32 +2,44 @@ import { CreateWorkflowComposerContext, WorkflowData } from "./type" import { SymbolMedusaWorkflowComposerContext } from "./helpers" /** - * Parallelize multiple steps. - * The steps will be run in parallel. The result of each step will be returned as part of the result array. - * Each StepResult can be accessed from the resulted array in the order they were passed to the parallelize function. + * This function is used to run multiple steps in parallel. The result of each step will be returned as part of the result array. * - * @param steps + * @typeParam TResult - The type of the expected result. + * + * @returns The step results. The results are ordered in the array by the order they're passed in the function's parameter. * * @example * ```ts - * import { createWorkflow, WorkflowData, parallelize } from "@medusajs/workflows" - * import { createProductStep, getProductStep, createPricesStep, attachProductToSalesChannelStep } from "./steps" + * import { + * createWorkflow, + * parallelize + * } from "@medusajs/workflows" + * import { + * createProductStep, + * getProductStep, + * createPricesStep, + * attachProductToSalesChannelStep + * } from "./steps" * - * interface MyWorkflowData { + * interface WorkflowInput { * title: string * } * - * const myWorkflow = createWorkflow("my-workflow", (input: WorkflowData) => { - * const product = createProductStep(input) + * const myWorkflow = createWorkflow< + * WorkflowInput, + * Product + * >("my-workflow", (input) => { + * const product = createProductStep(input) * - * const [prices, productSalesChannel] = parallelize( + * const [prices, productSalesChannel] = parallelize( * createPricesStep(product), * attachProductToSalesChannelStep(product) - * ) + * ) * - * const id = product.id - * return getProductStep(product.id) - * }) + * const id = product.id + * return getProductStep(product.id) + * } + * ) */ export function parallelize( ...steps: TResult diff --git a/packages/workflows/src/utils/composer/transform.ts b/packages/workflows/src/utils/composer/transform.ts index 42648019e6..03a97d3c6a 100644 --- a/packages/workflows/src/utils/composer/transform.ts +++ b/packages/workflows/src/utils/composer/transform.ts @@ -13,14 +13,67 @@ type Func1 = ( type Func = (input: T, context: StepExecutionContext) => U | Promise +/** + * + * This function transforms the output of other utility functions. + * + * For example, if you're using the value(s) of some step(s) as an input to a later step. As you can't directly manipulate data in the workflow constructor function passed to {@link createWorkflow}, + * the `transform` function provides access to the runtime value of the step(s) output so that you can manipulate them. + * + * Another example is if you're using the runtime value of some step(s) as the output of a workflow. + * + * If you're also retrieving the output of a hook and want to check if its value is set, you must use a workflow to get the runtime value of that hook. + * + * @returns There's no expected value to be returned by the `transform` function. + * + * @example + * import { + * createWorkflow, + * transform + * } from "@medusajs/workflows" + * import { step1, step2 } from "./steps" + * + * type WorkflowInput = { + * name: string + * } + * + * type WorkflowOutput = { + * message: string + * } + * + * const myWorkflow = createWorkflow< + * WorkflowInput, + * WorkflowOutput + * > + * ("hello-world", (input) => { + * const str1 = step1(input) + * const str2 = step2(input) + * + * return transform({ + * str1, + * str2 + * }, (input) => ({ + * message: `${input.str1}${input.str2}` + * })) + * }) + */ // prettier-ignore // eslint-disable-next-line max-len export function transform( + /** + * The output(s) of other step functions. + */ values: T, + /** + * The transform function used to perform action on the runtime values of the provided `values`. + */ ...func: | [Func1] ): WorkflowData +/** + * @internal + */ // prettier-ignore // eslint-disable-next-line max-len export function transform( @@ -30,6 +83,9 @@ export function transform( | [Func1, Func] ): WorkflowData +/** + * @internal + */ // prettier-ignore // eslint-disable-next-line max-len export function transform( @@ -40,6 +96,9 @@ export function transform( | [Func1, Func, Func] ): WorkflowData +/** + * @internal + */ // prettier-ignore // eslint-disable-next-line max-len export function transform( @@ -51,6 +110,9 @@ export function transform( | [Func1, Func, Func, Func] ): WorkflowData +/** + * @internal + */ // prettier-ignore // eslint-disable-next-line max-len export function transform( @@ -63,6 +125,9 @@ export function transform, Func, Func, Func, Func] ): WorkflowData +/** + * @internal + */ // prettier-ignore // eslint-disable-next-line max-len export function transform( @@ -76,6 +141,9 @@ export function transform, Func, Func, Func, Func, Func] ): WorkflowData +/** + * @internal + */ // prettier-ignore // eslint-disable-next-line max-len export function transform( @@ -90,14 +158,6 @@ export function transform, Func, Func, Func, Func, Func, Func] ): WorkflowData -/** - * Transforms the input value(s) using the provided functions. - * Allow to perform transformation on the future result of the step(s) to be passed - * to other steps later on at run time. - * - * @param values - * @param functions - */ export function transform( values: any | any[], ...functions: Function[] diff --git a/packages/workflows/src/utils/composer/type.ts b/packages/workflows/src/utils/composer/type.ts index 664f7f182e..f6f019d9e8 100644 --- a/packages/workflows/src/utils/composer/type.ts +++ b/packages/workflows/src/utils/composer/type.ts @@ -15,6 +15,12 @@ export type StepFunctionResult = ] : WorkflowData<{ [K in keyof TOutput]: TOutput[K] }> +/** + * A step function to be used in a workflow. + * + * @typeParam TInput - The type of the input of the step. + * @typeParam TOutput - The type of the output of the step. + */ export type StepFunction = { (input: { [K in keyof TInput]: WorkflowData }): WorkflowData<{ [K in keyof TOutput]: TOutput[K] @@ -28,6 +34,11 @@ export type WorkflowDataProperties = { __step__: string } +/** + * This type is used to encapsulate the input or output type of all utils. + * + * @typeParam T - The type of a step's input or result. + */ export type WorkflowData = (T extends object ? { [Key in keyof T]: WorkflowData @@ -53,9 +64,21 @@ export type CreateWorkflowComposerContext = { ) => TOutput } +/** + * The step's context. + */ export interface StepExecutionContext { + /** + * The container used to access resources, such as services, in the step. + */ container: MedusaContainer + /** + * Metadata passed in the input. + */ metadata: TransactionPayload["metadata"] + /** + * {@inheritDoc Context} + */ context: Context }