chore: query graph api (#9125)

CLOSES: FRMW-2704

**What**
Re-structure the Query graph API as well as introduce dynamic typing from schemas on the filters and better handling of relation treatment for fields/filters inference

Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
Carlos R. L. Rodrigues
2024-09-16 11:32:44 +00:00
committed by GitHub
co-authored by Adrien de Peretti
parent 3e97a64b21
commit 8829f89402
21 changed files with 2164 additions and 52 deletions
@@ -0,0 +1,454 @@
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: Date | string; output: Date | string }
JSON: { input: Record<string, unknown>; output: Record<string, 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>
metadata?: Maybe<Scalars["JSON"]["output"]>
}
export type ProductVariant = {
__typename?: "ProductVariant"
id: Scalars["ID"]["output"]
handle: Scalars["String"]["output"]
title: Scalars["String"]["output"]
sku: 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 "@medusajs/types" {
export interface RemoteQueryEntryPoints extends FixtureEntryPoints {}
}
@@ -0,0 +1,218 @@
import { QueryContext, QueryFilter } from "@medusajs/utils"
import "../__fixtures__/remote-query-type"
import { toRemoteQuery } from "../to-remote-query"
describe("toRemoteQuery", () => {
it("should transform a query with top level filtering", () => {
const format = toRemoteQuery({
entity: "product",
fields: ["id", "handle", "description"],
filters: QueryFilter<"product">({
handle: {
$ilike: "abc%",
},
}),
})
expect(format).toEqual({
product: {
__fields: ["id", "handle", "description"],
__args: {
filters: {
handle: {
$ilike: "abc%",
},
},
},
},
})
})
it("should transform a query with pagination", () => {
const format = toRemoteQuery({
entity: "product",
fields: ["id", "handle", "description"],
pagination: {
skip: 5,
take: 10,
},
})
expect(format).toEqual({
product: {
__fields: ["id", "handle", "description"],
__args: {
skip: 5,
take: 10,
},
},
})
})
it("should transform a query with top level filtering and pagination", () => {
const format = toRemoteQuery({
entity: "product",
fields: ["id", "handle", "description"],
pagination: {
skip: 5,
take: 10,
},
filters: QueryFilter<"product">({
handle: {
$ilike: "abc%",
},
}),
})
expect(format).toEqual({
product: {
__fields: ["id", "handle", "description"],
__args: {
skip: 5,
take: 10,
filters: {
handle: {
$ilike: "abc%",
},
},
},
},
})
})
it("should transform a query with filters and context into remote query input [1]", () => {
const format = toRemoteQuery({
entity: "product",
fields: [
"id",
"description",
"variants.title",
"variants.calculated_price",
"variants.options.*",
],
filters: {
variants: QueryFilter<"variants">({
sku: {
$ilike: "abc%",
},
}),
},
context: {
variants: {
calculated_price: QueryContext({
region_id: "reg_123",
currency_code: "usd",
}),
},
},
})
expect(format).toEqual({
product: {
__fields: ["id", "description"],
variants: {
__args: {
filters: {
sku: {
$ilike: "abc%",
},
},
},
calculated_price: {
__args: {
context: {
region_id: "reg_123",
currency_code: "usd",
},
},
},
__fields: ["title", "calculated_price"],
options: {
__fields: ["*"],
},
},
},
})
})
it("should transform a query with filters and context into remote query input [2]", () => {
const langContext = QueryContext({
context: {
lang: "pt-br",
},
})
const format = toRemoteQuery({
entity: "product",
fields: [
"id",
"title",
"description",
"product_translation.*",
"categories.*",
"categories.category_translation.*",
"variants.*",
"variants.variant_translation.*",
],
filters: QueryFilter<"product">({
id: "prod_01J742X0QPFW3R2ZFRTRC34FS8",
}),
context: {
product_translation: langContext,
categories: {
category_translation: langContext,
},
variants: {
variant_translation: langContext,
},
},
})
expect(format).toEqual({
product: {
__fields: ["id", "title", "description"],
__args: {
filters: {
id: "prod_01J742X0QPFW3R2ZFRTRC34FS8",
},
},
product_translation: {
__args: {
context: {
context: {
lang: "pt-br",
},
},
},
__fields: ["*"],
},
categories: {
category_translation: {
__args: {
context: {
context: {
lang: "pt-br",
},
},
},
__fields: ["*"],
},
__fields: ["*"],
},
variants: {
variant_translation: {
__args: {
context: {
context: {
lang: "pt-br",
},
},
},
__fields: ["*"],
},
__fields: ["*"],
},
},
})
})
})
@@ -1,10 +1,10 @@
import { RemoteQuery } from "./remote-query"
import {
GraphResultSet,
RemoteJoinerOptions,
RemoteJoinerQuery,
RemoteQueryFunction,
RemoteQueryFunctionReturnPagination,
RemoteQueryInput,
RemoteQueryObjectConfig,
RemoteQueryObjectFromStringResult,
} from "@medusajs/types"
@@ -13,6 +13,8 @@ import {
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { RemoteQuery } from "./remote-query"
import { toRemoteQuery } from "./to-remote-query"
/**
* API wrapper around the remoteQuery
@@ -25,7 +27,7 @@ export class Query {
*/
static traceGraphQuery?: (
queryFn: () => Promise<any>,
queryOptions: RemoteQueryObjectConfig<any>
queryOptions: RemoteQueryInput<any>
) => Promise<any>
/**
@@ -58,14 +60,16 @@ export class Query {
#unwrapQueryConfig(
config:
| RemoteQueryObjectConfig<any>
| RemoteQueryObjectFromStringResult<any>
| RemoteQueryObjectConfig<any>
| RemoteJoinerQuery
): object {
let normalizedQuery: any = config
if ("__value" in config) {
normalizedQuery = config.__value
} else if ("entity" in normalizedQuery) {
normalizedQuery = toRemoteQuery(normalizedQuery)
} else if (
"entryPoint" in normalizedQuery ||
"service" in normalizedQuery
@@ -95,6 +99,7 @@ export class Query {
async query(
queryOptions:
| RemoteQueryInput<any>
| RemoteQueryObjectConfig<any>
| RemoteQueryObjectFromStringResult<any>
| RemoteJoinerQuery,
@@ -110,7 +115,7 @@ export class Query {
const config = this.#unwrapQueryConfig(queryOptions)
if (Query.traceRemoteQuery) {
return await Query.traceRemoteQuery(
async () => this.#remoteQuery.query(config, undefined, options),
async () => await this.#remoteQuery.query(config, undefined, options),
queryOptions
)
}
@@ -133,10 +138,10 @@ export class Query {
* returns a result set
*/
async graph<const TEntry extends string>(
queryOptions: RemoteQueryObjectConfig<TEntry>,
queryOptions: RemoteQueryInput<TEntry>,
options?: RemoteJoinerOptions
): Promise<GraphResultSet<TEntry>> {
const normalizedQuery = remoteQueryObjectFromString(queryOptions).__value
const normalizedQuery = toRemoteQuery<TEntry>(queryOptions)
let response:
| any[]
| { rows: any[]; metadata: RemoteQueryFunctionReturnPagination }
@@ -148,8 +153,8 @@ export class Query {
if (Query.traceGraphQuery) {
response = await Query.traceGraphQuery(
async () =>
this.#remoteQuery.query(normalizedQuery, undefined, options),
queryOptions
await this.#remoteQuery.query(normalizedQuery, undefined, options),
queryOptions as RemoteQueryInput<any>
)
} else {
response = await this.#remoteQuery.query(
@@ -167,7 +172,7 @@ export class Query {
* API wrapper around the remoteQuery with backward compatibility support
* @param remoteQuery
*/
export function createQuery(remoteQuery: RemoteQuery): RemoteQueryFunction {
export function createQuery(remoteQuery: RemoteQuery) {
const query = new Query(remoteQuery)
function backwardCompatibleQuery(...args: any[]) {
@@ -177,5 +182,5 @@ export function createQuery(remoteQuery: RemoteQuery): RemoteQueryFunction {
backwardCompatibleQuery.graph = query.graph.bind(query)
backwardCompatibleQuery.gql = query.gql.bind(query)
return backwardCompatibleQuery
return backwardCompatibleQuery as Omit<RemoteQueryFunction, symbol>
}
@@ -0,0 +1,120 @@
import {
RemoteQueryEntryPoints,
RemoteQueryFilters,
RemoteQueryGraph,
RemoteQueryObjectConfig,
} from "@medusajs/types"
import { QueryContext, QueryFilter, isObject } from "@medusajs/utils"
const FIELDS = "__fields"
const ARGUMENTS = "__args"
/**
* convert a specific API configuration to a remote query object
*
* @param config
*
* @example
* const remoteQueryObject = toRemoteQuery({
* entity: "product",
* fields,
* filters: { variants: QueryFilter({ sku: "abc" }) },
* context: {
* variants: { calculated_price: QueryContext({ region_id: "reg_123" }) }
* }
* });
*
* console.log(remoteQueryObject);
*/
export function toRemoteQuery<const TEntity extends string>(config: {
entity: TEntity | keyof RemoteQueryEntryPoints
fields: RemoteQueryObjectConfig<TEntity>["fields"]
filters?: RemoteQueryFilters<TEntity>
pagination?: {
skip?: number
take?: number
}
context?: Record<string, any>
}): RemoteQueryGraph<TEntity> {
const { entity, fields = [], filters = {}, context = {} } = config
const joinerQuery: Record<string, any> = {
[entity]: {
__fields: [],
},
}
function processNestedObjects(
target: any,
source: Record<string, any>,
topLevel = true
) {
for (const key in source) {
const src = topLevel ? source : source[key]
if (!isObject(src)) {
target[key] = src
continue
}
if (QueryContext.isQueryContext(src) || QueryFilter.isQueryFilter(src)) {
const normalizedFilters = { ...src } as any
delete normalizedFilters.__type
const prop = QueryFilter.isQueryFilter(src) ? "filters" : "context"
if (topLevel) {
target[ARGUMENTS] ??= {}
target[ARGUMENTS][prop] = normalizedFilters
} else {
target[key] ??= {}
target[key][ARGUMENTS] ??= {}
target[key][ARGUMENTS][prop] = normalizedFilters
}
} else {
if (!topLevel) {
target[key] ??= {}
}
const nextTarget = topLevel ? target : target[key]
processNestedObjects(nextTarget, src, false)
}
}
}
// Process filters and context recursively
processNestedObjects(joinerQuery[entity], filters)
processNestedObjects(joinerQuery[entity], context)
for (const field of fields) {
const fieldAsString = field as string
if (!fieldAsString.includes(".")) {
joinerQuery[entity][FIELDS].push(field)
continue
}
const fieldSegments = fieldAsString.split(".")
const fieldProperty = fieldSegments.pop()
let combinedPath = ""
const deepConfigRef = fieldSegments.reduce((acc, curr) => {
combinedPath = combinedPath ? combinedPath + "." + curr : curr
acc[curr] ??= {}
return acc[curr]
}, joinerQuery[entity])
deepConfigRef[FIELDS] ??= []
deepConfigRef[FIELDS].push(fieldProperty)
}
if (config.pagination) {
joinerQuery[entity][ARGUMENTS] ??= {}
joinerQuery[entity][ARGUMENTS] = {
...joinerQuery[entity][ARGUMENTS],
...config.pagination,
}
}
return joinerQuery as RemoteQueryGraph<TEntity>
}