feat: Application types generation from project GQL schema's (#8995)

This commit is contained in:
Adrien de Peretti
2024-09-06 15:15:32 +05:30
committed by GitHub
parent ac30a989f4
commit 2c5e72d141
92 changed files with 5129 additions and 443 deletions
@@ -1,6 +1,6 @@
import { IPricingModuleService } from "@medusajs/types"
import { MedusaError, ModuleRegistrationName } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export interface GetVariantPriceSetsStepInput {
variantIds: string[]
@@ -24,21 +24,13 @@ export const getVariantPriceSetsStep = createStep(
const remoteQuery = container.resolve("remoteQuery")
const variantPriceSets = await remoteQuery(
{
variant: {
fields: ["id"],
price_set: {
fields: ["id"],
},
},
const variantPriceSets = await remoteQuery({
entryPoint: "variant",
fields: ["id", "price_set.id"],
variables: {
id: data.variantIds,
},
{
variant: {
id: data.variantIds,
},
}
)
})
const notFound: string[] = []
const priceSetIds: string[] = []
@@ -1,11 +1,6 @@
import { CartWorkflowDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
Modules,
isObject,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ContainerRegistrationKeys, isObject, Modules } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export interface RetrieveCartWithLinksStepInput {
cart_or_cart_id: string | CartWorkflowDTO
@@ -29,12 +24,17 @@ export const retrieveCartWithLinksStep = createStep(
const remoteQuery = container.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
const query = remoteQueryObjectFromString({
const query = {
entryPoint: Modules.CART,
fields,
})
variables: {
cart: {
id,
},
},
}
const [cart] = await remoteQuery(query, { cart: { id } })
const [cart] = await remoteQuery(query)
return new StepResponse(cart)
}
@@ -5,7 +5,7 @@ import {
Modules,
PromotionActions,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export interface UpdateCartPromotionStepInput {
id: string
@@ -33,9 +33,10 @@ export const updateCartPromotionsStep = createStep(
)
const existingCartPromotionLinks = await remoteQuery({
cart_promotion: {
__args: { cart_id: [id] },
fields: ["cart_id", "promotion_id"],
entryPoint: "cart_promotion",
fields: ["cart_id", "promotion_id"],
variables: {
cart_id: [id],
},
})
@@ -2,7 +2,7 @@ import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
/**
* The remote query's details.
@@ -164,7 +164,7 @@ export const useRemoteQueryStep = createStep(
: undefined,
}
const entities = await query(queryObject, undefined, config)
const entities = await query(queryObjectConfig, config)
const result = list ? entities : entities[0]
return new StepResponse(result)
@@ -6,7 +6,6 @@ import {
LINKS,
Modules,
promiseAll,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export type SetShippingOptionsPriceSetsStepInput = {
@@ -28,16 +27,14 @@ async function getCurrentShippingOptionPriceSetsLinks(
shippingOptionIds: string[],
{ remoteQuery }: { remoteQuery: RemoteQueryFunction }
): Promise<LinkItems> {
const query = remoteQueryObjectFromString({
const shippingOptionPriceSetLinks = (await remoteQuery({
service: LINKS.ShippingOptionPriceSet,
variables: {
filters: { shipping_option_id: shippingOptionIds },
take: null,
},
fields: ["shipping_option_id", "price_set_id"],
})
const shippingOptionPriceSetLinks = (await remoteQuery(query)) as {
} as any)) as {
shipping_option_id: string
price_set_id: string
}[]
@@ -10,12 +10,11 @@ import {
} from "@medusajs/types"
import {
ContainerRegistrationKeys,
isDefined,
LINKS,
ModuleRegistrationName,
isDefined,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
interface PriceRegionId {
region_id: string
@@ -33,16 +32,14 @@ async function getCurrentShippingOptionPrices(
): Promise<
{ shipping_option_id: string; price_set_id: string; prices: PriceDTO[] }[]
> {
const query = remoteQueryObjectFromString({
const shippingOptionPrices = (await remoteQuery({
service: LINKS.ShippingOptionPriceSet,
variables: {
filters: { shipping_option_id: shippingOptionIds },
take: null,
},
fields: ["shipping_option_id", "price_set_id", "price_set.prices.*"],
})
const shippingOptionPrices = (await remoteQuery(query)) as {
} as any)) as {
shipping_option_id: string
price_set_id: string
price_set: PriceSetDTO
@@ -3,9 +3,8 @@ import {
ContainerRegistrationKeys,
MedusaError,
ModuleRegistrationName,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export type FulfillmentProviderValidationWorkflowInput = {
id?: string
@@ -21,7 +20,10 @@ export const validateFulfillmentProvidersStepId =
*/
export const validateFulfillmentProvidersStep = createStep(
validateFulfillmentProvidersStepId,
async (input: FulfillmentProviderValidationWorkflowInput[], { container }) => {
async (
input: FulfillmentProviderValidationWorkflowInput[],
{ container }
) => {
const dataToValidate: {
service_zone_id: string
provider_id: string
@@ -83,7 +85,7 @@ export const validateFulfillmentProvidersStep = createStep(
)
}
const serviceZoneQuery = remoteQueryObjectFromString({
const serviceZones = await remoteQuery({
entryPoint: "service_zone",
fields: ["id", "fulfillment_set.locations.fulfillment_providers.id"],
variables: {
@@ -91,8 +93,6 @@ export const validateFulfillmentProvidersStep = createStep(
},
})
const serviceZones = await remoteQuery(serviceZoneQuery)
const serviceZonesMap = new Map<
string,
ServiceZoneDTO & {
@@ -1,8 +1,7 @@
import {
arrayDifference,
ContainerRegistrationKeys,
MedusaError,
arrayDifference,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { createStep } from "@medusajs/workflows-sdk"
@@ -17,13 +16,11 @@ export const validateInventoryItems = createStep(
ContainerRegistrationKeys.REMOTE_QUERY
)
const query = remoteQueryObjectFromString({
const items = await remoteQuery({
entryPoint: "inventory_item",
variables: { id },
fields: ["id"],
})
const items = await remoteQuery(query)
const diff = arrayDifference(
id,
items.map(({ id }) => id)
@@ -1,8 +1,7 @@
import {
arrayDifference,
ContainerRegistrationKeys,
MedusaError,
arrayDifference,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { InventoryTypes } from "@medusajs/types"
@@ -19,7 +18,7 @@ export const validateInventoryLocationsStep = createStep(
ContainerRegistrationKeys.REMOTE_QUERY
)
const locationQuery = remoteQueryObjectFromString({
const stockLocations = await remoteQuery({
entryPoint: "stock_location",
variables: {
id: data.map((d) => d.location_id),
@@ -27,8 +26,6 @@ export const validateInventoryLocationsStep = createStep(
fields: ["id"],
})
const stockLocations = await remoteQuery(locationQuery)
const diff = arrayDifference(
data.map((d) => d.location_id),
stockLocations.map((l) => l.id)
@@ -1,9 +1,5 @@
import {
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export const validateVariantPriceLinksStepId = "validate-variant-price-links"
/**
@@ -28,13 +24,11 @@ export const validateVariantPriceLinksStep = createStep(
.filter(Boolean)
.flat(1)
const variantPricingLinkQuery = remoteQueryObjectFromString({
const links = await remoteQuery({
entryPoint: "product_variant_price_set",
fields: ["variant_id", "price_set_id"],
variables: { variant_id: variantIds, take: null },
})
const links = await remoteQuery(variantPricingLinkQuery)
const variantPriceSetMap: Record<string, string> = {}
for (const link of links) {
@@ -1,8 +1,5 @@
import { FilterableProductProps, RemoteQueryFunction } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export type GetAllProductsStepInput = {
@@ -27,7 +24,7 @@ export const getAllProductsStep = createStep(
// We intentionally fetch the products serially here to avoid putting too much load on the DB
while (true) {
const remoteQueryObject = remoteQueryObjectFromString({
const { rows: products } = await remoteQuery({
entryPoint: "product",
variables: {
filters: data.filter,
@@ -36,8 +33,6 @@ export const getAllProductsStep = createStep(
},
fields: data.select,
})
const { rows: products } = await remoteQuery(remoteQueryObject)
allProducts.push(...products)
if (products.length < pageSize) {
@@ -1,16 +1,15 @@
import { RemoteLink } from "@medusajs/modules-sdk"
import { IPaymentModuleService, RemoteQueryFunction } from "@medusajs/types"
import {
arrayDifference,
ContainerRegistrationKeys,
LINKS,
MedusaError,
ModuleRegistrationName,
Modules,
arrayDifference,
promiseAll,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export interface SetRegionsPaymentProvidersStepInput {
input: {
@@ -64,16 +63,14 @@ async function getCurrentRegionPaymentProvidersLinks(
[Modules.PAYMENT]: { payment_provider_id: string }
}[]
> {
const query = remoteQueryObjectFromString({
const regionProviderLinks = (await remoteQuery({
service: LINKS.RegionPaymentProvider,
variables: {
filters: { region_id: regionIds },
take: null,
},
fields: ["region_id", "payment_provider_id"],
})
const regionProviderLinks = (await remoteQuery(query)) as {
} as any)) as {
region_id: string
payment_provider_id: string
}[]
+2
View File
@@ -27,6 +27,8 @@
"typescript": "^5.1.6"
},
"dependencies": {
"@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/typescript": "^4.0.9",
"@graphql-tools/merge": "^9.0.0",
"@graphql-tools/schema": "^10.0.0",
"@medusajs/orchestration": "^0.5.7",
+1
View File
@@ -5,3 +5,4 @@ export * from "./medusa-module"
export * from "./remote-link"
export * from "./remote-query"
export * from "./types"
export { gqlSchemaToTypes } from "./utils"
+81 -9
View File
@@ -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"
@@ -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"
+17
View File
@@ -0,0 +1,17 @@
module.exports = {
moduleNameMapper: {},
transform: {
"^.+\\.[jt]s$": [
"@swc/jest",
{
jsc: {
parser: { syntax: "typescript", decorators: true },
transform: { decoratorMetadata: true },
},
},
],
},
testEnvironment: `node`,
moduleFileExtensions: [`js`, `ts`],
modulePathIgnorePatterns: ["dist/"],
}
+2 -1
View File
@@ -24,6 +24,7 @@
"awilix": "^8.0.1",
"bignumber.js": "^9.1.2",
"cross-env": "^5.2.1",
"expect-type": "^0.20.0",
"ioredis": "^5.4.1",
"rimraf": "^5.0.1",
"typescript": "^5.1.6",
@@ -31,7 +32,7 @@
"winston": "^3.8.2"
},
"scripts": {
"build": "rimraf dist && tsc --build",
"build": "rimraf dist && tsc -p tsconfig.spec.json --noEmit && tsc -p tsconfig.build.json",
"watch": "tsc --build --watch",
"test": "exit 0"
}
@@ -0,0 +1,144 @@
// TODO: The intent is to manage fields picking from a object, not to act upon at the moment and just keeping it here for reference.
import { Prettify } from "./common"
type Split<S extends string, D extends string> = string extends S
? string[]
: S extends ""
? []
: S extends `${infer T}${D}${infer U}`
? [T, ...Split<U, D>]
: [S]
type NestedPickHelper<T, Path extends string[]> = Path extends [
infer First,
...infer Rest
]
? First extends keyof T
? Rest extends string[]
? Rest["length"] extends 0
? T[First]
: T[First] extends Array<infer Item>
? {
[K in keyof Item as Rest[0] extends "*"
? K
: K extends Rest[number]
? K
: never]: Item[K] extends object
? NestedPickHelper<Item[K], Rest>
: Item[K] extends Array<infer Item>
? NestedPickHelper<Item, Rest>
: Rest[0] extends "*"
? Item[K]
: K extends Rest[number]
? Item[K]
: never
}[]
: T[First] extends object
? {
[K in keyof T[First] as Rest[0] extends "*"
? K
: K extends Rest[number]
? K
: never]: T[First][K] extends object
? NestedPickHelper<T[First], Rest>
: T[First][K] extends Array<infer Item>
? NestedPickHelper<Item, Rest>
: Rest[0] extends "*"
? T[First][K]
: K extends Rest[number]
? T[First][K]
: never
}
: First extends "*"
? {
[K in keyof T]: T[K]
}
: {
[K in keyof T[First] as K extends Rest[number]
? K
: never]: NestedPickHelper<T[First], Rest>
}
: never
: First extends `${infer ArrayKey}[${infer Index}]`
? ArrayKey extends keyof T
? T[ArrayKey] extends (infer U)[]
? NestedPickHelper<U, Rest & string[]>
: never
: never
: First extends "*"
? T
: never
: T
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
x: infer I
) => void
? I
: never
export type NestedPickFirstIteration<T, Props extends string[]> = {
[P in Props[number] as Split<P, ".">[0] & string]: NestedPickHelper<
T,
Split<P, ".">
>
}
type NestedPick<T, Props extends string[]> = {
[K in keyof NestedPickFirstIteration<T, Props>]: Prettify<
NestedPickFirstIteration<T, Props>[K] extends Array<infer V>
? UnionToIntersection<V>[]
: UnionToIntersection<NestedPickFirstIteration<T, Props>[K]>
>
}
type Obj = {
id: string
title: string
variant: {
id: string
description: string
}
options: { id: string; value: string }[]
extra: {
detail: {
name: string
info: {
data: string
}
}
}
}
type Test = NestedPick<
Obj,
[
"id",
"variant.description",
"variant.id",
"options.id",
"options.value",
"extra.detail.info.data"
]
>
const test: Test = {
id: "test",
variant: {
description: "test",
id: "test",
},
options: [
{
id: "test",
value: "test",
},
],
extra: {
detail: {
info: {
data: "test",
},
},
},
}
@@ -0,0 +1,458 @@
export type Maybe<T> = T | null
export type InputMaybe<T> = Maybe<T>
export type Exact<T extends { [key: string]: unknown }> = {
[K in keyof T]: T[K]
}
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
[SubKey in K]?: Maybe<T[SubKey]>
}
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
[SubKey in K]: Maybe<T[SubKey]>
}
export type MakeEmpty<
T extends { [key: string]: unknown },
K extends keyof T
> = { [_ in K]?: never }
export type Incremental<T> =
| T
| {
[P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never
}
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string }
String: { input: string; output: string }
Boolean: { input: boolean; output: boolean }
Int: { input: number; output: number }
Float: { input: number; output: number }
DateTime: {
input: { output: "Date | string" }
output: { output: "Date | string" }
}
JSON: {
input: { output: "Record<any, unknown>" }
output: { output: "Record<any, unknown>" }
}
}
export type SimpleProduct = {
id: Scalars["ID"]["output"]
handle: string
title?: Scalars["String"]["output"]
variants?: Maybe<Array<Maybe<Pick<ProductVariant, "id">>>>
sales_channels_link?: Array<
Pick<LinkProductSalesChannel, "product_id" | "sales_channel_id">
>
sales_channels?: Array<Pick<SalesChannel, "id" | "name">>
}
export type Product = {
__typename?: "Product"
id: Scalars["ID"]["output"]
handle: Scalars["String"]["output"]
title: Scalars["String"]["output"]
description?: Scalars["String"]["output"]
variants?: Array<ProductVariant>
sales_channels_link?: Array<LinkProductSalesChannel>
sales_channels?: Array<SalesChannel>
}
export type ProductVariant = {
__typename?: "ProductVariant"
id: Scalars["ID"]["output"]
handle: Scalars["String"]["output"]
title: Scalars["String"]["output"]
product?: Maybe<Product>
}
export type ProductCategory = {
__typename?: "ProductCategory"
id: Scalars["ID"]["output"]
handle: Scalars["String"]["output"]
title?: Maybe<Scalars["String"]["output"]>
}
export type SalesChannel = {
__typename?: "SalesChannel"
id: Scalars["ID"]["output"]
name?: Maybe<Scalars["String"]["output"]>
description?: Maybe<Scalars["String"]["output"]>
created_at?: Maybe<Scalars["DateTime"]["output"]>
updated_at?: Maybe<Scalars["DateTime"]["output"]>
products_link?: Maybe<Array<Maybe<LinkProductSalesChannel>>>
api_keys_link?: Maybe<Array<Maybe<LinkPublishableApiKeySalesChannel>>>
locations_link?: Maybe<Array<Maybe<LinkSalesChannelStockLocation>>>
}
export type LinkCartPaymentCollection = {
__typename?: "LinkCartPaymentCollection"
cart_id: Scalars["String"]["output"]
payment_collection_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkCartPromotion = {
__typename?: "LinkCartPromotion"
cart_id: Scalars["String"]["output"]
promotion_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkLocationFulfillmentProvider = {
__typename?: "LinkLocationFulfillmentProvider"
stock_location_id: Scalars["String"]["output"]
fulfillment_provider_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkLocationFulfillmentSet = {
__typename?: "LinkLocationFulfillmentSet"
stock_location_id: Scalars["String"]["output"]
fulfillment_set_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkOrderCart = {
__typename?: "LinkOrderCart"
order_id: Scalars["String"]["output"]
cart_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkOrderFulfillment = {
__typename?: "LinkOrderFulfillment"
order_id: Scalars["String"]["output"]
fulfillment_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkOrderPaymentCollection = {
__typename?: "LinkOrderPaymentCollection"
order_id: Scalars["String"]["output"]
payment_collection_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkOrderPromotion = {
__typename?: "LinkOrderPromotion"
order_id: Scalars["String"]["output"]
promotion_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkReturnFulfillment = {
__typename?: "LinkReturnFulfillment"
return_id: Scalars["String"]["output"]
fulfillment_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkProductSalesChannel = {
__typename?: "LinkProductSalesChannel"
product_id: Scalars["String"]["output"]
sales_channel_id: Scalars["String"]["output"]
product?: Maybe<Product>
sales_channel?: Maybe<SalesChannel>
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkProductVariantInventoryItem = {
__typename?: "LinkProductVariantInventoryItem"
variant_id: Scalars["String"]["output"]
inventory_item_id: Scalars["String"]["output"]
required_quantity: Scalars["Int"]["output"]
variant?: Maybe<Product>
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkProductVariantPriceSet = {
__typename?: "LinkProductVariantPriceSet"
variant_id: Scalars["String"]["output"]
price_set_id: Scalars["String"]["output"]
variant?: Maybe<Product>
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkPublishableApiKeySalesChannel = {
__typename?: "LinkPublishableApiKeySalesChannel"
publishable_key_id: Scalars["String"]["output"]
sales_channel_id: Scalars["String"]["output"]
sales_channel?: Maybe<SalesChannel>
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkRegionPaymentProvider = {
__typename?: "LinkRegionPaymentProvider"
region_id: Scalars["String"]["output"]
payment_provider_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkSalesChannelStockLocation = {
__typename?: "LinkSalesChannelStockLocation"
sales_channel_id: Scalars["String"]["output"]
stock_location_id: Scalars["String"]["output"]
sales_channel?: Maybe<SalesChannel>
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export type LinkShippingOptionPriceSet = {
__typename?: "LinkShippingOptionPriceSet"
shipping_option_id: Scalars["String"]["output"]
price_set_id: Scalars["String"]["output"]
createdAt: Scalars["String"]["output"]
updatedAt: Scalars["String"]["output"]
deletedAt?: Maybe<Scalars["String"]["output"]>
}
export interface FixtureEntryPoints {
file: any
files: any
workflow_execution: any
workflow_executions: any
inventory_items: any
inventory_item: any
inventory: any
reservation: any
reservations: any
reservation_item: any
reservation_items: any
inventory_level: any
inventory_levels: any
stock_location_address: any
stock_location_addresses: any
stock_location: any
stock_locations: any
price_set: any
price_sets: any
price_list: any
price_lists: any
price: any
prices: any
price_preference: any
price_preferences: any
product_variant: ProductVariant
product_variants: ProductVariant
variant: ProductVariant
variants: ProductVariant
product: Product
products: Product
simple_product: SimpleProduct
product_option: any
product_options: any
product_type: any
product_types: any
product_image: any
product_images: any
product_tag: any
product_tags: any
product_collection: any
product_collections: any
product_category: ProductCategory
product_categories: ProductCategory
sales_channel: SalesChannel
sales_channels: SalesChannel
customer_address: any
customer_addresses: any
customer_group_customer: any
customer_group_customers: any
customer_group: any
customer_groups: any
customer: any
customers: any
cart: any
carts: any
address: any
addresses: any
line_item: any
line_items: any
line_item_adjustment: any
line_item_adjustments: any
line_item_tax_line: any
line_item_tax_lines: any
shipping_method: any
shipping_methods: any
shipping_method_adjustment: any
shipping_method_adjustments: any
shipping_method_tax_line: any
shipping_method_tax_lines: any
promotion: any
promotions: any
campaign: any
campaigns: any
promotion_rule: any
promotion_rules: any
api_key: any
api_keys: any
tax_rate: any
tax_rates: any
tax_region: any
tax_regions: any
tax_rate_rule: any
tax_rate_rules: any
tax_provider: any
tax_providers: any
store: any
stores: any
store_currency: any
store_currencies: any
user: any
users: any
invite: any
invites: any
auth_identity: any
auth_identities: any
order: any
orders: any
order_address: any
order_addresses: any
order_line_item: any
order_line_items: any
order_line_item_adjustment: any
order_line_item_adjustments: any
order_line_item_tax_line: any
order_line_item_tax_lines: any
order_shipping_method: any
order_shipping_methods: any
order_shipping_method_adjustment: any
order_shipping_method_adjustments: any
order_shipping_method_tax_line: any
order_shipping_method_tax_lines: any
order_transaction: any
order_transactions: any
order_change: any
order_changes: any
order_change_action: any
order_change_actions: any
order_item: any
order_items: any
order_summary: any
order_summaries: any
order_shipping: any
order_shippings: any
return_reason: any
return_reasons: any
return: any
returns: any
return_item: any
return_items: any
order_claim: any
order_claims: any
order_claim_item: any
order_claim_items: any
order_claim_item_image: any
order_claim_item_images: any
order_exchange: any
order_exchanges: any
order_exchange_item: any
order_exchange_items: any
payment: any
payments: any
payment_collection: any
payment_collections: any
payment_provider: any
payment_providers: any
payment_session: any
payment_sessions: any
refund_reason: any
refund_reasons: any
fulfillment_address: any
fulfillment_addresses: any
fulfillment_item: any
fulfillment_items: any
fulfillment_label: any
fulfillment_labels: any
fulfillment_provider: any
fulfillment_providers: any
fulfillment_set: any
fulfillment_sets: any
fulfillment: any
fulfillments: any
geo_zone: any
geo_zones: any
service_zone: any
service_zones: any
shipping_option_rule: any
shipping_option_rules: any
shipping_option_type: any
shipping_option_types: any
shipping_option: any
shipping_options: any
shipping_profile: any
shipping_profiles: any
notification: any
notifications: any
region: any
regions: any
country: any
countries: any
currency: any
currencies: any
cart_payment_collection: LinkCartPaymentCollection
cart_payment_collections: LinkCartPaymentCollection
cart_promotion: LinkCartPromotion
cart_promotions: LinkCartPromotion
location_fulfillment_provider: LinkLocationFulfillmentProvider
location_fulfillment_providers: LinkLocationFulfillmentProvider
location_fulfillment_set: LinkLocationFulfillmentSet
location_fulfillment_sets: LinkLocationFulfillmentSet
order_cart: LinkOrderCart
order_carts: LinkOrderCart
order_fulfillment: LinkOrderFulfillment
order_fulfillments: LinkOrderFulfillment
order_payment_collection: LinkOrderPaymentCollection
order_payment_collections: LinkOrderPaymentCollection
order_promotion: LinkOrderPromotion
order_promotions: LinkOrderPromotion
return_fulfillment: LinkReturnFulfillment
return_fulfillments: LinkReturnFulfillment
product_sales_channel: LinkProductSalesChannel
product_sales_channels: LinkProductSalesChannel
product_variant_inventory_item: LinkProductVariantInventoryItem
product_variant_inventory_items: LinkProductVariantInventoryItem
product_variant_price_set: LinkProductVariantPriceSet
product_variant_price_sets: LinkProductVariantPriceSet
publishable_api_key_sales_channel: LinkPublishableApiKeySalesChannel
publishable_api_key_sales_channels: LinkPublishableApiKeySalesChannel
region_payment_provider: LinkRegionPaymentProvider
region_payment_providers: LinkRegionPaymentProvider
sales_channel_location: LinkSalesChannelStockLocation
sales_channel_locations: LinkSalesChannelStockLocation
shipping_option_price_set: LinkShippingOptionPriceSet
shipping_option_price_sets: LinkShippingOptionPriceSet
}
declare module "../remote-query-entry-points" {
export interface RemoteQueryEntryPoints extends FixtureEntryPoints {}
}
@@ -0,0 +1,131 @@
import { ObjectToRemoteQueryFields } from "../object-to-remote-query-fields"
import { expectTypeOf } from "expect-type"
describe("ObjectToRemoteQueryFields", () => {
it("should return all the nested paths properties from an object", () => {
type Object = {
id: string
title: string
description: string
date: Date
variants: {
id: string
sku: string
title: string
}[]
sales_channel: {
id: string
name: string
value: string
}
metadata: Record<string, unknown>
}
type Paths = ObjectToRemoteQueryFields<Object>
expectTypeOf<Paths>().toEqualTypeOf<
| "*"
| "date"
| "metadata"
| "id"
| "title"
| "description"
| "variants.*"
| "variants.id"
| "variants.sku"
| "variants.title"
| "sales_channel.*"
| "sales_channel.id"
| "sales_channel.name"
| "sales_channel.value"
>()
})
it("should return all the nested paths properties from an object with nullable and undefined", () => {
type Maybe<T> = T | null
type Object = {
id: string
title: string
description: string
date: Date
variants: Maybe<
Maybe<{
id: string
sku: string
title: string
}>[]
>
sales_channel?: Maybe<{
id: string
name: string
value: string
}>
metadata: Record<string, unknown>
}
type Paths = ObjectToRemoteQueryFields<Object>
expectTypeOf<Paths>().toEqualTypeOf<
| "*"
| "id"
| "metadata"
| "date"
| "title"
| "description"
| "variants.*"
| "variants.id"
| "variants.sku"
| "variants.title"
| "sales_channel.*"
| "sales_channel.id"
| "sales_channel.name"
| "sales_channel.value"
>()
})
it("should fail return all the nested paths properties from an object with nullable and undefined", () => {
type Maybe<T> = T | null
type Object = {
id: string
title: string
description: string
date: Date
variants: Maybe<
Maybe<{
id: string
sku: string
title: string
}>[]
>
sales_channel?: Maybe<{
id: string
name: string
value: string
}>
metadata: Record<string, unknown>
}
type Paths = ObjectToRemoteQueryFields<Object>
expectTypeOf<Paths>().toEqualTypeOf<
// @ts-expect-error
| "foo"
| "*"
| "date"
| "metadata"
| "id"
| "title"
| "description"
| "variants.*"
| "variants.id"
| "variants.sku"
| "variants.title"
| "sales_channel.*"
| "sales_channel.id"
| "sales_channel.name"
| "sales_channel.value"
>()
})
})
@@ -0,0 +1,56 @@
import { expectTypeOf } from "expect-type"
import { RemoteQueryEntryPoints } from "../remote-query-entry-points"
import { FixtureEntryPoints } from "../__fixtures__/remote-query"
import { RemoteQueryObjectConfig } from "../remote-query-object-from-string"
describe("RemoteQuery", () => {
describe("RemoteQueryEntryPoints", () => {
it("should include declaration merging types with the defined entry points", () => {
expectTypeOf<RemoteQueryEntryPoints>().toMatchTypeOf<FixtureEntryPoints>()
})
})
describe("RemoteQueryObjectConfig", () => {
it("should return the correct type for fields when using a string entry point", () => {
type Result = RemoteQueryObjectConfig<"simple_product">["fields"]
expectTypeOf<Result>().toEqualTypeOf<
(
| "*"
| "id"
| "handle"
| "title"
| "variants.id"
| "variants.*"
| "sales_channels.id"
| "sales_channels.*"
| "sales_channels.name"
| "sales_channels_link.*"
| "sales_channels_link.product_id"
| "sales_channels_link.sales_channel_id"
)[]
>()
})
it("should fail return the correct type for fields when using a string entry point", () => {
type Result = RemoteQueryObjectConfig<"simple_product">["fields"]
expectTypeOf<Result>().toEqualTypeOf<
// @ts-expect-error
(
| "*"
| "foo"
| "id"
| "handle"
| "title"
| "variants.id"
| "variants.*"
| "sales_channels.id"
| "sales_channels.*"
| "sales_channels.name"
| "sales_channels_link.*"
| "sales_channels_link.product_id"
| "sales_channels_link.sales_channel_id"
)[]
>()
})
})
})
+9 -12
View File
@@ -1,19 +1,22 @@
import {
JoinerRelationship,
JoinerServiceConfig,
RemoteJoinerOptions,
RemoteJoinerQuery,
} from "../joiner"
import { JoinerRelationship, JoinerServiceConfig } from "../joiner"
import { MedusaContainer } from "../common"
import { RepositoryService } from "../dal"
import { Logger } from "../logger"
import {
RemoteQueryObjectConfig,
RemoteQueryObjectFromStringResult,
} from "./remote-query-object-from-string"
export { RemoteQueryObjectConfig, RemoteQueryObjectFromStringResult }
export type Constructor<T> = new (...args: any[]) => T | (new () => T)
export * from "../common/medusa-container"
export * from "./medusa-internal-service"
export * from "./module-provider"
export * from "./remote-query"
export * from "./remote-query-entry-points"
export type LogLevel =
| "query"
@@ -292,12 +295,6 @@ export type ModuleBootstrapDeclaration =
// | ModuleServiceInitializeOptions
// | ModuleServiceInitializeCustomDataLayerOptions
export type RemoteQueryFunction = (
query: string | RemoteJoinerQuery | object,
variables?: Record<string, unknown>,
options?: RemoteJoinerOptions
) => Promise<any> | null
export interface IModuleService {
/**
* @ignore
@@ -0,0 +1,59 @@
type Marker = [never, 0, 1, 2, 3, 4]
type ExcludedProps = ["__typename"]
type SpecialNonRelationProps = ["metadata"]
type RawBigNumberPrefix = "raw_"
type ExpandStarSelector<
T extends object,
Depth extends number,
Exclusion extends string[]
> = ObjectToRemoteQueryFields<T & { "*": "*" }, Depth, Exclusion>
type TypeOnly<T> = Required<Exclude<T, null | undefined>>
/**
* Output an array of strings representing the path to each leaf node in an object
*/
export type ObjectToRemoteQueryFields<
MaybeT,
Depth extends number = 2,
Exclusion extends string[] = [],
T = TypeOnly<MaybeT> & { "*": "*" }
> = Depth extends never
? never
: T extends object
? {
[K in keyof T]: K extends // handle big number
`${RawBigNumberPrefix}${string}`
? Exclude<K, symbol>
: // handle metadata
K extends SpecialNonRelationProps[number]
? Exclude<K, symbol>
: // Special props that should be excluded
K extends ExcludedProps[number]
? never
: // Prevent recursive reference to itself
K extends Exclusion[number]
? never
: TypeOnly<T[K]> extends Array<infer R>
? TypeOnly<R> extends Date
? Exclude<K, symbol>
: TypeOnly<R> extends object
? `${Exclude<K, symbol>}.${ExpandStarSelector<
TypeOnly<R>,
Marker[Depth],
[K & string, ...Exclusion]
>}`
: never
: TypeOnly<T[K]> extends Date
? Exclude<K, symbol>
: TypeOnly<T[K]> extends object
? `${Exclude<K, symbol>}.${ExpandStarSelector<
TypeOnly<T[K]>,
Marker[Depth],
[K & string, ...Exclusion]
>}`
: Exclude<K, symbol>
}[keyof T]
: never
@@ -0,0 +1,4 @@
/**
* Bucket filled with map of entry point -> types that are autogenerated by the CLI
*/
export interface RemoteQueryEntryPoints {}
@@ -0,0 +1,22 @@
import { RemoteQueryEntryPoints } from "./remote-query-entry-points"
import { ObjectToRemoteQueryFields } from "./object-to-remote-query-fields"
export type RemoteQueryObjectConfig<TEntry extends string> = {
// service: string This property is still supported under the hood but part of the type due to types missmatch towards fields
entryPoint: TEntry | keyof RemoteQueryEntryPoints
variables?: any
fields: ObjectToRemoteQueryFields<
RemoteQueryEntryPoints[TEntry & keyof RemoteQueryEntryPoints]
> extends never
? string[]
: ObjectToRemoteQueryFields<
RemoteQueryEntryPoints[TEntry & keyof RemoteQueryEntryPoints]
>[]
}
export type RemoteQueryObjectFromStringResult<
TConfig extends RemoteQueryObjectConfig<any>
> = {
__TConfig: TConfig
__value: object
}
@@ -0,0 +1,83 @@
import { RemoteJoinerOptions, RemoteJoinerQuery } from "../joiner"
import {
RemoteQueryObjectConfig,
RemoteQueryObjectFromStringResult,
} from "./remote-query-object-from-string"
/*type ExcludedProps = "__typename"*/
export type RemoteQueryFunctionReturnPagination = {
skip: number
take: number
count: number
}
/*export type RemoteQueryReturnedData<TEntry extends string> =
TEntry extends keyof RemoteQueryEntryPoints
? Prettify<Omit<RemoteQueryEntryPoints[TEntry], ExcludedProps>>
: any*/
/*export type NarrowRemoteFunctionReturnType<
TConfig extends RemoteQueryObjectConfig<any>
> = TConfig extends RemoteQueryObjectConfig<infer TEntry>
? TConfig extends { variables: infer Variables }
? Variables extends { skip: number }
? {
rows: RemoteQueryReturnedData<TEntry>[]
metadata: RemoteQueryFunctionReturnPagination
}
: Variables extends { skip?: number | undefined } | { skip?: number }
? // TODO: the real type is the one in parenthsis but we put any for now as the current API is broken and need fixin in a separate iteration (RemoteQueryReturnedData<TEntry>[] | {rows: RemoteQueryReturnedData<TEntry>[] metadata: RemoteQueryFunctionReturnPagination })
any
: RemoteQueryReturnedData<TEntry>[]
: RemoteQueryReturnedData<TEntry>[]
: never*/
/*export type RemoteQueryFunctionReturnType<
TConfig extends
| RemoteQueryObjectConfig<any>
| RemoteQueryObjectFromStringResult<any>
> = TConfig extends RemoteQueryObjectFromStringResult<any>
? NarrowRemoteFunctionReturnType<TConfig['__value']>
: TConfig extends RemoteQueryObjectConfig<any>
? NarrowRemoteFunctionReturnType<TConfig>
: never*/
export type RemoteQueryFunction = {
/**
* Query wrapper to provide specific API's and pre processing around remoteQuery.query
* @param queryConfig
* @param options
*/
<const TEntry extends string>(
queryConfig: RemoteQueryObjectConfig<TEntry>,
options?: RemoteJoinerOptions
): Promise<any>
/**
* Query wrapper to provide specific API's and pre processing around remoteQuery.query
* @param queryConfig
* @param options
*/
<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
*/
(query: RemoteJoinerQuery, options?: RemoteJoinerOptions): Promise<any>
/**
* Query wrapper to provide specific GraphQL like API around remoteQuery.query
* @param query
* @param variables
* @param options
*/
gql: (
query: string,
variables?: Record<string, unknown>,
options?: RemoteJoinerOptions
) => Promise<any>
}
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"include": ["src"],
"exclude": [
"dist",
"src/**/__tests__",
"src/**/__mocks__",
"src/**/__fixtures__",
"node_modules"
],
}
-2
View File
@@ -21,8 +21,6 @@
"include": ["src"],
"exclude": [
"dist",
"./src/**/__tests__",
"./src/**/__mocks__",
"node_modules"
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"include": ["src", "integration-tests"],
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"sourceMap": true
}
}
@@ -0,0 +1,46 @@
import { deepMerge } from "../deep-merge"
describe("Deep merge ", function () {
it("should merge 2 objects", () => {
const a = {
x: 1,
y: {
z: 3,
w: 4,
a: {
b: 1,
},
},
}
const aCopy = JSON.parse(JSON.stringify(a))
const b = {
y: {
w: 5,
q: 6,
a: {
c: 14,
},
},
z: 2,
}
const bCopy = JSON.parse(JSON.stringify(b))
const merged = deepMerge(a, b)
expect(merged).toEqual({
x: 1,
y: {
z: 3,
w: 5,
a: {
b: 1,
c: 14,
},
q: 6,
},
z: 2,
})
expect(a).toEqual(aCopy)
expect(b).toEqual(bCopy)
})
})
@@ -45,7 +45,7 @@ describe("remoteQueryObjectFromString", function () {
fields,
})
expect(output).toEqual({
expect(output.__value).toEqual({
product: {
__args: {
q: "name",
@@ -101,9 +101,9 @@ describe("remoteQueryObjectFromString", function () {
service: "product",
variables: {},
fields,
})
} as any)
expect(output).toEqual({
expect(output.__value).toEqual({
product: {
__args: {},
fields: [
@@ -0,0 +1,26 @@
import { isObject } from "./is-object"
export function deepMerge(target: any, source: any) {
const recursive = (a, b) => {
if (!isObject(a)) {
return b
}
if (!isObject(b)) {
return a
}
Object.keys(b).forEach((key) => {
if (isObject(b[key])) {
a[key] ??= {}
a[key] = deepMerge(a[key], b[key])
} else {
a[key] = b[key]
}
})
return a
}
const copy = { ...target }
return recursive(copy, source)
}
@@ -1,17 +0,0 @@
import { MapToConfig } from "./map-object-to"
export function generateLinkableKeysMap(
linkableKeys: Record<string, string>
): MapToConfig {
const entityLinkableKeysMap: MapToConfig = {}
Object.entries(linkableKeys).forEach(([key, value]) => {
entityLinkableKeysMap[value] ??= []
entityLinkableKeysMap[value].push({
mapTo: key,
valueFrom: key.split("_").pop()!,
})
})
return entityLinkableKeysMap
}
+5 -5
View File
@@ -12,10 +12,12 @@ export * from "./deduplicate"
export * from "./deep-copy"
export * from "./deep-equal-obj"
export * from "./deep-flat-map"
export * from "./deep-merge"
export * from "./define-config"
export * from "./env-editor"
export * from "./errors"
export * from "./file-system"
export * from "./generate-entity-id"
export * from "./generate-linkable-keys-map"
export * from "./get-caller-file-path"
export * from "./get-config-file"
export * from "./get-duplicates"
@@ -26,7 +28,6 @@ export * from "./get-set-difference"
export * from "./graceful-shutdown-server"
export * from "./group-by"
export * from "./handle-postgres-database-error"
export * from "./is-truthy"
export * from "./is-big-number"
export * from "./is-boolean"
export * from "./is-date"
@@ -35,10 +36,12 @@ export * from "./is-email"
export * from "./is-object"
export * from "./is-present"
export * from "./is-string"
export * from "./is-truthy"
export * from "./load-env"
export * from "./lower-case-first"
export * from "./map-object-to"
export * from "./medusa-container"
export * from "./normalize-import-path-with-source"
export * from "./object-from-string-path"
export * from "./object-to-string-path"
export * from "./optional-numeric-serializer"
@@ -68,6 +71,3 @@ export * from "./trim-zeros"
export * from "./upper-case-first"
export * from "./validate-handle"
export * from "./wrap-handler"
export * from "./define-config"
export * from "./env-editor"
export * from "./normalize-import-path-with-source"
@@ -1,3 +1,7 @@
import {
RemoteQueryObjectConfig,
RemoteQueryObjectFromStringResult,
} from "@medusajs/types"
import { isObject } from "./is-object"
/**
@@ -83,20 +87,18 @@ import { isObject } from "./is-object"
* // },
* // }
*/
export function remoteQueryObjectFromString(
config:
| {
entryPoint: string
variables?: any
fields: string[]
}
| {
service: string
variables?: any
fields: string[]
}
): object {
const { entryPoint, service, variables, fields } = {
export function remoteQueryObjectFromString<
const TEntry extends string,
const TConfig extends RemoteQueryObjectConfig<TEntry>
>(
config: TConfig | RemoteQueryObjectConfig<TEntry>
): RemoteQueryObjectFromStringResult<TConfig> {
const {
entryPoint,
service,
variables = {},
fields = [],
} = {
...config,
entryPoint: "entryPoint" in config ? config.entryPoint : undefined,
service: "service" in config ? config.service : undefined,
@@ -114,12 +116,13 @@ export function remoteQueryObjectFromString(
const usedVariables = new Set()
for (const field of fields) {
if (!field.includes(".")) {
const fieldAsString = field as string
if (!fieldAsString.includes(".")) {
remoteJoinerConfig[entryKey]["fields"].push(field)
continue
}
const fieldSegments = field.split(".")
const fieldSegments = fieldAsString.split(".")
const fieldProperty = fieldSegments.pop()
let combinedPath = ""
@@ -151,5 +154,7 @@ export function remoteQueryObjectFromString(
remoteJoinerConfig[entryKey]["__args"] = topLevelArgs ?? {}
return remoteJoinerConfig
return {
__value: remoteJoinerConfig,
} as RemoteQueryObjectFromStringResult<TConfig>
}
@@ -51,7 +51,7 @@ export function defineJoinerConfig(
alias?: JoinerServiceConfigAlias[]
schema?: string
models?: DmlEntity<any, any>[] | { name: string }[]
linkableKeys?: Record<string, string>
linkableKeys?: ModuleJoinerConfig["linkableKeys"]
primaryKeys?: string[]
} = {}
): Omit<
@@ -151,18 +151,19 @@ export function defineJoinerConfig(
schema = toGraphQLSchema([...modelDefinitions.values()])
}
if (!linkableKeys) {
const linkableKeysFromDml = buildLinkableKeysFromDmlObjects([
...modelDefinitions.values(),
])
const linkableKeysFromMikroOrm = buildLinkableKeysFromMikroOrmObjects([
...mikroOrmObjects.values(),
])
linkableKeys = {
...linkableKeysFromDml,
...linkableKeysFromMikroOrm,
}
const linkableKeysFromDml = buildLinkableKeysFromDmlObjects([
...modelDefinitions.values(),
])
const linkableKeysFromMikroOrm = buildLinkableKeysFromMikroOrmObjects([
...mikroOrmObjects.values(),
])
const mergedLinkableKeys = {
...linkableKeysFromDml,
...linkableKeysFromMikroOrm,
...linkableKeys,
}
linkableKeys = mergedLinkableKeys
if (!primaryKeys && modelDefinitions.size) {
const linkConfig = buildLinkConfigFromModelObjects(
@@ -370,6 +371,7 @@ export function buildLinkConfigFromModelObjects<
const linkableKeyName =
parsedProperty.dataType.options?.linkable ??
`${camelToSnakeCase(model.name).toLowerCase()}_${property}`
modelLinkConfig[property] = {
linkable: linkableKeyName,
primaryKey: property,
@@ -397,21 +399,24 @@ export function buildLinkConfigFromLinkableKeys<
for (const [linkable, modelName] of Object.entries(linkableKeys)) {
const kebabCasedModelName = camelToSnakeCase(toCamelCase(modelName))
const inferredReferenceProperty = linkable.replace(
`${kebabCasedModelName}_`,
""
)
const keyName = lowerCaseFirst(modelName)
const config = {
linkable: linkable,
primaryKey: inferredReferenceProperty,
serviceName,
field: lowerCaseFirst(modelName),
field: keyName,
}
linkConfig[lowerCaseFirst(modelName)] = {
[inferredReferenceProperty]: config,
linkConfig[keyName] ??= {
toJSON: () => config,
}
linkConfig[keyName][inferredReferenceProperty] = config
}
return linkConfig as Record<string, any>
@@ -432,6 +437,5 @@ export function buildModelsNameToLinkableKeysMap(
valueFrom: key.split("_").pop()!,
})
})
return entityLinkableKeysMap
}
@@ -35,12 +35,12 @@ export function Module<
} {
const modelObjects = service[MedusaServiceModelObjectsSymbol] ?? {}
const defaultJoinerConfig = defineJoinerConfig(serviceName, {
models: Object.keys(modelObjects).length
? Object.values(modelObjects)
: undefined,
})
service.prototype.__joinerConfig ??= () => defaultJoinerConfig
service.prototype.__joinerConfig ??= () =>
defineJoinerConfig(serviceName, {
models: Object.keys(modelObjects).length
? Object.values(modelObjects)
: undefined,
})
let linkable = {} as Linkable