chore: local workflow proxying methods to pass context (#6263)
What: - When calling a module's method inside a Local Workflow the MedusaContext is passed as the last argument to the method if not provided - Add `requestId` to req - A couple of fixes on Remote Joiner and the data fetcher for internal services Why: - The context used to initialize the workflow has to be shared with all modules. properties like transactionId will be used to emit events and requestId to trace logs for example.
This commit is contained in:
committed by
GitHub
parent
a2bf6756ac
commit
45134e4d11
@@ -144,4 +144,79 @@ describe("toRemoteJoinerQuery", () => {
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("should transform a nested object with arguments and directives to a Remote Joiner Query format only using variables", async () => {
|
||||
const obj = {
|
||||
product: {
|
||||
fields: ["id", "title", "handle"],
|
||||
variants: {
|
||||
fields: ["sku"],
|
||||
__directives: {
|
||||
directiveName: "value",
|
||||
},
|
||||
shipping_profiles: {
|
||||
profile: {
|
||||
fields: ["id", "name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const rjQuery = toRemoteJoinerQuery(obj, {
|
||||
product: {
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
},
|
||||
"product.variants.shipping_profiles.profile": {
|
||||
context: {
|
||||
customer_group: "cg_123",
|
||||
region_id: "US",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(rjQuery).toEqual({
|
||||
alias: "product",
|
||||
fields: ["id", "title", "handle"],
|
||||
expands: [
|
||||
{
|
||||
property: "variants",
|
||||
directives: [
|
||||
{
|
||||
name: "directiveName",
|
||||
value: "value",
|
||||
},
|
||||
],
|
||||
fields: ["sku"],
|
||||
},
|
||||
{
|
||||
property: "variants.shipping_profiles",
|
||||
},
|
||||
{
|
||||
property: "variants.shipping_profiles.profile",
|
||||
args: [
|
||||
{
|
||||
name: "context",
|
||||
value: {
|
||||
customer_group: "cg_123",
|
||||
region_id: "US",
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: ["id", "name"],
|
||||
},
|
||||
],
|
||||
args: [
|
||||
{
|
||||
name: "limit",
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
name: "offset",
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -37,10 +37,11 @@ class GraphQLParser {
|
||||
}
|
||||
|
||||
private parseValueNode(valueNode: ValueNode): unknown {
|
||||
const obj = {}
|
||||
|
||||
switch (valueNode.kind) {
|
||||
case Kind.VARIABLE:
|
||||
const variableName = valueNode.name.value
|
||||
return this.variables ? this.variables[variableName] : undefined
|
||||
return this.variables ? this.variables[valueNode.name.value] : undefined
|
||||
case Kind.INT:
|
||||
return parseInt(valueNode.value, 10)
|
||||
case Kind.FLOAT:
|
||||
@@ -55,7 +56,6 @@ class GraphQLParser {
|
||||
case Kind.LIST:
|
||||
return valueNode.values.map((v) => this.parseValueNode(v))
|
||||
case Kind.OBJECT:
|
||||
let obj = {}
|
||||
for (const field of valueNode.fields) {
|
||||
obj[field.name.value] = this.parseValueNode(field.value)
|
||||
}
|
||||
|
||||
@@ -1,53 +1,81 @@
|
||||
import { RemoteJoinerQuery } from "@medusajs/types"
|
||||
|
||||
export function toRemoteJoinerQuery(obj: any): RemoteJoinerQuery {
|
||||
export function toRemoteJoinerQuery(
|
||||
obj: any,
|
||||
variables: Record<string, any> = {}
|
||||
): RemoteJoinerQuery {
|
||||
const remoteJoinerQuery: RemoteJoinerQuery = {
|
||||
alias: "",
|
||||
fields: [],
|
||||
expands: [],
|
||||
}
|
||||
|
||||
function extractRecursive(obj, parentName = "", isEntryPoint = true) {
|
||||
for (const key in obj) {
|
||||
let entryPoint = ""
|
||||
function extractRecursive(obj: any, parentName = "", isEntryPoint = true) {
|
||||
for (const key of Object.keys(obj ?? {})) {
|
||||
const value = obj[key]
|
||||
|
||||
const canExpand =
|
||||
typeof value === "object" &&
|
||||
!["fields", "__args", "__directives"].includes(key)
|
||||
|
||||
if (canExpand) {
|
||||
const entityName = parentName ? `${parentName}.${key}` : key
|
||||
const expandObj: any = {
|
||||
property: entityName,
|
||||
}
|
||||
|
||||
const reference = isEntryPoint ? remoteJoinerQuery : expandObj
|
||||
|
||||
if (value.__args) {
|
||||
reference.args = Object.entries(value.__args).map(
|
||||
([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (value.__directives) {
|
||||
reference.directives = Object.entries(value.__directives).map(
|
||||
([name, value]) => ({ name, value })
|
||||
)
|
||||
}
|
||||
|
||||
reference.fields = value.fields
|
||||
|
||||
if (isEntryPoint) {
|
||||
remoteJoinerQuery.alias = key
|
||||
} else {
|
||||
remoteJoinerQuery.expands!.push(expandObj)
|
||||
}
|
||||
|
||||
extractRecursive(value, isEntryPoint ? "" : entityName, false)
|
||||
if (!canExpand) {
|
||||
continue
|
||||
}
|
||||
|
||||
const entityName = parentName ? `${parentName}.${key}` : key
|
||||
const variablesPath = !isEntryPoint
|
||||
? `${entryPoint}${parentName ? "." + parentName : parentName}.${key}`
|
||||
: key
|
||||
|
||||
if (isEntryPoint) {
|
||||
entryPoint = key
|
||||
}
|
||||
|
||||
const currentVariables = variables[variablesPath]
|
||||
|
||||
const expandObj: any = {
|
||||
property: entityName,
|
||||
}
|
||||
|
||||
const reference = isEntryPoint ? remoteJoinerQuery : expandObj
|
||||
|
||||
if (currentVariables) {
|
||||
reference.args = Object.entries(currentVariables).map(
|
||||
([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (value.__args) {
|
||||
reference.args = [
|
||||
...(reference.__args || []),
|
||||
...Object.entries(value.__args).map(([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
if (value.__directives) {
|
||||
reference.directives = Object.entries(value.__directives).map(
|
||||
([name, value]) => ({ name, value })
|
||||
)
|
||||
}
|
||||
|
||||
if (value.fields) {
|
||||
reference.fields = value.fields
|
||||
}
|
||||
|
||||
if (isEntryPoint) {
|
||||
remoteJoinerQuery.alias = key
|
||||
} else {
|
||||
remoteJoinerQuery.expands!.push(expandObj)
|
||||
}
|
||||
|
||||
extractRecursive(value, isEntryPoint ? "" : entityName, false)
|
||||
}
|
||||
|
||||
return remoteJoinerQuery
|
||||
|
||||
@@ -53,7 +53,7 @@ export class RemoteJoiner {
|
||||
}, {})
|
||||
|
||||
if (expands) {
|
||||
for (const key in expands) {
|
||||
for (const key of Object.keys(expands ?? {})) {
|
||||
const expand = expands[key]
|
||||
if (expand) {
|
||||
if (Array.isArray(data[key])) {
|
||||
@@ -146,12 +146,14 @@ export class RemoteJoiner {
|
||||
const isReadOnlyDefinition =
|
||||
service.serviceName === undefined || service.isReadOnlyLink
|
||||
if (!isReadOnlyDefinition) {
|
||||
if (!service.alias) {
|
||||
service.alias = [{ name: service.serviceName!.toLowerCase() }]
|
||||
} else if (!Array.isArray(service.alias)) {
|
||||
service.alias ??= []
|
||||
|
||||
if (!Array.isArray(service.alias)) {
|
||||
service.alias = [service.alias]
|
||||
}
|
||||
|
||||
service.alias.push({ name: service.serviceName! })
|
||||
|
||||
// handle alias.name as array
|
||||
for (let idx = 0; idx < service.alias.length; idx++) {
|
||||
const alias = service.alias[idx]
|
||||
@@ -173,6 +175,11 @@ export class RemoteJoiner {
|
||||
for (const alias of service.alias) {
|
||||
if (this.serviceConfigCache.has(`alias_${alias.name}}`)) {
|
||||
const defined = this.serviceConfigCache.get(`alias_${alias.name}}`)
|
||||
|
||||
if (service.serviceName === defined?.serviceName) {
|
||||
continue
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Cannot add alias "${alias.name}" for "${service.serviceName}". It is already defined for Service "${defined?.serviceName}".`
|
||||
)
|
||||
@@ -223,7 +230,9 @@ export class RemoteJoiner {
|
||||
(rel) => rel.isInternalService === true
|
||||
)
|
||||
|
||||
if (isInternalServicePresent) continue
|
||||
if (isInternalServicePresent) {
|
||||
continue
|
||||
}
|
||||
|
||||
throw new Error(`Service "${serviceName}" was not found`)
|
||||
}
|
||||
@@ -338,7 +347,9 @@ export class RemoteJoiner {
|
||||
relationship
|
||||
)
|
||||
const isObj = isDefined(response.path)
|
||||
const resData = isObj ? response.data[response.path!] : response.data
|
||||
let resData = isObj ? response.data[response.path!] : response.data
|
||||
|
||||
resData = Array.isArray(resData) ? resData : [resData]
|
||||
|
||||
const filteredDataArray = resData.map((data: any) =>
|
||||
RemoteJoiner.filterFields(data, expand.fields, expand.expands)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
|
||||
import { createContainerLike, createMedusaContainer } from "@medusajs/utils"
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import { asValue } from "awilix"
|
||||
|
||||
import {
|
||||
@@ -25,7 +25,7 @@ export class GlobalWorkflow extends WorkflowManager {
|
||||
|
||||
if (!Array.isArray(modulesLoaded) && modulesLoaded) {
|
||||
if (!("cradle" in modulesLoaded)) {
|
||||
container = createContainerLike(modulesLoaded)
|
||||
container = createMedusaContainer(modulesLoaded)
|
||||
} else {
|
||||
container = modulesLoaded
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
|
||||
import { createContainerLike, createMedusaContainer } from "@medusajs/utils"
|
||||
import {
|
||||
MedusaContext,
|
||||
MedusaContextType,
|
||||
MedusaModuleType,
|
||||
createMedusaContainer,
|
||||
} from "@medusajs/utils"
|
||||
import { asValue } from "awilix"
|
||||
import {
|
||||
DistributedTransaction,
|
||||
@@ -28,6 +33,7 @@ export class LocalWorkflow {
|
||||
protected customOptions: Partial<TransactionModelOptions> = {}
|
||||
protected workflow: WorkflowDefinition
|
||||
protected handlers: Map<string, StepHandler>
|
||||
protected medusaContext?: Context
|
||||
|
||||
constructor(
|
||||
workflowId: string,
|
||||
@@ -47,9 +53,9 @@ export class LocalWorkflow {
|
||||
|
||||
if (!Array.isArray(modulesLoaded) && modulesLoaded) {
|
||||
if (!("cradle" in modulesLoaded)) {
|
||||
container = createContainerLike(modulesLoaded)
|
||||
container = createMedusaContainer(modulesLoaded)
|
||||
} else {
|
||||
container = modulesLoaded
|
||||
container = createMedusaContainer({}, modulesLoaded) // copy container
|
||||
}
|
||||
} else if (Array.isArray(modulesLoaded) && modulesLoaded.length) {
|
||||
container = createMedusaContainer()
|
||||
@@ -60,7 +66,49 @@ export class LocalWorkflow {
|
||||
}
|
||||
}
|
||||
|
||||
this.container = container
|
||||
this.container = this.contextualizedMedusaModules(container)
|
||||
}
|
||||
|
||||
private contextualizedMedusaModules(container) {
|
||||
if (!container) {
|
||||
return createMedusaContainer()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
const this_ = this
|
||||
const originalResolver = container.resolve
|
||||
container.resolve = function (registrationName, opts) {
|
||||
const resolved = originalResolver(registrationName, opts)
|
||||
if (resolved?.constructor?.__type !== MedusaModuleType) {
|
||||
return resolved
|
||||
}
|
||||
|
||||
return new Proxy(resolved, {
|
||||
get: function (target, prop) {
|
||||
if (typeof target[prop] !== "function") {
|
||||
return target[prop]
|
||||
}
|
||||
|
||||
return async (...args) => {
|
||||
const ctxIndex =
|
||||
MedusaContext.getIndex(target, prop as string) ?? args.length - 1
|
||||
|
||||
const hasContext = args[ctxIndex]?.__type === MedusaContextType
|
||||
if (!hasContext) {
|
||||
const context = this_.medusaContext
|
||||
if (context?.__type === MedusaContextType) {
|
||||
delete context?.manager
|
||||
delete context?.transactionManager
|
||||
|
||||
args[ctxIndex] = context
|
||||
}
|
||||
}
|
||||
return await target[prop].apply(target, [...args])
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
protected commit() {
|
||||
@@ -265,7 +313,7 @@ export class LocalWorkflow {
|
||||
if (this.flow.hasChanges) {
|
||||
this.commit()
|
||||
}
|
||||
|
||||
this.medusaContext = context
|
||||
const { handler, orchestrator } = this.workflow
|
||||
|
||||
const transaction = await orchestrator.beginTransaction(
|
||||
@@ -288,6 +336,7 @@ export class LocalWorkflow {
|
||||
}
|
||||
|
||||
async getRunningTransaction(uniqueTransactionId: string, context?: Context) {
|
||||
this.medusaContext = context
|
||||
const { handler, orchestrator } = this.workflow
|
||||
|
||||
const transaction = await orchestrator.retrieveExistingTransaction(
|
||||
@@ -303,6 +352,7 @@ export class LocalWorkflow {
|
||||
context?: Context,
|
||||
subscribe?: DistributedTransactionEvents
|
||||
) {
|
||||
this.medusaContext = context
|
||||
const { orchestrator } = this.workflow
|
||||
|
||||
const transaction = await this.getRunningTransaction(
|
||||
@@ -329,6 +379,7 @@ export class LocalWorkflow {
|
||||
context?: Context,
|
||||
subscribe?: DistributedTransactionEvents
|
||||
): Promise<DistributedTransaction> {
|
||||
this.medusaContext = context
|
||||
const { handler, orchestrator } = this.workflow
|
||||
|
||||
const { cleanUpEventListeners } = this.registerEventCallbacks({
|
||||
@@ -355,6 +406,7 @@ export class LocalWorkflow {
|
||||
context?: Context,
|
||||
subscribe?: DistributedTransactionEvents
|
||||
): Promise<DistributedTransaction> {
|
||||
this.medusaContext = context
|
||||
const { handler, orchestrator } = this.workflow
|
||||
|
||||
const { cleanUpEventListeners } = this.registerEventCallbacks({
|
||||
|
||||
@@ -81,7 +81,7 @@ export class WorkflowManager {
|
||||
const finalFlow = flow instanceof OrchestratorBuilder ? flow.build() : flow
|
||||
|
||||
if (WorkflowManager.workflows.has(workflowId)) {
|
||||
function excludeStepUuid(key, value) {
|
||||
const excludeStepUuid = (key, value) => {
|
||||
return key === "uuid" ? undefined : value
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user