feat: Application types generation from project GQL schema's (#8995)
This commit is contained in:
committed by
GitHub
parent
ac30a989f4
commit
2c5e72d141
@@ -5,3 +5,4 @@ export * from "./medusa-module"
|
||||
export * from "./remote-link"
|
||||
export * from "./remote-query"
|
||||
export * from "./types"
|
||||
export { gqlSchemaToTypes } from "./utils"
|
||||
|
||||
@@ -17,20 +17,24 @@ import {
|
||||
RemoteJoinerOptions,
|
||||
RemoteJoinerQuery,
|
||||
RemoteQueryFunction,
|
||||
RemoteQueryObjectConfig,
|
||||
RemoteQueryObjectFromStringResult,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
createMedusaContainer,
|
||||
isObject,
|
||||
isString,
|
||||
MedusaError,
|
||||
ModuleRegistrationName,
|
||||
Modules,
|
||||
ModulesSdkUtils,
|
||||
createMedusaContainer,
|
||||
isObject,
|
||||
isString,
|
||||
promiseAll,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import type { Knex } from "@mikro-orm/knex"
|
||||
import { asValue } from "awilix"
|
||||
import { GraphQLSchema } from "graphql/type"
|
||||
import { MODULE_PACKAGE_NAMES } from "./definitions"
|
||||
import {
|
||||
MedusaModule,
|
||||
@@ -227,6 +231,7 @@ export type MedusaAppOutput = {
|
||||
link: RemoteLink | undefined
|
||||
query: RemoteQueryFunction
|
||||
entitiesMap?: Record<string, any>
|
||||
gqlSchema?: GraphQLSchema
|
||||
notFound?: Record<string, Record<string, string>>
|
||||
runMigrations: RunMigrationFn
|
||||
revertMigrations: RevertMigrationFn
|
||||
@@ -352,15 +357,17 @@ async function MedusaApp_({
|
||||
)
|
||||
|
||||
if (loaderOnly) {
|
||||
async function query(...args: Parameters<RemoteQueryFunction>) {
|
||||
throw new Error("Querying not allowed in loaderOnly mode")
|
||||
}
|
||||
|
||||
return {
|
||||
onApplicationShutdown,
|
||||
onApplicationPrepareShutdown,
|
||||
onApplicationStart,
|
||||
modules: allModules,
|
||||
link: undefined,
|
||||
query: async () => {
|
||||
throw new Error("Querying not allowed in loaderOnly mode")
|
||||
},
|
||||
query: query as RemoteQueryFunction,
|
||||
runMigrations: async () => {
|
||||
throw new Error("Migrations not allowed in loaderOnly mode")
|
||||
},
|
||||
@@ -413,11 +420,75 @@ async function MedusaApp_({
|
||||
customRemoteFetchData: remoteFetchData,
|
||||
})
|
||||
|
||||
const query = async (
|
||||
query: string | RemoteJoinerQuery | object,
|
||||
/**
|
||||
* Query wrapper to provide specific API's and pre processing around remoteQuery.query
|
||||
* @param queryConfig
|
||||
* @param options
|
||||
*/
|
||||
async function query<const TEntry extends string>(
|
||||
queryConfig: RemoteQueryObjectConfig<TEntry>,
|
||||
options?: RemoteJoinerOptions
|
||||
): Promise<any>
|
||||
|
||||
async function query<
|
||||
const TConfig extends RemoteQueryObjectFromStringResult<any>
|
||||
>(queryConfig: TConfig, options?: RemoteJoinerOptions): Promise<any>
|
||||
|
||||
/**
|
||||
* Query wrapper to provide specific API's and pre processing around remoteQuery.query
|
||||
* @param query
|
||||
* @param options
|
||||
*/
|
||||
async function query(
|
||||
query: RemoteJoinerQuery,
|
||||
options?: RemoteJoinerOptions
|
||||
): Promise<any>
|
||||
|
||||
/**
|
||||
* Query wrapper to provide specific API's and pre processing around remoteQuery.query
|
||||
* @param query
|
||||
* @param options
|
||||
*/
|
||||
async function query<const TEntry extends string>(
|
||||
query:
|
||||
| RemoteJoinerQuery
|
||||
| RemoteQueryObjectConfig<TEntry>
|
||||
| RemoteQueryObjectFromStringResult<any>,
|
||||
options?: RemoteJoinerOptions
|
||||
) {
|
||||
if (!isObject(query)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Invalid query, expected object and received something else."
|
||||
)
|
||||
}
|
||||
|
||||
let normalizedQuery: any = query
|
||||
|
||||
if ("__value" in query) {
|
||||
normalizedQuery = query.__value
|
||||
} else if (
|
||||
"entryPoint" in normalizedQuery ||
|
||||
"service" in normalizedQuery
|
||||
) {
|
||||
normalizedQuery = remoteQueryObjectFromString(
|
||||
normalizedQuery as Parameters<typeof remoteQueryObjectFromString>[0]
|
||||
).__value
|
||||
}
|
||||
|
||||
return await remoteQuery.query(normalizedQuery, undefined, options)
|
||||
}
|
||||
/**
|
||||
* Query wrapper to provide specific GraphQL like API around remoteQuery.query
|
||||
* @param query
|
||||
* @param variables
|
||||
* @param options
|
||||
*/
|
||||
query.gql = async function (
|
||||
query: string,
|
||||
variables?: Record<string, unknown>,
|
||||
options?: RemoteJoinerOptions
|
||||
) => {
|
||||
) {
|
||||
return await remoteQuery.query(query, variables, options)
|
||||
}
|
||||
|
||||
@@ -524,6 +595,7 @@ async function MedusaApp_({
|
||||
link: remoteLink,
|
||||
query,
|
||||
entitiesMap: schema.getTypeMap(),
|
||||
gqlSchema: schema,
|
||||
notFound,
|
||||
runMigrations,
|
||||
revertMigrations,
|
||||
|
||||
@@ -145,7 +145,7 @@ export class RemoteQuery {
|
||||
return {
|
||||
skip: options.skip,
|
||||
take: options.take,
|
||||
cursor: options.cursor,
|
||||
// cursor: options.cursor, not yet supported
|
||||
// TODO: next cursor
|
||||
count,
|
||||
}
|
||||
|
||||
@@ -7,3 +7,5 @@ export enum MODULE_RESOURCE_TYPE {
|
||||
SHARED = "shared",
|
||||
ISOLATED = "isolated",
|
||||
}
|
||||
|
||||
export { GraphQLSchema } from "graphql"
|
||||
|
||||
99
packages/core/modules-sdk/src/utils/gql-schema-to-types.ts
Normal file
99
packages/core/modules-sdk/src/utils/gql-schema-to-types.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { MedusaModule } from "../medusa-module"
|
||||
import { FileSystem } from "@medusajs/utils"
|
||||
import { GraphQLSchema } from "graphql/type"
|
||||
import { parse, printSchema } from "graphql"
|
||||
import { codegen } from "@graphql-codegen/core"
|
||||
import * as typescriptPlugin from "@graphql-codegen/typescript"
|
||||
|
||||
function buildEntryPointsTypeMap(
|
||||
schema: string
|
||||
): { entryPoint: string; entityType: any }[] {
|
||||
// build map entry point to there type to be merged and used by the remote query
|
||||
|
||||
const joinerConfigs = MedusaModule.getAllJoinerConfigs()
|
||||
return joinerConfigs
|
||||
.flatMap((config) => {
|
||||
const aliases = Array.isArray(config.alias)
|
||||
? config.alias
|
||||
: config.alias
|
||||
? [config.alias]
|
||||
: []
|
||||
|
||||
return aliases.flatMap((alias) => {
|
||||
const names = Array.isArray(alias.name) ? alias.name : [alias.name]
|
||||
const entity = alias.args?.["entity"]
|
||||
return names.map((aliasItem) => {
|
||||
return {
|
||||
entryPoint: aliasItem,
|
||||
entityType: entity
|
||||
? schema.includes(`export type ${entity} `)
|
||||
? alias.args?.["entity"]
|
||||
: "any"
|
||||
: "any",
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
async function generateTypes({
|
||||
outputDir,
|
||||
config,
|
||||
}: {
|
||||
outputDir: string
|
||||
config: Parameters<typeof codegen>[0]
|
||||
}) {
|
||||
const fileSystem = new FileSystem(outputDir)
|
||||
|
||||
let output = await codegen(config)
|
||||
const entryPoints = buildEntryPointsTypeMap(output)
|
||||
|
||||
const remoteQueryEntryPoints = `
|
||||
declare module '@medusajs/types' {
|
||||
interface RemoteQueryEntryPoints {
|
||||
${entryPoints
|
||||
.map((entry) => ` ${entry.entryPoint}: ${entry.entityType}`)
|
||||
.join("\n")}
|
||||
}
|
||||
}`
|
||||
|
||||
output += remoteQueryEntryPoints
|
||||
|
||||
await fileSystem.create("remote-query-types.d.ts", output)
|
||||
await fileSystem.create(
|
||||
"index.d.ts",
|
||||
"export * as RemoteQueryTypes from './remote-query-types'"
|
||||
)
|
||||
}
|
||||
|
||||
export async function gqlSchemaToTypes({
|
||||
schema,
|
||||
outputDir,
|
||||
}: {
|
||||
schema: GraphQLSchema
|
||||
outputDir: string
|
||||
}) {
|
||||
const config = {
|
||||
documents: [],
|
||||
config: {
|
||||
scalars: {
|
||||
DateTime: { output: "Date | string" },
|
||||
JSON: { output: "Record<any, unknown>" },
|
||||
},
|
||||
},
|
||||
filename: "",
|
||||
schema: parse(printSchema(schema as any)),
|
||||
plugins: [
|
||||
// Each plugin should be an object
|
||||
{
|
||||
typescript: {}, // Here you can pass configuration to the plugin
|
||||
},
|
||||
],
|
||||
pluginMap: {
|
||||
typescript: typescriptPlugin,
|
||||
},
|
||||
}
|
||||
|
||||
await generateTypes({ outputDir, config })
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./clean-graphql-schema"
|
||||
export * from "./graphql-schema-to-fields"
|
||||
export * from "./gql-schema-to-types"
|
||||
|
||||
Reference in New Issue
Block a user