breaking: move shared HTTP utils to the framework (#9402)

Fixes: FRMW-2728, FRMW-2729

After this PR gets merged the following middleware will be exported from the `@medusajs/framework/http` import path.

- applyParamsAsFilters
- clearFiltersByKey
- applyDefaultFilters
- setContext
- getQueryConfig
- httpCompression
- maybeApplyLinkFilter
- refetchEntities
- unlessPath
- validateBody
- validateQuery

Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
Harminder VirkandAdrien de Peretti authored and GitHub committed 2024-10-03 09:42:00 +00:00
1 parent 193f93464f
commit 48e00169d2
557 files changed
+2365 -3499

No files matched your search

@@ -4,4 +4,3 @@ export * from "./link-sales-channels-to-publishable-key"
export * from "./revoke-api-keys"
export * from "./update-api-keys"
export * from "./validate-sales-channel-exists"
@@ -3,4 +3,3 @@ export * from "./delete-api-keys"
export * from "./link-sales-channels-to-publishable-key"
export * from "./revoke-api-keys"
export * from "./update-api-keys"
@@ -1,3 +1,2 @@
export * from "./steps"
export * from "./workflows"
@@ -1,3 +1,2 @@
export * from "./steps"
export * from "./workflows"
@@ -28,4 +28,3 @@ export * from "./update-line-items"
export * from "./validate-cart-payments"
export * from "./validate-cart-shipping-options"
export * from "./validate-variant-prices"
@@ -83,7 +83,8 @@ export const updateTaxLinesWorkflow = createWorkflow(
transform({ input, cart }, (data) => ({
cart: data.cart,
items: data.input.items || data.cart.items,
shipping_methods: data.input.shipping_methods || data.cart.shipping_methods,
shipping_methods:
data.input.shipping_methods || data.cart.shipping_methods,
force_tax_calculation: data.input.force_tax_calculation,
}))
)
@@ -6,4 +6,3 @@ export * from "./delete-customers"
export * from "./remove-customer-account"
export * from "./update-addresses"
export * from "./update-customers"
@@ -15,4 +15,3 @@ export * from "./update-fulfillment"
export * from "./update-shipping-profiles"
export * from "./upsert-shipping-options"
export * from "./validate-shipment"
@@ -1,3 +1,2 @@
export * from "./capture-payment"
export * from "./refund-payment"
@@ -1,3 +1,2 @@
export * from "./steps"
export * from "./workflows"
@@ -1,3 +1,2 @@
export * from "./steps"
export * from "./workflows"
@@ -3,4 +3,3 @@ export * from "./create-users"
export * from "./delete-users"
export * from "./remove-user-account"
export * from "./update-users"
+1 -1
View File
@@ -59,7 +59,6 @@
"@mikro-orm/postgresql": "5.9.7",
"@swc/core": "^1.7.28",
"@swc/jest": "^0.2.36",
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^8.5.9",
"awilix": "^8.0.1",
"ioredis": "^5.4.1",
@@ -78,6 +77,7 @@
"@medusajs/utils": "^1.11.9",
"@medusajs/workflows-sdk": "^0.1.6",
"@opentelemetry/api": "^1.9.0",
"@types/express": "^4.17.17",
"connect-redis": "5.2.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
+3 -8
View File
@@ -1,18 +1,13 @@
import { createMedusaContainer } from "@medusajs/utils"
import { AwilixContainer, ResolveOptions } from "awilix"
import { TransformObjectMethodToAsync } from "@medusajs/types";
/**
* The following interface acts as a bucket that other modules or the
* utils package can fill using declaration merging
*/
export interface ModuleImplementations {}
import { ModuleImplementations } from "@medusajs/types"
/**
* The Medusa Container extends [Awilix](https://github.com/jeffijoe/awilix) to
* provide dependency injection functionalities.
*/
export type MedusaContainer<Cradle extends object = TransformObjectMethodToAsync<ModuleImplementations>> =
// export type MedusaContainer<Cradle extends object = TransformObjectMethodToAsync<ModuleImplementations>> =
export type MedusaContainer<Cradle extends object = ModuleImplementations> =
Omit<AwilixContainer, "resolve"> & {
resolve<K extends keyof Cradle>(
key: K,
@@ -0,0 +1,45 @@
import zod from "zod"
import { MedusaError } from "@medusajs/utils"
import { validateAndTransformBody } from "../utils/validate-body"
import { MedusaRequest, MedusaResponse } from "../types"
const createLinkBody = () => {
return zod.object({
add: zod.array(zod.string()).optional(),
remove: zod.array(zod.string()).optional(),
})
}
describe("validateAndTransformBody", () => {
afterEach(() => {
jest.clearAllMocks()
})
it("should pass additionalDataValidator to validator factory", async () => {
let mockRequest = {
query: {},
body: {},
} as MedusaRequest
const mockResponse = {} as MedusaResponse
const nextFunction = jest.fn()
mockRequest.additionalDataValidator = zod.object({
brand_id: zod.number(),
})
const validatorFactory = (schema?: Zod.ZodObject<any, any>) => {
return schema ? createLinkBody().merge(schema) : createLinkBody()
}
let middleware = validateAndTransformBody(validatorFactory)
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenCalledWith(
new MedusaError(
"invalid_data",
`Invalid request: Field 'brand_id' is required`
)
)
})
})
@@ -0,0 +1,725 @@
import z from "zod"
import { MedusaError } from "@medusajs/utils"
import { validateAndTransformQuery } from "../utils/validate-query"
import { MedusaRequest, MedusaResponse, MedusaNextFunction } from "../types"
export const createSelectParams = () => {
return z.object({
fields: z.string().optional(),
})
}
const createFindParams = ({
offset,
limit,
order,
}: {
offset?: number
limit?: number
order?: string
} = {}) => {
const selectParams = createSelectParams()
return selectParams.merge(
z.object({
offset: z.preprocess(
(val) => {
if (val && typeof val === "string") {
return parseInt(val)
}
return val
},
z
.number()
.optional()
.default(offset ?? 0)
),
limit: z.preprocess(
(val) => {
if (val && typeof val === "string") {
return parseInt(val)
}
return val
},
z
.number()
.optional()
.default(limit ?? 20)
),
order: order
? z.string().optional().default(order)
: z.string().optional(),
})
)
}
describe("validateAndTransformQuery", () => {
afterEach(() => {
jest.clearAllMocks()
})
it("should transform the input query", async () => {
let mockRequest = {
query: {},
} as MedusaRequest
const mockResponse = {} as MedusaResponse
const nextFunction: MedusaNextFunction = jest.fn()
const expectations = ({
offset,
limit,
inputOrder,
transformedOrder,
}: {
offset: number
limit: number
inputOrder: string | undefined
transformedOrder?: Record<string, "ASC" | "DESC">
relations?: string[]
}) => {
expect(mockRequest.validatedQuery).toEqual({
offset,
limit,
order: inputOrder,
})
expect(mockRequest.filterableFields).toEqual({})
expect(mockRequest.listConfig).toEqual({
take: limit,
skip: offset,
select: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
relations: [
"metadata",
"metadata.parent",
"metadata.children",
"metadata.product",
],
order: transformedOrder,
})
expect(mockRequest.remoteQueryConfig).toEqual({
fields: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
pagination: {
order: transformedOrder,
skip: offset,
take: limit,
},
})
}
let queryConfig: any = {
defaultFields: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
defaultRelations: [
"metadata",
"metadata.parent",
"metadata.children",
"metadata.product",
],
isList: true,
}
let middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expectations({
limit: 20,
offset: 0,
inputOrder: undefined,
})
mockRequest = {
query: {
limit: "10",
offset: "5",
order: "created_at",
},
} as unknown as MedusaRequest
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expectations({
limit: 10,
offset: 5,
inputOrder: "created_at",
transformedOrder: { created_at: "ASC" },
})
mockRequest = {
query: {
limit: "10",
offset: "5",
order: "created_at",
},
} as unknown as MedusaRequest
queryConfig = {
defaults: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
isList: true,
}
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expectations({
limit: 10,
offset: 5,
inputOrder: "created_at",
transformedOrder: { created_at: "ASC" },
})
})
it("should transform the input query taking into account the fields symbols (+,- or no symbol)", async () => {
let mockRequest = {
query: {
fields: "id",
},
} as unknown as MedusaRequest
const mockResponse = {} as MedusaResponse
const nextFunction: MedusaNextFunction = jest.fn()
let queryConfig: any = {
defaultFields: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
defaultRelations: [
"metadata",
"metadata.parent",
"metadata.children",
"metadata.product",
],
isList: true,
}
let middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(mockRequest.listConfig).toEqual(
expect.objectContaining({
select: ["id"],
})
)
mockRequest = {
query: {
fields: "+test_prop,-prop-test-something",
},
} as unknown as MedusaRequest
queryConfig = {
defaultFields: [
"id",
"prop-test-something",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
defaultRelations: [
"metadata",
"metadata.parent",
"metadata.children",
"metadata.product",
],
isList: true,
}
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(mockRequest.listConfig).toEqual(
expect.objectContaining({
select: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
"test_prop",
],
})
)
mockRequest = {
query: {
fields: "+test_prop,-updated_at",
},
} as unknown as MedusaRequest
queryConfig = {
defaults: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
isList: true,
}
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(mockRequest.listConfig).toEqual(
expect.objectContaining({
select: [
"id",
"created_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
"test_prop",
],
})
)
})
it(`should transform the input and manage the allowed fields and relations properly without error`, async () => {
let mockRequest = {
query: {
fields: "*product.variants,+product.id",
},
} as unknown as MedusaRequest
const mockResponse = {} as MedusaResponse
const nextFunction: MedusaNextFunction = jest.fn()
let queryConfig: any = {
defaults: [
"id",
"created_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
allowed: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
"product",
"product.variants",
],
isList: true,
}
let middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(mockRequest.listConfig).toEqual(
expect.objectContaining({
select: [
"id",
"created_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
"product.id",
],
relations: [
"metadata",
"metadata.parent",
"metadata.children",
"metadata.product",
"product",
"product.variants",
],
})
)
expect(mockRequest.remoteQueryConfig).toEqual(
expect.objectContaining({
fields: [
"id",
"created_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
"product.id",
"product.variants.*",
],
})
)
mockRequest = {
query: {
fields: "store.name",
},
} as unknown as MedusaRequest
queryConfig = {
defaultFields: [
"id",
"created_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
allowed: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
"product",
"product.variants",
"store.name",
],
isList: true,
}
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(mockRequest.listConfig).toEqual(
expect.objectContaining({
select: ["store.name", "id"],
relations: ["store"],
})
)
expect(mockRequest.remoteQueryConfig).toEqual(
expect.objectContaining({
fields: ["store.name", "id"],
})
)
})
it("should throw when attempting to transform the input if disallowed fields are requested", async () => {
let mockRequest = {
query: {
fields: "+test_prop",
},
} as unknown as MedusaRequest
const mockResponse = {} as MedusaResponse
const nextFunction: MedusaNextFunction = jest.fn()
let queryConfig: any = {
defaultFields: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
defaultRelations: [
"metadata",
"metadata.parent",
"metadata.children",
"metadata.product",
],
allowed: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
isList: true,
}
let middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenLastCalledWith(
new MedusaError(
MedusaError.Types.INVALID_DATA,
`Requested fields [test_prop] are not valid`
)
)
mockRequest = {
query: {
fields: "product",
},
} as unknown as MedusaRequest
queryConfig = {
defaultFields: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
defaultRelations: [
"metadata",
"metadata.parent",
"metadata.children",
"metadata.product",
],
allowed: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
isList: true,
}
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenLastCalledWith(
new MedusaError(
MedusaError.Types.INVALID_DATA,
`Requested fields [product] are not valid`
)
)
mockRequest = {
query: {
fields: "store",
},
} as unknown as MedusaRequest
queryConfig = {
defaultFields: [
"id",
"created_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
allowed: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
"product",
"product.variants",
"store.name",
],
isList: true,
}
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenLastCalledWith(
new MedusaError(
MedusaError.Types.INVALID_DATA,
`Requested fields [store] are not valid`
)
)
mockRequest = {
query: {
fields: "*product",
},
} as unknown as MedusaRequest
queryConfig = {
defaults: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
allowed: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
isList: true,
}
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenLastCalledWith(
new MedusaError(
MedusaError.Types.INVALID_DATA,
`Requested fields [product] are not valid`
)
)
mockRequest = {
query: {
fields: "*product.variants",
},
} as unknown as MedusaRequest
queryConfig = {
defaults: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
allowed: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
"product",
],
isList: true,
}
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenLastCalledWith(
new MedusaError(
MedusaError.Types.INVALID_DATA,
`Requested fields [product.variants] are not valid`
)
)
mockRequest = {
query: {
fields: "*product",
},
} as unknown as MedusaRequest
queryConfig = {
defaults: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
],
allowed: [
"id",
"created_at",
"updated_at",
"deleted_at",
"metadata.id",
"metadata.parent.id",
"metadata.children.id",
"metadata.product.id",
"product.title",
],
isList: true,
}
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenLastCalledWith(
new MedusaError(
MedusaError.Types.INVALID_DATA,
`Requested fields [product] are not valid`
)
)
})
})
@@ -2,4 +2,11 @@ export * from "./express-loader"
export * from "./router"
export * from "./types"
export * from "./middlewares"
export * from "./utils/http-compression"
export * from "./utils/validate-body"
export * from "./utils/validate-query"
export * from "./utils/get-query-config"
export * from "./utils/define-middlewares"
export * from "./utils/maybe-apply-link-filter"
export * from "./utils/refetch-entities"
export * from "./utils/unless-path"
@@ -0,0 +1,37 @@
import { isObject, isPresent } from "@medusajs/utils"
import { MedusaNextFunction, MedusaRequest } from "../types"
export function applyDefaultFilters<TFilter extends object>(
filtersToApply: TFilter
) {
return async (req: MedusaRequest, _, next: MedusaNextFunction) => {
for (const [filter, filterValue] of Object.entries(filtersToApply)) {
let valueToApply = filterValue
// If certain manipulations need to be done on a middleware level, we can provide a simple
// function that mutates the data based on any custom requirement
if (typeof filterValue === "function") {
// pass the actual filterable fields so that the function can mutate the original object.
// Currently we only need it to delete filter keys from the request filter object, but this could
// be used for other purposes. If we can't find other purposes, we can refactor to accept an array
// of strings to delete after filters have been applied.
valueToApply = filterValue(
req.filterableFields,
req.remoteQueryConfig.fields
)
}
// If the value to apply is an object, we add it to any existing filters thats already applied
if (isObject(valueToApply)) {
req.filterableFields[filter] = {
...(req.filterableFields[filter] || {}),
...valueToApply,
}
} else if (isPresent(valueToApply)) {
req.filterableFields[filter] = valueToApply
}
}
return next()
}
}
@@ -0,0 +1,13 @@
import { MedusaNextFunction, MedusaRequest } from "../types"
export function applyParamsAsFilters(mappings: { [param: string]: string }) {
return async (req: MedusaRequest, _, next: MedusaNextFunction) => {
for (const [param, paramValue] of Object.entries(req.params)) {
if (mappings[param]) {
req.filterableFields[mappings[param]] = paramValue
}
}
return next()
}
}
@@ -0,0 +1,11 @@
import { MedusaNextFunction, MedusaRequest } from "../types"
export function clearFiltersByKey(keys: string[]) {
return async (req: MedusaRequest, _, next: MedusaNextFunction) => {
keys.forEach((key) => {
delete req.filterableFields[key]
})
return next()
}
}
@@ -1,4 +1,3 @@
import { Query } from "@medusajs/types"
import {
ApiKeyType,
ContainerRegistrationKeys,
@@ -31,7 +30,7 @@ export async function ensurePublishableApiKeyMiddleware(
}
let apiKey
const query: Query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
try {
const { data } = await query.graph(
@@ -3,7 +3,6 @@ import { NextFunction, Response } from "express"
import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils"
import { formatException } from "./exception-formatter"
import { MedusaRequest } from "../types"
import { logger as logger_ } from "../../logger"
const QUERY_RUNNER_RELEASED = "QueryRunnerAlreadyReleasedError"
const TRANSACTION_STARTED = "TransactionAlreadyStartedError"
@@ -20,9 +19,7 @@ export function errorHandler() {
res: Response,
next: NextFunction
) => {
const logger: typeof logger_ = req.scope.resolve(
ContainerRegistrationKeys.LOGGER
)
const logger = req.scope.resolve(ContainerRegistrationKeys.LOGGER)
err = formatException(err)
@@ -1,3 +1,7 @@
export * from "./authenticate-middleware"
export * from "./error-handler"
export * from "./exception-formatter"
export * from "./apply-default-filters"
export * from "./apply-params-as-filters"
export * from "./clear-filters-by-key"
export * from "./set-context"
@@ -0,0 +1,21 @@
import { MedusaNextFunction, MedusaRequest } from "../types"
export function setContext(context: Record<string, any>) {
return async (req: MedusaRequest, _, next: MedusaNextFunction) => {
const ctx: Record<string, any> = { ...(req.context || {}) }
for (const [contextKey, contextValue] of Object.entries(context || {})) {
let valueToApply = contextValue
if (typeof contextValue === "function") {
valueToApply = await contextValue(req, ctx)
}
ctx[contextKey] = valueToApply
}
req.context = ctx
return next()
}
}
+5 -9
View File
@@ -1,17 +1,13 @@
import type { NextFunction, Request, Response } from "express"
import { ZodObject } from "zod"
import { MedusaPricingContext, RequestQueryFields } from "@medusajs/types"
import {
FindConfig,
MedusaPricingContext,
RequestQueryFields,
} from "@medusajs/types"
import { MedusaContainer } from "../container"
export interface FindConfig<Entity> {
select?: (keyof Entity)[]
skip?: number
take?: number
relations?: string[]
order?: { [K: string]: "ASC" | "DESC" }
}
/**
* List of all the supported HTTP methods
*/
@@ -0,0 +1,232 @@
import { pick } from "lodash"
import { RequestQueryFields, FindConfig, QueryConfig } from "@medusajs/types"
import {
isDefined,
isPresent,
MedusaError,
getSetDifference,
stringToSelectRelationObject,
} from "@medusajs/utils"
export function pickByConfig<TModel>(
obj: TModel | TModel[],
config: FindConfig<TModel>
): Partial<TModel> | Partial<TModel>[] {
const fields = [...(config.select ?? []), ...(config.relations ?? [])]
if (fields.length) {
if (Array.isArray(obj)) {
return obj.map((o) => pick(o, fields))
} else {
return pick(obj, fields)
}
}
return obj
}
export function prepareListQuery<T extends RequestQueryFields, TEntity>(
validated: T,
queryConfig: QueryConfig<TEntity> = {}
) {
// TODO: this function will be simplified a lot once we drop support for the old api
const { order, fields, limit = 50, expand, offset = 0 } = validated
let {
allowed = [],
defaults = [],
defaultFields = [],
defaultLimit,
allowedFields = [],
allowedRelations = [],
defaultRelations = [],
isList,
} = queryConfig
allowedFields = allowed.length ? allowed : allowedFields
defaultFields = defaults.length ? defaults : defaultFields
// e.g *product.variants meaning that we want all fields from the product.variants
// in that case it wont be part of the select but it will be part of the relations.
// For the remote query we will have to add the fields to the fields array as product.variants.*
const starFields: Set<string> = new Set()
let allFields = new Set(defaultFields) as Set<string>
if (isDefined(fields)) {
const customFields = fields.split(",").filter(Boolean)
const shouldReplaceDefaultFields =
!customFields.length ||
customFields.some((field) => {
return !(
field.startsWith("-") ||
field.startsWith("+") ||
field.startsWith(" ") ||
field.startsWith("*")
)
})
if (shouldReplaceDefaultFields) {
allFields = new Set(customFields.map((f) => f.replace(/^[+ -]/, "")))
} else {
customFields.forEach((field) => {
if (field.startsWith("+") || field.startsWith(" ")) {
allFields.add(field.trim().replace(/^\+/, ""))
} else if (field.startsWith("-")) {
allFields.delete(field.replace(/^-/, ""))
} else {
allFields.add(field)
}
})
}
allFields.add("id")
}
allFields.forEach((field) => {
if (field.startsWith("*")) {
starFields.add(field.replace(/^\*/, ""))
allFields.delete(field)
}
})
const notAllowedFields: string[] = []
if (allowedFields.length) {
;[...allFields, ...Array.from(starFields)].forEach((field) => {
const hasAllowedField = allowedFields.includes(field)
if (hasAllowedField) {
return
}
// Select full relation in that case it must match an allowed field fully
// e.g product.variants in that case we must have a product.variants in the allowedFields
if (starFields.has(field)) {
if (hasAllowedField) {
return
}
notAllowedFields.push(field)
return
}
const fieldStartsWithAllowedField = allowedFields.some((allowedField) =>
field.startsWith(allowedField)
)
if (!fieldStartsWithAllowedField) {
notAllowedFields.push(field)
return
}
})
}
if (allFields.size && notAllowedFields.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Requested fields [${Array.from(notAllowedFields).join(
", "
)}] are not valid`
)
}
// TODO: maintain backward compatibility, remove in the future
const { select, relations } = stringToSelectRelationObject(
Array.from(allFields)
)
let allRelations = new Set([
...relations,
...defaultRelations,
...Array.from(starFields),
])
if (isDefined(expand)) {
allRelations = new Set(expand.split(",").filter(Boolean))
}
if (allowedRelations.length && expand) {
const allAllowedRelations = new Set([...allowedRelations])
const notAllowedRelations = getSetDifference(
allRelations,
allAllowedRelations
)
if (allRelations.size && notAllowedRelations.size) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Requested fields [${Array.from(notAllowedRelations).join(
", "
)}] are not valid`
)
}
}
// End of expand compatibility
let orderBy: { [k: symbol]: "DESC" | "ASC" } | undefined = {}
if (isDefined(order)) {
let orderField = order
if (order.startsWith("-")) {
const [, field] = order.split("-")
orderField = field
orderBy = { [field]: "DESC" }
} else {
orderBy = { [order]: "ASC" }
}
if (
queryConfig?.allowedFields?.length &&
!queryConfig?.allowedFields.includes(orderField)
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order field ${orderField} is not valid`
)
}
}
const finalOrder = isPresent(orderBy) ? orderBy : undefined
return {
listConfig: {
select: select.length ? select : undefined,
relations: Array.from(allRelations),
skip: offset,
take: limit ?? defaultLimit,
order: finalOrder,
},
remoteQueryConfig: {
// Add starFields that are relations only on which we want all properties with a dedicated format to the remote query
fields: [
...Array.from(allFields),
...Array.from(starFields).map((f) => `${f}.*`),
],
pagination: isList
? {
skip: offset,
take: limit ?? defaultLimit,
order: finalOrder,
}
: {},
},
}
}
export function prepareRetrieveQuery<T extends RequestQueryFields, TEntity>(
validated: T,
queryConfig?: QueryConfig<TEntity>
) {
const { listConfig, remoteQueryConfig } = prepareListQuery(
validated,
queryConfig
)
return {
retrieveConfig: {
select: listConfig.select,
relations: listConfig.relations,
},
remoteQueryConfig: {
fields: remoteQueryConfig.fields,
pagination: {},
},
}
}
@@ -0,0 +1,43 @@
import compression from "compression"
import type { ConfigModule } from "@medusajs/types"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { HttpCompressionOptions, ProjectConfigOptions } from "../../config"
import type { MedusaRequest, MedusaResponse } from "../types"
export function shouldCompressResponse(
req: MedusaRequest,
res: MedusaResponse
) {
const { projectConfig } = req.scope.resolve<ConfigModule>(
ContainerRegistrationKeys.CONFIG_MODULE
)
const { enabled } = compressionOptions(projectConfig)
if (!enabled) {
return false
}
if (req.headers["x-no-compression"]) {
// don't compress responses with this request header
return false
}
// fallback to standard filter function
return compression.filter(req, res)
}
export function compressionOptions(
config: ProjectConfigOptions
): HttpCompressionOptions {
const responseCompressionOptions = config.http.compression ?? {}
responseCompressionOptions.enabled =
responseCompressionOptions.enabled ?? false
responseCompressionOptions.level = responseCompressionOptions.level ?? 6
responseCompressionOptions.memLevel = responseCompressionOptions.memLevel ?? 8
responseCompressionOptions.threshold =
responseCompressionOptions.threshold ?? 1024
return responseCompressionOptions
}
@@ -0,0 +1,104 @@
import {
arrayIntersection,
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaNextFunction, MedusaRequest } from "../types"
export function maybeApplyLinkFilter({
entryPoint,
resourceId,
filterableField,
filterByField = "id",
}) {
return async function linkFilter(
req: MedusaRequest,
_,
next: MedusaNextFunction
) {
const filterableFields = req.filterableFields
if (!filterableFields?.[filterableField]) {
return next()
}
const filterFields = filterableFields[filterableField]
const idsToFilterBy = Array.isArray(filterFields)
? filterFields
: [filterFields]
delete filterableFields[filterableField]
const remoteQuery = req.scope.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
const queryObject = remoteQueryObjectFromString({
entryPoint,
fields: [resourceId],
variables: { filters: { [filterableField]: idsToFilterBy } },
})
const resources = await remoteQuery(queryObject)
let existingFilters = filterableFields[filterByField] as
| string[]
| string
| undefined
if (existingFilters) {
if (typeof existingFilters === "string") {
existingFilters = [existingFilters]
}
filterableFields[filterByField] = arrayIntersection(
existingFilters,
resources.map((p) => p[resourceId])
)
} else {
filterableFields[filterByField] = resources.map((p) => p[resourceId])
}
req.filterableFields = transformFilterableFields(filterableFields)
return next()
}
}
/*
Transforms an object key string into nested objects
before = {
"test.something.another": []
}
after = {
test: {
something: {
another: []
}
}
}
*/
function transformFilterableFields(filterableFields: Record<string, unknown>) {
const result = {}
for (const key of Object.keys(filterableFields)) {
const value = filterableFields[key]
const keys = key.split(".")
let current = result
// Iterate over the keys, creating nested objects as needed
for (let i = 0; i < keys.length; i++) {
const part = keys[i]
current[part] ??= {}
if (i === keys.length - 1) {
// If its the last key, assign the value
current[part] = value
break
}
current = current[part]
}
}
return result
}
@@ -0,0 +1,48 @@
import { MedusaContainer } from "@medusajs/types"
import {
isString,
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest } from "../types"
export const refetchEntities = async (
entryPoint: string,
idOrFilter: string | object,
scope: MedusaContainer,
fields: string[],
pagination: MedusaRequest["remoteQueryConfig"]["pagination"] = {}
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const filters = isString(idOrFilter) ? { id: idOrFilter } : idOrFilter
let context: object = {}
if ("context" in filters) {
if (filters.context) {
context = filters.context!
}
delete filters.context
}
const variables = { filters, ...context, ...pagination }
const queryObject = remoteQueryObjectFromString({
entryPoint,
variables,
fields,
})
return await remoteQuery(queryObject)
}
export const refetchEntity = async (
entryPoint: string,
idOrFilter: string | object,
scope: MedusaContainer,
fields: string[]
) => {
const [entity] = await refetchEntities(entryPoint, idOrFilter, scope, fields)
return entity
}
@@ -0,0 +1,22 @@
import {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
MiddlewareFunction,
} from "../types"
/**
* Due to how our route loader works, where we load all middlewares before routes, ambiguous routes * end up having all middlewares on different routes executed before the route handler is.
*/
/**
* This function allows us to skip middlewares for particular routes, so we can temporarily solve * * this without completely breaking the route loader for everyone.
*/
export const unlessPath =
(onPath: RegExp, middleware: MiddlewareFunction) =>
(req: MedusaRequest, res: MedusaResponse, next: MedusaNextFunction) => {
if (onPath.test(req.path)) {
return next()
} else {
return middleware(req, res, next)
}
}
@@ -0,0 +1,36 @@
import { z } from "zod"
import { NextFunction } from "express"
import { MedusaRequest, MedusaResponse } from "../types"
import { zodValidator } from "../../zod/zod-helpers"
export function validateAndTransformBody(
zodSchema:
| z.ZodObject<any, any>
| ((
customSchema?: z.ZodObject<any, any>
) => z.ZodObject<any, any> | z.ZodEffects<any, any>)
): (
req: MedusaRequest,
res: MedusaResponse,
next: NextFunction
) => Promise<void> {
return async function validateBody(
req: MedusaRequest,
_: MedusaResponse,
next: NextFunction
) {
try {
let schema: z.ZodObject<any, any> | z.ZodEffects<any, any>
if (typeof zodSchema === "function") {
schema = zodSchema(req.additionalDataValidator)
} else {
schema = zodSchema
}
req.validatedBody = await zodValidator(schema, req.body)
next()
} catch (e) {
next(e)
}
}
}
@@ -0,0 +1,91 @@
import { z } from "zod"
import { omit } from "lodash"
import { NextFunction } from "express"
import { removeUndefinedProperties, MedusaError } from "@medusajs/utils"
import { BaseEntity, QueryConfig, RequestQueryFields } from "@medusajs/types"
import { zodValidator } from "../../zod/zod-helpers"
import { MedusaRequest, MedusaResponse } from "../types"
import { prepareListQuery, prepareRetrieveQuery } from "./get-query-config"
/**
* Normalize an input query, especially from array like query params to an array type
* e.g: /admin/orders/?fields[]=id,status,cart_id becomes { fields: ["id", "status", "cart_id"] }
*
* We only support up to 2 levels of depth for query params in order to have a somewhat readable query param, and limit possible performance issues
*/
const normalizeQuery = (req: MedusaRequest) => {
return Object.entries(req.query).reduce((acc, [key, val]) => {
let normalizedValue = val
if (Array.isArray(val) && val.length === 1 && typeof val[0] === "string") {
normalizedValue = val[0].split(",")
}
if (key.includes(".")) {
const [parent, child, ...others] = key.split(".")
if (others.length > 0) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
`Key accessor more than 2 levels deep: ${key}`
)
}
if (!acc[parent]) {
acc[parent] = {}
}
acc[parent] = {
...acc[parent],
[child]: normalizedValue,
}
} else {
acc[key] = normalizedValue
}
return acc
}, {})
}
/**
* Omit the non filterable config from the validated object
* @param obj
*/
const getFilterableFields = <T extends RequestQueryFields>(obj: T): T => {
const result = omit(obj, ["limit", "offset", "fields", "order"]) as T
return removeUndefinedProperties(result)
}
export function validateAndTransformQuery<TEntity extends BaseEntity>(
zodSchema: z.ZodObject<any, any> | z.ZodEffects<any, any>,
queryConfig: QueryConfig<TEntity>
): (
req: MedusaRequest,
res: MedusaResponse,
next: NextFunction
) => Promise<void> {
return async function validateQuery(
req: MedusaRequest,
_: MedusaResponse,
next: NextFunction
) {
try {
const allowed = (req.allowed ?? queryConfig.allowed ?? []) as string[]
delete req.allowed
const query = normalizeQuery(req)
const validated = await zodValidator(zodSchema, query)
const cnf = queryConfig.isList
? prepareListQuery(validated, { ...queryConfig, allowed })
: prepareRetrieveQuery(validated, { ...queryConfig, allowed })
req.validatedQuery = validated
req.filterableFields = getFilterableFields(req.validatedQuery)
req.remoteQueryConfig = cnf.remoteQueryConfig
req.listConfig = (cnf as any).listConfig
req.retrieveConfig = (cnf as any).retrieveConfig
next()
} catch (e) {
next(e)
}
}
}
+1
View File
@@ -10,6 +10,7 @@ export * from "./medusa-app-loader"
export * from "./subscribers"
export * from "./workflows"
export * from "./telemetry"
export * from "./zod"
export const MEDUSA_CLI_PATH = require.resolve("@medusajs/medusa-cli")
+1 -1
View File
@@ -1 +1 @@
export * from './job-loader'
export * from "./job-loader"
@@ -33,6 +33,7 @@ import {
container as mainContainer,
MedusaContainer,
} from "./container"
import type { Knex } from "@mikro-orm/knex"
export class MedusaAppLoader {
/**
@@ -106,9 +107,9 @@ export class MedusaAppLoader {
protected prepareSharedResourcesAndDeps() {
const injectedDependencies = {
[ContainerRegistrationKeys.PG_CONNECTION]: this.#container.resolve(
ContainerRegistrationKeys.PG_CONNECTION
),
[ContainerRegistrationKeys.PG_CONNECTION]: this.#container.resolve<
Knex<any>
>(ContainerRegistrationKeys.PG_CONNECTION),
[ContainerRegistrationKeys.LOGGER]: this.#container.resolve(
ContainerRegistrationKeys.LOGGER
),
@@ -1,11 +1,11 @@
/**
* Custom wrapper on top of MikroORM CLI to override the issue
* they have when importing TypeScript files.
*
*
* They have hardcoded the module system of TypeScript to CommonJS
* and that makes it impossible to use any other module system
* like Node16 or NodeNext and so on.
*
*
* With this wrapper, we monkey patch the code responsible for register
* ts-node and then boot their CLI. Since, the code footprint is
* small, we should be okay with managing this wrapper.
@@ -51,7 +51,6 @@ require("@jercle/yargonaut")
.style("yellow", "required")
.helpStyle("green")
.errorsStyle("red")
;(async () => {
const argv = await CLIConfigurator.configure()
const args = await argv.parse(process.argv.slice(2))
@@ -1 +1 @@
export * from "@medusajs/workflows-sdk/composer"
export * from "@medusajs/workflows-sdk/composer"
+1
View File
@@ -0,0 +1 @@
export * from "./zod-helpers"
@@ -0,0 +1,134 @@
import { MedusaError } from "@medusajs/framework/utils"
import {
z,
ZodError,
ZodInvalidTypeIssue,
ZodInvalidUnionIssue,
ZodIssue,
} from "zod"
const formatPath = (issue: ZodIssue) => {
return issue.path.join(", ")
}
const formatInvalidType = (issues: ZodIssue[]) => {
const expected = issues
.map((i) => {
// Unforutnately the zod library doesn't distinguish between a wrong type and a required field, which we want to handle differently
if (i.code === "invalid_type" && i.message !== "Required") {
return i.expected
}
return
})
.filter(Boolean)
if (!expected.length) {
return
}
const received = (issues?.[0] as ZodInvalidTypeIssue)?.received
return `Expected type: '${expected.join(", ")}' for field '${formatPath(
issues[0]
)}', got: '${received}'`
}
const formatRequiredField = (issues: ZodIssue[]) => {
const expected = issues
.map((i) => {
if (i.code === "invalid_type" && i.message === "Required") {
return i.expected
}
return
})
.filter(Boolean)
if (!expected.length) {
return
}
return `Field '${formatPath(issues[0])}' is required`
}
const formatUnionError = (issue: ZodInvalidUnionIssue) => {
const issues = issue.unionErrors.flatMap((e) => e.issues)
return (
formatInvalidType(issues) || formatRequiredField(issues) || issue.message
)
}
const formatError = (err: ZodError) => {
const issueMessages = err.issues.slice(0, 3).map((issue) => {
switch (issue.code) {
case "invalid_type":
return (
formatInvalidType([issue]) ||
formatRequiredField([issue]) ||
issue.message
)
case "invalid_literal":
return `Expected literal: '${issue.expected}' for field '${formatPath(
issue
)}', but got: '${issue.received}'`
case "invalid_union":
return formatUnionError(issue)
case "invalid_enum_value":
return `Expected: '${issue.options.join(", ")}' for field '${formatPath(
issue
)}', but got: '${issue.received}'`
case "unrecognized_keys":
return `Unrecognized fields: '${issue.keys.join(", ")}'`
case "invalid_arguments":
return `Invalid arguments for '${issue.path.join(", ")}'`
case "too_small":
return `Value for field '${formatPath(
issue
)}' too small, expected at least: '${issue.minimum}'`
case "too_big":
return `Value for field '${formatPath(
issue
)}' too big, expected at most: '${issue.maximum}'`
case "not_multiple_of":
return `Value for field '${formatPath(issue)}' not multiple of: '${
issue.multipleOf
}'`
case "not_finite":
return `Value for field '${formatPath(issue)}' not finite: '${
issue.message
}'`
case "invalid_union_discriminator":
case "invalid_return_type":
case "invalid_date":
case "invalid_string":
case "invalid_intersection_types":
default:
return issue.message
}
})
return issueMessages.join("; ")
}
export async function zodValidator<T>(
zodSchema: z.ZodObject<any, any> | z.ZodEffects<any, any>,
body: T
): Promise<z.ZodRawShape> {
let strictSchema = zodSchema
// ZodEffects doesn't support setting as strict, for all other schemas we want to enforce strictness.
if ("strict" in zodSchema) {
strictSchema = zodSchema.strict()
}
try {
return await strictSchema.parseAsync(body)
} catch (err) {
if (err instanceof ZodError) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Invalid request: ${formatError(err)}`
)
}
throw err
}
}
+4 -1
View File
@@ -1,3 +1,6 @@
{
"extends": "../../../_tsconfig.base.json"
"extends": "../../../_tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src"
}
}
+7 -4
View File
@@ -59,9 +59,12 @@ export class Upload {
}
async delete(id: string, headers?: ClientHeaders) {
return this.client.fetch<HttpTypes.AdminFileDeleteResponse>(`/admin/uploads/${id}`, {
method: "DELETE",
headers,
})
return this.client.fetch<HttpTypes.AdminFileDeleteResponse>(
`/admin/uploads/${id}`,
{
method: "DELETE",
headers,
}
)
}
}
+14 -12
View File
@@ -204,12 +204,13 @@ export class Store {
lineItemId: string,
headers?: ClientHeaders
) => {
return this.client.fetch<
HttpTypes.StoreLineItemDeleteResponse
>(`/store/carts/${cartId}/line-items/${lineItemId}`, {
method: "DELETE",
headers,
})
return this.client.fetch<HttpTypes.StoreLineItemDeleteResponse>(
`/store/carts/${cartId}/line-items/${lineItemId}`,
{
method: "DELETE",
headers,
}
)
},
addShippingMethod: async (
cartId: string,
@@ -432,12 +433,13 @@ export class Store {
)
},
deleteAddress: async (addressId: string, headers?: ClientHeaders) => {
return this.client.fetch<
HttpTypes.StoreCustomerAddressDeleteResponse
>(`/store/customers/me/addresses/${addressId}`, {
method: "DELETE",
headers,
})
return this.client.fetch<HttpTypes.StoreCustomerAddressDeleteResponse>(
`/store/customers/me/addresses/${addressId}`,
{
method: "DELETE",
headers,
}
)
},
}
}
@@ -1,4 +1,3 @@
export * from "./module-loader"
export * from "./module-provider-loader"
export * from "./register-modules"
+1 -1
View File
@@ -1,7 +1,7 @@
import {
AuthIdentityDTO,
AuthenticationInput,
AuthenticationResponse,
AuthIdentityDTO,
} from "./common"
// This interface currently won't allow for linking multiple providers to a single auth entity. That flow is more complex and not supported yet.
+1 -1
View File
@@ -2,9 +2,9 @@ import { FindConfig } from "../common"
import { IModuleService } from "../modules-sdk"
import { Context } from "../shared-context"
import {
AuthIdentityDTO,
AuthenticationInput,
AuthenticationResponse,
AuthIdentityDTO,
CreateAuthIdentityDTO,
CreateProviderIdentityDTO,
FilterableAuthIdentityProps,
+1 -1
View File
@@ -32,8 +32,8 @@ import {
CreateShippingMethodForSingleCartDTO,
CreateShippingMethodTaxLineDTO,
UpdateAddressDTO,
UpdateCartDTO,
UpdateCartDataDTO,
UpdateCartDTO,
UpdateLineItemDTO,
UpdateLineItemTaxLineDTO,
UpdateLineItemWithSelectorDTO,
+24 -2
View File
@@ -306,9 +306,31 @@ export type RawRounding = {
/**
* @ignore
*/
export type QueryConfig<TEntity extends BaseEntity> = {
export type QueryConfig<TEntity> = {
/**
* Default fields and relations to return
*/
defaults?: (keyof TEntity | string)[]
allowed?: (keyof TEntity | string)[]
/**
* @deprecated Use `defaults` instead
*/
defaultFields?: (keyof TEntity | string)[]
/**
* @deprecated Use `defaultFields` instead and the relations will be inferred
*/
defaultRelations?: string[]
/**
* Fields and relations that are allowed to be requested
*/
allowed?: string[]
/**
* @deprecated Use `allowed` instead
*/
allowedFields?: string[]
/**
* @deprecated Use `allowedFields` instead and the relations will be inferred
*/
allowedRelations?: string[]
defaultLimit?: number
isList?: boolean
}
+1 -1
View File
@@ -3,4 +3,4 @@ import { MedusaContainer } from "./medusa-container"
export type ExecArgs = {
container: MedusaContainer
args: string[]
}
}
@@ -2,7 +2,7 @@ import stream from "stream"
/**
* @interface
*
*
* Details of a file upload's result.
*/
export type FileServiceUploadResult = {
@@ -19,7 +19,7 @@ export type FileServiceUploadResult = {
/**
* @interface
*
*
* The relevant details to upload a file through a stream.
*/
export type FileServiceGetUploadStreamResult = {
@@ -44,7 +44,7 @@ export type FileServiceGetUploadStreamResult = {
/**
* @interface
*
*
* The details of a file to retrieve.
*/
export type GetUploadedFileType = {
@@ -61,7 +61,7 @@ export type GetUploadedFileType = {
/**
* @interface
*
*
* The details of the file to remove.
*/
export type DeleteFileType = {
@@ -75,7 +75,7 @@ export type DeleteFileType = {
/**
* @interface
*
*
* The details of the file being uploaded through a stream.
*/
export type UploadStreamDescriptorType = {
@@ -1,7 +1,7 @@
export type FulfillmentOption = {
/**
* The option's ID.
*
*
* @example express
*/
id: string
@@ -22,4 +22,4 @@ export type AdminApiKeyListResponse = PaginatedResponse<{
api_keys: AdminApiKey[]
}>
export type AdminApiKeyDeleteResponse = DeleteResponse<"api_key">
export type AdminApiKeyDeleteResponse = DeleteResponse<"api_key">
@@ -1,4 +1,3 @@
export * from "./payloads"
export * from "./queries"
export * from "./responses"
@@ -26,4 +26,4 @@ export interface AdminCampaignResponse {
campaign: AdminCampaign
}
export type AdminCampaignDeleteResponse = DeleteResponse<"campaign">
export type AdminCampaignDeleteResponse = DeleteResponse<"campaign">
@@ -20,7 +20,8 @@ export interface StoreCart extends Omit<BaseCart, "items"> {
payment_collection?: StorePaymentCollection
region?: StoreRegion
}
export interface StoreCartLineItem extends Omit<BaseCartLineItem, "product" | "variant" | "cart"> {
export interface StoreCartLineItem
extends Omit<BaseCartLineItem, "product" | "variant" | "cart"> {
product?: StoreProduct
variant?: StoreProductVariant
cart: StoreCart
@@ -39,4 +40,4 @@ export interface StoreCartShippingMethod extends BaseCartShippingMethod {
adjustments?: (BaseShippingMethodAdjustment & {
shipping_method: StoreCartShippingMethod
})[]
}
}
@@ -1,3 +1,3 @@
export * from "./entities"
export * from "./payloads"
export * from "./responses"
export * from "./responses"
@@ -94,4 +94,4 @@ export interface StoreAddAddress {
* Holds custom data in key-value pairs.
*/
metadata?: Record<string, unknown> | null
}
}
@@ -6,17 +6,22 @@ export interface StoreCartResponse {
cart: StoreCart
}
export type StoreCompleteCartResponse = {
type: "cart"
cart: StoreCart
error: {
message: string
name: string
type: string
}
} | {
type: "order"
order: StoreOrder
}
export type StoreCompleteCartResponse =
| {
type: "cart"
cart: StoreCart
error: {
message: string
name: string
type: string
}
}
| {
type: "order"
order: StoreOrder
}
export type StoreLineItemDeleteResponse = DeleteResponseWithParent<"line-item", StoreCart>
export type StoreLineItemDeleteResponse = DeleteResponseWithParent<
"line-item",
StoreCart
>
@@ -1,3 +1,3 @@
export * from "./entities"
export * from "./queries"
export * from "./responses"
export * from "./responses"
@@ -1,6 +1,7 @@
import { OperatorMap } from "../../../dal";
import { BaseCollectionListParams } from "../common";
import { OperatorMap } from "../../../dal"
import { BaseCollectionListParams } from "../common"
export interface StoreCollectionFilters extends Omit<BaseCollectionListParams, "id"> {
export interface StoreCollectionFilters
extends Omit<BaseCollectionListParams, "id"> {
deleted_at?: OperatorMap<string>
}
@@ -15,14 +15,15 @@ export type DeleteResponse<TObject extends string> = {
deleted: boolean
}
export type DeleteResponseWithParent<TObject extends string, TParent = {}> =
DeleteResponse<TObject> &
{
/**
* The parent resource of the item that was deleted, if applicable.
*/
parent?: TParent
}
export type DeleteResponseWithParent<
TObject extends string,
TParent = {}
> = DeleteResponse<TObject> & {
/**
* The parent resource of the item that was deleted, if applicable.
*/
parent?: TParent
}
export type PaginatedResponse<T> = {
limit: number
@@ -38,4 +39,4 @@ export type BatchResponse<T> = {
object: string
deleted: boolean
}
}
}
@@ -1,3 +1,3 @@
export * from "./admin"
export * from "./common"
export * from "./store"
export * from "./store"
@@ -1,3 +1,3 @@
import { BaseCurrency } from "../common";
import { BaseCurrency } from "../common"
export interface StoreCurrency extends BaseCurrency {}
export interface StoreCurrency extends BaseCurrency {}
@@ -1,3 +1,3 @@
export * from "./entities"
export * from "./queries"
export * from "./responses"
export * from "./responses"
@@ -1,8 +1,9 @@
import { BaseFilterable } from "../../../dal";
import { FindParams } from "../../common";
import { BaseFilterable } from "../../../dal"
import { FindParams } from "../../common"
export interface StoreGetCurrencyListParams extends
FindParams, BaseFilterable<StoreGetCurrencyListParams> {
q?: string
code?: string | string[]
}
export interface StoreGetCurrencyListParams
extends FindParams,
BaseFilterable<StoreGetCurrencyListParams> {
q?: string
code?: string | string[]
}
@@ -1,5 +1,5 @@
import { PaginatedResponse } from "../../common";
import { StoreCurrency } from "./entities";
import { PaginatedResponse } from "../../common"
import { StoreCurrency } from "./entities"
export interface StoreCurrencyResponse {
currency: StoreCurrency
@@ -1,4 +1,4 @@
export * from "./entities"
export * from "./payloads"
export * from "./queries"
export * from "./responses"
export * from "./responses"
@@ -1,2 +1 @@
export * from "./admin";
export * from "./admin"
@@ -1,4 +1,4 @@
export * from "./entities"
export * from "./payloads"
export * from "./queries"
export * from "./responses"
export * from "./responses"
@@ -1,4 +1,8 @@
import { DeleteResponse, DeleteResponseWithParent, PaginatedResponse } from "../../common"
import {
DeleteResponse,
DeleteResponseWithParent,
PaginatedResponse,
} from "../../common"
import { AdminCustomer, AdminCustomerAddress } from "./entities"
export interface AdminCustomerResponse {
@@ -1,9 +1,6 @@
import {
BaseCustomer,
BaseCustomerAddress,
} from "../common"
import { BaseCustomer, BaseCustomerAddress } from "../common"
export interface StoreCustomer extends Omit<BaseCustomer, "created_by"> {
addresses: StoreCustomerAddress[]
}
export interface StoreCustomerAddress extends BaseCustomerAddress {}
export interface StoreCustomerAddress extends BaseCustomerAddress {}
@@ -1,4 +1,4 @@
export * from "./entities"
export * from "./payloads"
export * from "./queries"
export * from "./responses"
export * from "./responses"
@@ -1,8 +1,5 @@
import {
BaseCustomerAddressFilters,
BaseCustomerFilters,
} from "../common"
import { BaseCustomerAddressFilters, BaseCustomerFilters } from "../common"
export interface StoreCustomerFilters extends BaseCustomerFilters {}
export interface StoreCustomerAddressFilters
extends BaseCustomerAddressFilters {}
extends BaseCustomerAddressFilters {}
@@ -1,5 +1,5 @@
import { DeleteResponseWithParent, PaginatedResponse } from "../../common";
import { StoreCustomer, StoreCustomerAddress } from "./entities";
import { DeleteResponseWithParent, PaginatedResponse } from "../../common"
import { StoreCustomer, StoreCustomerAddress } from "./entities"
export interface StoreCustomerResponse {
customer: StoreCustomer
@@ -12,4 +12,7 @@ export interface StoreCustomerAddressResponse {
export interface StoreCustomerAddressListResponse
extends PaginatedResponse<{ addresses: StoreCustomerAddress[] }> {}
export type StoreCustomerAddressDeleteResponse = DeleteResponseWithParent<"address", StoreCustomer>
export type StoreCustomerAddressDeleteResponse = DeleteResponseWithParent<
"address",
StoreCustomer
>
@@ -1,3 +1,3 @@
import { BaseExchange } from "../common";
import { BaseExchange } from "../common"
export interface AdminExchange extends BaseExchange {}
export interface AdminExchange extends BaseExchange {}
@@ -1,6 +1,10 @@
import { OperatorMap } from "../../dal"
import { FindParams } from "../common"
import { BaseOrder, BaseOrderShippingMethod, BaseOrderTransaction } from "../order/common"
import {
BaseOrder,
BaseOrderShippingMethod,
BaseOrderTransaction,
} from "../order/common"
import { AdminReturn, AdminReturnItem } from "../return"
export interface BaseExchangeItem {
@@ -1 +1 @@
export * from "./admin"
export * from "./admin"
@@ -1,3 +1,3 @@
import { BaseFile } from "../common"
export interface AdminFile extends BaseFile {}
export interface AdminFile extends BaseFile {}
@@ -1,3 +1,3 @@
export * from "./entities"
export * from "./payloads"
export * from "./responses"
export * from "./responses"
@@ -1,3 +1,3 @@
import { BaseUploadFile } from "../common";
import { BaseUploadFile } from "../common"
export type AdminUploadFile = BaseUploadFile
@@ -1,5 +1,5 @@
import { DeleteResponse } from "../../common";
import { AdminFile } from "./entities";
import { DeleteResponse } from "../../common"
import { AdminFile } from "./entities"
export interface AdminFileResponse {
file: AdminFile
@@ -9,4 +9,4 @@ export interface AdminFileListResponse {
files: AdminFile[]
}
export type AdminFileDeleteResponse = DeleteResponse<"file">
export type AdminFileDeleteResponse = DeleteResponse<"file">
@@ -1,3 +1,3 @@
import { BaseFulfillmentProvider } from "../common";
import { BaseFulfillmentProvider } from "../common"
export interface AdminFulfillmentProvider extends BaseFulfillmentProvider {}
@@ -1,4 +1,4 @@
export interface BaseFulfillmentProvider {
id: string
is_enabled: boolean
}
}
@@ -3,4 +3,4 @@ export interface AdminGetFulfillmentProvidersParams {
stock_location_id?: string | string[]
is_enabled?: boolean
q?: string
}
}
@@ -1,7 +1,9 @@
import { FindParams } from "../../common"
import { BaseFilterable, OperatorMap } from "../../../dal"
export interface AdminInventoryItemParams extends FindParams, BaseFilterable<AdminInventoryItemParams> {
export interface AdminInventoryItemParams
extends FindParams,
BaseFilterable<AdminInventoryItemParams> {
id?: string | string[]
q?: string
sku?: string | string[]
@@ -1,4 +1,8 @@
import { DeleteResponse, DeleteResponseWithParent, PaginatedResponse } from "../../common"
import {
DeleteResponse,
DeleteResponseWithParent,
PaginatedResponse,
} from "../../common"
import { AdminInventoryItem } from "./entities"
export interface AdminInventoryItemResponse {
@@ -14,4 +18,4 @@ export type AdminInventoryItemDeleteResponse = DeleteResponse<"inventory_item">
export type AdminInventoryLevelDeleteResponse = DeleteResponseWithParent<
"inventory-level",
AdminInventoryItem
>
>
@@ -7,4 +7,4 @@ export interface AdminInvite {
metadata?: Record<string, unknown>
created_at?: Date
updated_at?: Date
}
}
@@ -1,4 +1,4 @@
export * from "./entities"
export * from "./payloads"
export * from "./queries"
export * from "./responses"
export * from "./responses"
@@ -6,4 +6,4 @@ export type AdminAcceptInvite = {
export type AdminCreateInvite = {
email: string
metadata?: Record<string, unknown>
}
}
@@ -1,11 +1,13 @@
import { BaseFilterable, OperatorMap } from "../../../dal"
import { FindParams } from "../../common"
export interface AdminGetInvitesParams extends FindParams, BaseFilterable<AdminGetInvitesParams> {
export interface AdminGetInvitesParams
extends FindParams,
BaseFilterable<AdminGetInvitesParams> {
q?: string
id?: string | string[]
email?: string | string[]
created_at?: OperatorMap<string>
updated_at?: OperatorMap<string>
deleted_at?: OperatorMap<string>
}
}
@@ -1,6 +1,6 @@
import { DeleteResponse, PaginatedResponse } from "../../common";
import { AdminUser } from "../../user";
import { AdminInvite } from "./entities";
import { DeleteResponse, PaginatedResponse } from "../../common"
import { AdminUser } from "../../user"
import { AdminInvite } from "./entities"
export interface AdminInviteResponse {
invite: AdminInvite
@@ -10,10 +10,12 @@ export type AdminInviteListResponse = PaginatedResponse<{
invites: AdminInvite[]
}>
export type AdminAcceptInviteResponse = {
user: AdminUser
} | {
message: string
}
export type AdminAcceptInviteResponse =
| {
user: AdminUser
}
| {
message: string
}
export type AdminInviteDeleteResponse = DeleteResponse<"invite">
export type AdminInviteDeleteResponse = DeleteResponse<"invite">
@@ -27,8 +27,11 @@ export interface AdminOrder extends Omit<BaseOrder, "items"> {
shipping_methods: AdminOrderShippingMethod[]
}
export interface AdminOrderChange extends
Omit<BaseOrderChange, "order" | "claim" | "return_order" | "exchange" | "actions"> {
export interface AdminOrderChange
extends Omit<
BaseOrderChange,
"order" | "claim" | "return_order" | "exchange" | "actions"
> {
order: AdminOrder
claim: AdminClaim
return_order: AdminReturn
@@ -36,14 +39,16 @@ export interface AdminOrderChange extends
actions: AdminOrderChangeAction[]
}
export interface AdminOrderChangeAction extends Omit<BaseOrderChangeAction, "order_change" | "order"> {
export interface AdminOrderChangeAction
extends Omit<BaseOrderChangeAction, "order_change" | "order"> {
order_change: AdminOrderChange
order: AdminOrder | null
}
export interface AdminOrderFulfillment extends BaseOrderFulfillment {}
export interface AdminOrderLineItem extends Omit<BaseOrderLineItem, "variant" | "product"> {
export interface AdminOrderLineItem
extends Omit<BaseOrderLineItem, "variant" | "product"> {
variant?: AdminProductVariant
product?: AdminProduct
}
@@ -1,4 +1,4 @@
export * from "./entities"
export * from "./payload"
export * from "./queries"
export * from "./responses"
export * from "./responses"
@@ -1,8 +1,7 @@
import { OperatorMap } from "../../../dal"
import { FulfillmentStatus, PaymentStatus } from "../../../order"
import { FindParams } from "../../common"
import { BaseOrderFilters } from "../common"
import { BaseOrderChangesFilters } from "../common"
import { BaseOrderChangesFilters, BaseOrderFilters } from "../common"
export interface AdminOrderFilters extends FindParams, BaseOrderFilters {
id?: string[] | string
@@ -15,5 +14,4 @@ export interface AdminOrderFilters extends FindParams, BaseOrderFilters {
updated_at?: OperatorMap<string>
}
export interface AdminOrderChangesFilters extends BaseOrderChangesFilters {}
@@ -16,7 +16,8 @@ import {
BaseOrderShippingMethodTaxLine,
} from "../common"
export interface StoreOrder extends Omit<BaseOrder, "items" | "version" | "transations"> {
export interface StoreOrder
extends Omit<BaseOrder, "items" | "version" | "transations"> {
shipping_address?: StoreOrderAddress | null
billing_address?: StoreOrderAddress | null
items: StoreOrderLineItem[] | null
@@ -25,7 +26,8 @@ export interface StoreOrder extends Omit<BaseOrder, "items" | "version" | "trans
fulfillments?: StoreOrderFulfillment[]
customer?: StoreCustomer
}
export interface StoreOrderLineItem extends Omit<BaseOrderLineItem, "product" | "variant"> {
export interface StoreOrderLineItem
extends Omit<BaseOrderLineItem, "product" | "variant"> {
variant?: StoreProductVariant
product?: StoreProduct
tax_lines?: (BaseOrderLineItemTaxLine & {
@@ -52,4 +54,4 @@ export interface StoreOrderShippingMethod extends BaseOrderShippingMethod {
shipping_method: StoreOrderShippingMethod
}
}
export interface StoreOrderFulfillment extends BaseOrderFulfillment {}
export interface StoreOrderFulfillment extends BaseOrderFulfillment {}
@@ -1,3 +1,3 @@
export * from "./entities"
export * from "./queries"
export * from "./responses"
export * from "./responses"
@@ -1,9 +1,10 @@
import { BaseFilterable } from "../../../dal";
import { OrderStatus } from "../../../order";
import { FindParams } from "../../common";
import { BaseFilterable } from "../../../dal"
import { OrderStatus } from "../../../order"
import { FindParams } from "../../common"
export interface StoreOrderFilters extends
FindParams, BaseFilterable<StoreOrderFilters> {
id?: string | string[]
status?: OrderStatus | OrderStatus[]
}
export interface StoreOrderFilters
extends FindParams,
BaseFilterable<StoreOrderFilters> {
id?: string | string[]
status?: OrderStatus | OrderStatus[]
}
@@ -1,5 +1,5 @@
import { PaginatedResponse } from "../../common";
import { StoreOrder } from "./entities";
import { PaginatedResponse } from "../../common"
import { StoreOrder } from "./entities"
export interface StoreOrderResponse {
order: StoreOrder
@@ -25,4 +25,4 @@ export interface AdminPaymentSession extends BasePaymentSession {
payment_collection?: AdminPaymentCollection
}
export interface AdminRefund extends BaseRefund {}
export interface AdminRefundReason extends RefundReason {}
export interface AdminRefundReason extends RefundReason {}
@@ -1,4 +1,4 @@
export * from "./entities"
export * from "./payloads"
export * from "./queries"
export * from "./responses"
export * from "./responses"
@@ -7,11 +7,12 @@ import {
} from "../common"
import { AdminRefundReason } from "./entities"
export interface AdminPaymentProviderFilters extends
FindParams, BaseFilterable<AdminPaymentProviderFilters>{
id: string | string[]
is_enabled?: boolean
}
export interface AdminPaymentProviderFilters
extends FindParams,
BaseFilterable<AdminPaymentProviderFilters> {
id: string | string[]
is_enabled?: boolean
}
export interface AdminPaymentCollectionFilters
extends BasePaymentCollectionFilters {}
export interface AdminPaymentSessionFilters extends BasePaymentSessionFilters {}
@@ -24,7 +25,9 @@ export interface AdminPaymentFilters extends FindParams, BasePaymentFilters {
deleted_at?: OperatorMap<string>
}
export interface RefundReasonFilters extends FindParams, BaseFilterable<AdminRefundReason> {
export interface RefundReasonFilters
extends FindParams,
BaseFilterable<AdminRefundReason> {
id?: string | string[]
q?: string
}
Loaded 100 of 557 files, more files were not shown because too many files have changed in this diff. Show more