docs: migrate guides to TSDoc references (#6100)
This commit is contained in:
@@ -14,7 +14,7 @@ class LocalService extends AbstractFileService implements IFileService {
|
||||
protected backendUrl_: string
|
||||
|
||||
constructor({}, options) {
|
||||
super({}, options)
|
||||
super(arguments[0], options)
|
||||
|
||||
this.uploadDir_ = options.upload_dir || "uploads"
|
||||
this.backendUrl_ = options.backend_url || "http://localhost:9000"
|
||||
|
||||
@@ -26,7 +26,7 @@ class MinioService extends AbstractFileService implements IFileService {
|
||||
protected downloadUrlDuration: string | number
|
||||
|
||||
constructor({}, options) {
|
||||
super({}, options)
|
||||
super(arguments[0], options)
|
||||
|
||||
this.bucket_ = options.bucket
|
||||
this.accessKeyId_ = options.access_key_id
|
||||
|
||||
@@ -33,7 +33,7 @@ class S3Service extends AbstractFileService implements IFileService {
|
||||
protected client_: S3Client
|
||||
|
||||
constructor({ logger }, options) {
|
||||
super({}, options)
|
||||
super(arguments[0], options)
|
||||
|
||||
this.prefix_ = options.prefix ? `${options.prefix}/` : ''
|
||||
this.bucket_ = options.bucket
|
||||
|
||||
@@ -195,6 +195,16 @@ export const useAdminCancelClaim = (
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The details of the claim's fulfillment.
|
||||
*/
|
||||
export type AdminFulfillClaimReq = AdminPostOrdersOrderClaimsClaimFulfillmentsReq & {
|
||||
/**
|
||||
* The claim's ID.
|
||||
*/
|
||||
claim_id: string
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook creates a Fulfillment for a Claim, and change its fulfillment status to `partially_fulfilled` or `fulfilled` depending on whether all the items were fulfilled.
|
||||
* It may also change the status to `requires_action` if any actions are required.
|
||||
@@ -238,12 +248,7 @@ export const useAdminFulfillClaim = (
|
||||
options?: UseMutationOptions<
|
||||
Response<AdminOrdersRes>,
|
||||
Error,
|
||||
AdminPostOrdersOrderClaimsClaimFulfillmentsReq & {
|
||||
/**
|
||||
* The claim's ID.
|
||||
*/
|
||||
claim_id: string
|
||||
}
|
||||
AdminFulfillClaimReq
|
||||
>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
@@ -253,7 +258,7 @@ export const useAdminFulfillClaim = (
|
||||
({
|
||||
claim_id,
|
||||
...payload
|
||||
}: AdminPostOrdersOrderClaimsClaimFulfillmentsReq & { claim_id: string }) =>
|
||||
}: AdminFulfillClaimReq) =>
|
||||
client.admin.orders.fulfillClaim(orderId, claim_id, payload),
|
||||
buildOptions(
|
||||
queryClient,
|
||||
|
||||
@@ -203,6 +203,16 @@ export const useCreatePaymentSession = (
|
||||
return useMutation(() => client.carts.createPaymentSessions(cartId), options)
|
||||
}
|
||||
|
||||
/**
|
||||
* The details of the payment session to update.
|
||||
*/
|
||||
export type UpdatePaymentSessionReq = StorePostCartsCartPaymentSessionUpdateReq & {
|
||||
/**
|
||||
* The payment provider's identifier.
|
||||
*/
|
||||
provider_id: string
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook updates a Payment Session with additional data. This can be useful depending on the payment provider used.
|
||||
* All payment sessions are updated and cart totals are recalculated afterwards.
|
||||
@@ -248,17 +258,12 @@ export const useUpdatePaymentSession = (
|
||||
options?: UseMutationOptions<
|
||||
StoreCartsRes,
|
||||
Error,
|
||||
{
|
||||
/**
|
||||
* The payment provider's identifier.
|
||||
*/
|
||||
provider_id: string
|
||||
} & StorePostCartsCartPaymentSessionUpdateReq
|
||||
UpdatePaymentSessionReq
|
||||
>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
return useMutation(
|
||||
({ data, provider_id }) =>
|
||||
({ data, provider_id }: UpdatePaymentSessionReq) =>
|
||||
client.carts.updatePaymentSession(cartId, provider_id, { data }),
|
||||
options
|
||||
)
|
||||
|
||||
@@ -4,62 +4,342 @@ import {
|
||||
FileServiceGetUploadStreamResult,
|
||||
FileServiceUploadResult,
|
||||
GetUploadedFileType,
|
||||
MedusaContainer,
|
||||
UploadStreamDescriptorType,
|
||||
} from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* A file service class is defined in a TypeScript or JavaScript file that’s created in the `src/services` directory.
|
||||
* The class must extend the `AbstractFileService` class imported from the `@medusajs/medusa` package.
|
||||
*
|
||||
* Based on services’ naming conventions, the file’s name should be the slug version of the file service’s name
|
||||
* without `service`, and the class’s name should be the pascal case of the file service’s name following by `Service`.
|
||||
*
|
||||
* For example, create the file `src/services/local-file.ts` with the following content:
|
||||
*
|
||||
* ```ts title="src/services/local-file.ts"
|
||||
* import { AbstractFileService } from "@medusajs/medusa"
|
||||
* import {
|
||||
* DeleteFileType,
|
||||
* FileServiceGetUploadStreamResult,
|
||||
* FileServiceUploadResult,
|
||||
* GetUploadedFileType,
|
||||
* UploadStreamDescriptorType,
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* async upload(
|
||||
* fileData: Express.Multer.File
|
||||
* ): Promise<FileServiceUploadResult> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async uploadProtected(
|
||||
* fileData: Express.Multer.File
|
||||
* ): Promise<FileServiceUploadResult> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async delete(fileData: DeleteFileType): Promise<void> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async getUploadStreamDescriptor(
|
||||
* fileData: UploadStreamDescriptorType
|
||||
* ): Promise<FileServiceGetUploadStreamResult> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async getDownloadStream(
|
||||
* fileData: GetUploadedFileType
|
||||
* ): Promise<NodeJS.ReadableStream> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* async getPresignedDownloadUrl(
|
||||
* fileData: GetUploadedFileType
|
||||
* ): Promise<string> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* export default LocalFileService
|
||||
* ```
|
||||
*
|
||||
* :::note[Multer Typing]
|
||||
*
|
||||
* The examples implement a file service supporting local uploads.
|
||||
*
|
||||
* If you’re using TypeScript and you're following along with the implementation,
|
||||
* you should install the Multer types package in the root of your Medusa backend to resolve errors within your file service types:
|
||||
*
|
||||
* ```bash npm2yarn
|
||||
* npm install @types/multer
|
||||
* ```
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface IFileService extends TransactionBaseService {
|
||||
/**
|
||||
* upload file to fileservice
|
||||
* @param file Multer file from express multipart/form-data
|
||||
* */
|
||||
* This method is used to upload a file to the Medusa backend.
|
||||
*
|
||||
* @param {Express.Multer.File} file - A [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information).
|
||||
* The file is uploaded to a temporary directory by default. Among the file’s details, you can access the file’s path in the `path` property of the file object.
|
||||
* @returns {Promise<FileServiceUploadResult>} The details of the upload's result.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* // ...
|
||||
* async upload(
|
||||
* fileData: Express.Multer.File
|
||||
* ): Promise<FileServiceUploadResult> {
|
||||
* const filePath =
|
||||
* `${this.publicPath}/${fileData.originalname}`
|
||||
* fs.copyFileSync(fileData.path, filePath)
|
||||
* return {
|
||||
* url: `${this.serverUrl}/${filePath}`,
|
||||
* key: filePath,
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* :::tip
|
||||
*
|
||||
* This example does not account for duplicate names to maintain simplicity in this guide. So, an uploaded file can replace another existing file that has the same name.
|
||||
*
|
||||
* :::
|
||||
*/
|
||||
upload(file: Express.Multer.File): Promise<FileServiceUploadResult>
|
||||
|
||||
/**
|
||||
* upload private file to fileservice
|
||||
* @param file Multer file from express multipart/form-data
|
||||
* */
|
||||
* This method is used to upload a file to the Medusa backend, but to a protected storage. Typically, this would be used to store files that
|
||||
* shouldn’t be accessible by using the file’s URL or should only be accessible by authenticated users. For example, exported or imported
|
||||
* CSV files.
|
||||
*
|
||||
* @param {Express.Multer.File} file - A [multer file object](http://expressjs.com/en/resources/middleware/multer.html#file-information).
|
||||
* The file is uploaded to a temporary directory by default. Among the file’s details, you can access the file’s path in the `path` property of the file object.
|
||||
* @returns {Promise<FileServiceUploadResult>} The details of the upload's result.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* // ...
|
||||
* async uploadProtected(
|
||||
* fileData: Express.Multer.File
|
||||
* ): Promise<FileServiceUploadResult> {
|
||||
* const filePath =
|
||||
* `${this.protectedPath}/${fileData.originalname}`
|
||||
* fs.copyFileSync(fileData.path, filePath)
|
||||
* return {
|
||||
* url: `${this.serverUrl}/${filePath}`,
|
||||
* key: filePath
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* :::tip
|
||||
*
|
||||
* This example does not account for duplicate names to maintain simplicity in this guide. So, an uploaded file can replace another existing file that has the same name.
|
||||
*
|
||||
* :::
|
||||
*/
|
||||
uploadProtected(file: Express.Multer.File): Promise<FileServiceUploadResult>
|
||||
|
||||
/**
|
||||
* remove file from fileservice
|
||||
* @param fileData Remove file described by record
|
||||
* */
|
||||
* This method is used to delete a file from storage.
|
||||
*
|
||||
* @param {DeleteFileType} fileData - The details of the file to remove.
|
||||
* @returns {Promise<void>} Resolves when the file is deleted successfully.
|
||||
*
|
||||
* @example
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
*
|
||||
* async delete(
|
||||
* fileData: DeleteFileType
|
||||
* ): Promise<void> {
|
||||
* fs.rmSync(fileData.fileKey)
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
delete(fileData: DeleteFileType): Promise<void>
|
||||
|
||||
/**
|
||||
* upload file to fileservice from stream
|
||||
* @param fileData file metadata relevant for fileservice to create and upload the file
|
||||
* */
|
||||
* This method is used to retrieve a write stream to be used to upload a file.
|
||||
*
|
||||
* @param {UploadStreamDescriptorType} fileData - The details of the file being uploaded.
|
||||
* @returns {Promise<FileServiceGetUploadStreamResult>} The result of the file-stream upload.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { Stream } from "stream"
|
||||
*
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* // ...
|
||||
* async getUploadStreamDescriptor({
|
||||
* name,
|
||||
* ext,
|
||||
* isPrivate = true,
|
||||
* }: UploadStreamDescriptorType
|
||||
* ): Promise<FileServiceGetUploadStreamResult> {
|
||||
* const filePath = `${isPrivate ?
|
||||
* this.publicPath : this.protectedPath
|
||||
* }/${name}.${ext}`
|
||||
*
|
||||
* const pass = new Stream.PassThrough()
|
||||
* const writeStream = fs.createWriteStream(filePath)
|
||||
*
|
||||
* pass.pipe(writeStream)
|
||||
*
|
||||
* return {
|
||||
* writeStream: pass,
|
||||
* promise: Promise.resolve(),
|
||||
* url: `${this.serverUrl}/${filePath}`,
|
||||
* fileKey: filePath,
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
getUploadStreamDescriptor(
|
||||
fileData: UploadStreamDescriptorType
|
||||
): Promise<FileServiceGetUploadStreamResult>
|
||||
|
||||
/**
|
||||
* download file from fileservice as stream
|
||||
* @param fileData file metadata relevant for fileservice to download the file
|
||||
* @returns readable stream of the file to download
|
||||
* */
|
||||
* This method is used to retrieve a read stream for a file, which can then be used to download the file.
|
||||
*
|
||||
* @param {GetUploadedFileType} fileData - The details of the file.
|
||||
* @returns {Promise<NodeJS.ReadableStream>} The [read stream](https://nodejs.org/api/webstreams.html#class-readablestream) to read and download the file.
|
||||
*
|
||||
* @example
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
*
|
||||
* async getDownloadStream({
|
||||
* fileKey,
|
||||
* isPrivate = true,
|
||||
* }: GetUploadedFileType
|
||||
* ): Promise<NodeJS.ReadableStream> {
|
||||
* const filePath = `${isPrivate ?
|
||||
* this.publicPath : this.protectedPath
|
||||
* }/${fileKey}`
|
||||
* const readStream = fs.createReadStream(filePath)
|
||||
*
|
||||
* return readStream
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
getDownloadStream(
|
||||
fileData: GetUploadedFileType
|
||||
): Promise<NodeJS.ReadableStream>
|
||||
|
||||
/**
|
||||
* Generate a presigned download url to obtain a file
|
||||
* @param fileData file metadata relevant for fileservice to download the file
|
||||
* @returns presigned url to download the file
|
||||
* */
|
||||
* This method is used to retrieve a download URL of the file. For some file services, such as S3, a presigned URL indicates a temporary URL to get access to a file.
|
||||
*
|
||||
* If your file service doesn’t perform or offer a similar functionality, you can just return the URL to download the file.
|
||||
*
|
||||
* @param {GetUploadedFileType} fileData - The details of the file.
|
||||
* @returns {Promise<string>} The presigned URL to download the file
|
||||
*
|
||||
* @example
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
*
|
||||
* async getPresignedDownloadUrl({
|
||||
* fileKey,
|
||||
* isPrivate = true,
|
||||
* }: GetUploadedFileType
|
||||
* ): Promise<string> {
|
||||
* // Local upload doesn't provide
|
||||
* // support for presigned URLs,
|
||||
* // so just return the file's URL.
|
||||
*
|
||||
* const filePath = `${isPrivate ?
|
||||
* this.publicPath : this.protectedPath
|
||||
* }/${fileKey}`
|
||||
* return `${this.serverUrl}/${filePath}`
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
getPresignedDownloadUrl(fileData: GetUploadedFileType): Promise<string>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractFileService
|
||||
extends TransactionBaseService
|
||||
implements IFileService
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isFileService = true
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isFileService(object): object is AbstractFileService {
|
||||
return object?.constructor?._isFileService
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your file service to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs,
|
||||
* you can initialize it in the constructor and use it in other methods in the service.
|
||||
*
|
||||
* Additionally, if you’re creating your file service as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin,
|
||||
* you can access them in the constructor.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this file service is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { Logger } from "@medusajs/medusa"
|
||||
* import * as fs from "fs"
|
||||
*
|
||||
* class LocalFileService extends AbstractFileService {
|
||||
* // can also be replaced by an environment variable
|
||||
* // or a plugin option
|
||||
* protected serverUrl = "http://localhost:9000"
|
||||
* protected publicPath = "uploads"
|
||||
* protected protectedPath = "protected-uploads"
|
||||
* protected logger_: Logger
|
||||
*
|
||||
* constructor({ logger }: InjectedDependencies) {
|
||||
* // @ts-ignore
|
||||
* super(...arguments)
|
||||
* this.logger_ = logger
|
||||
*
|
||||
* // for public uploads
|
||||
* if (!fs.existsSync(this.publicPath)) {
|
||||
* fs.mkdirSync(this.publicPath)
|
||||
* }
|
||||
*
|
||||
* // for protected uploads
|
||||
* if (!fs.existsSync(this.protectedPath)) {
|
||||
* fs.mkdirSync(this.protectedPath)
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
abstract upload(
|
||||
fileData: Express.Multer.File
|
||||
): Promise<FileServiceUploadResult>
|
||||
|
||||
@@ -411,6 +411,7 @@ export abstract class AbstractFulfillmentService
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your fulfillment provider to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
* Additionally, if you’re creating your fulfillment provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor.
|
||||
*
|
||||
|
||||
@@ -1,18 +1,196 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { TransactionBaseService } from "./transaction-base-service"
|
||||
|
||||
type ReturnedData = {
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The details of a sent or resent notification.
|
||||
*/
|
||||
export type ReturnedData = {
|
||||
/**
|
||||
* The receiver of the Notification. For example, if you sent an email to the customer then `to` is the email address of the customer.
|
||||
* In other cases, it might be a phone number or a username.
|
||||
*/
|
||||
to: string
|
||||
/**
|
||||
* The status of the sent notification. There are no restriction on the returned status.
|
||||
*/
|
||||
status: string
|
||||
/**
|
||||
* The data used to send the Notification. For example, if you sent an order confirmation email to the customer, then the `data` object
|
||||
* might include the order items or the subject of the email. This `data` is necessary if the notification is resent later as you can use the same data.
|
||||
*/
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* :::note[Prerequisites]
|
||||
*
|
||||
* Before creating a Notification Provider, [install an event bus module](https://docs.medusajs.com/development/events/modules/redis).
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* A Notification Provider is a provider that handles sending and resending of notifications.
|
||||
*
|
||||
* To create a Notification Provider, create a TypeScript or JavaScript file in `src/services`. The name of the file is the name of the provider
|
||||
* (for example, `sendgrid.ts`). The file must export a class that extends the `AbstractNotificationService` class imported from `@medusajs/medusa`.
|
||||
*
|
||||
* For example, create the file `src/services/email-sender.ts` with the following content:
|
||||
*
|
||||
* ```ts title="src/services/email-sender.ts"
|
||||
* import { AbstractNotificationService } from "@medusajs/medusa"
|
||||
* import { EntityManager } from "typeorm"
|
||||
*
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* protected manager_: EntityManager
|
||||
* protected transactionManager_: EntityManager
|
||||
*
|
||||
* sendNotification(
|
||||
* event: string,
|
||||
* data: unknown,
|
||||
* attachmentGenerator: unknown
|
||||
* ): Promise<{
|
||||
* to: string;
|
||||
* status: string;
|
||||
* data: Record<string, unknown>;
|
||||
* }> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* resendNotification(
|
||||
* notification: unknown,
|
||||
* config: unknown,
|
||||
* attachmentGenerator: unknown
|
||||
* ): Promise<{
|
||||
* to: string;
|
||||
* status: string;
|
||||
* data: Record<string, unknown>;
|
||||
* }> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* export default EmailSenderService
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* ## Identifier Property
|
||||
*
|
||||
* The `NotificationProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the notification provider
|
||||
* class is used when the Notification Provider is created in the database.
|
||||
*
|
||||
* The value of this property is also used later when you want to subscribe the Notification Provider to events in a [Loader](https://docs.medusajs.com/development/loaders/overview).
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```ts
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* static identifier = "email-sender"
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface INotificationService extends TransactionBaseService {
|
||||
/**
|
||||
* When an event is triggered that your Notification Provider is registered as a handler for, the [`NotificationService`](https://docs.medusajs.com/references/services/classes/services.NotificationService)
|
||||
* in the Medusa backend executes this method of your Notification Provider.
|
||||
*
|
||||
* In this method, you can perform the necessary operation to send the Notification. For example, you can send an email to the customer when they place an order.
|
||||
*
|
||||
* @param {string} event - The name of the event that was triggered. For example, `order.placed`.
|
||||
* @param {unknown} data - The data payload of the event that was triggered. For example, if the `order.placed` event is triggered,
|
||||
* the `eventData` object contains the property `id` which is the ID of the order that was placed. You can refer to the
|
||||
* [Events reference](https://docs.medusajs.com/development/events/events-list) for information on all events and their payloads.
|
||||
* @param {unknown} attachmentGenerator - If you’ve previously register an attachment generator to the `NotificationService` using the
|
||||
* [`registerAttachmentGenerator`](https://docs.medusajs.com/references/services/classes/services.NotificationService#registerattachmentgenerator) method,
|
||||
* you have access to it here. You can use the `attachmentGenerator` to generate on-demand invoices or other documents. The default value of this parameter is `null`.
|
||||
* @returns {Promise<ReturnedData>} The sending details.
|
||||
*
|
||||
* @example
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* // ...
|
||||
* async sendNotification(
|
||||
* event: string,
|
||||
* data: any,
|
||||
* attachmentGenerator: unknown
|
||||
* ): Promise<{
|
||||
* to: string;
|
||||
* status: string;
|
||||
* data: Record<string, unknown>;
|
||||
* }> {
|
||||
* if (event === "order.placed") {
|
||||
* // retrieve order
|
||||
* const order = await this.orderService.retrieve(data.id)
|
||||
* // TODO send email
|
||||
*
|
||||
* console.log("Notification sent")
|
||||
* return {
|
||||
* to: order.email,
|
||||
* status: "done",
|
||||
* data: {
|
||||
* // any data necessary to send the email
|
||||
* // for example:
|
||||
* subject: "You placed a new order!",
|
||||
* items: order.items,
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
sendNotification(
|
||||
event: string,
|
||||
data: unknown,
|
||||
attachmentGenerator: unknown
|
||||
): Promise<ReturnedData>
|
||||
|
||||
/**
|
||||
* This method is used to resend notifications, which is typically triggered by the
|
||||
* [Resend Notification API Route](https://docs.medusajs.com/api/admin#notifications_postnotificationsnotificationresend).
|
||||
*
|
||||
* @param {unknown} notification - The original [Notification record](https://docs.medusajs.com/references/entities/classes/Notification) that was created after you sent the
|
||||
* notification with `sendNotification`. It includes the `to` and `data` attributes which are populated originally using the `to` and `data` properties of
|
||||
* the object you return in {@link sendNotification}.
|
||||
* @param {unknown} config - The new configuration used to resend the notification. The [Resend Notification API Route](https://docs.medusajs.com/api/admin#notifications_postnotificationsnotificationresend),
|
||||
* allows you to pass a new `to` field. If specified, it will be available in this config object.
|
||||
* @param {unknown} attachmentGenerator - f you’ve previously register an attachment generator to the `NotificationService` using the
|
||||
* [`registerAttachmentGenerator`](https://docs.medusajs.com/references/services/classes/services.NotificationService#registerattachmentgenerator) method,
|
||||
* you have access to it here. You can use the `attachmentGenerator` to generate on-demand invoices or other documents. The default value of this parameter is `null`.
|
||||
* @returns {Promise<ReturnedData>} The resend details.
|
||||
*
|
||||
* @example
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* // ...
|
||||
* async resendNotification(
|
||||
* notification: any,
|
||||
* config: any,
|
||||
* attachmentGenerator: unknown
|
||||
* ): Promise<{
|
||||
* to: string;
|
||||
* status: string;
|
||||
* data: Record<string, unknown>;
|
||||
* }> {
|
||||
* // check if the receiver should be changed
|
||||
* const to: string = config.to || notification.to
|
||||
*
|
||||
* // TODO resend the notification using the same data
|
||||
* // that is saved under notification.data
|
||||
*
|
||||
* console.log("Notification resent")
|
||||
* return {
|
||||
* to,
|
||||
* status: "done",
|
||||
* data: notification.data, // make changes to the data
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
resendNotification(
|
||||
notification: unknown,
|
||||
config: unknown,
|
||||
@@ -20,21 +198,78 @@ export interface INotificationService extends TransactionBaseService {
|
||||
): Promise<ReturnedData>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractNotificationService
|
||||
extends TransactionBaseService
|
||||
implements INotificationService
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isNotificationService = true
|
||||
static identifier: string
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isNotificationService(object): boolean {
|
||||
return object?.constructor?._isNotificationService
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
getIdentifier(): string {
|
||||
return (this.constructor as any).identifier
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your notification provider to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs,
|
||||
* you can initialize it in the constructor and use it in other methods in the service.
|
||||
*
|
||||
* Additionally, if you’re creating your notification provider as an external plugin to be installed on any Medusa backend and you want to access the options
|
||||
* added for the plugin, you can access it in the constructor.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this notification provider is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { AbstractNotificationService, OrderService } from "@medusajs/medusa"
|
||||
* import { EntityManager } from "typeorm"
|
||||
*
|
||||
* class EmailSenderService extends AbstractNotificationService {
|
||||
* // ...
|
||||
* protected orderService: OrderService
|
||||
*
|
||||
* constructor(container, options) {
|
||||
* super(container)
|
||||
* // you can access options here in case you're
|
||||
* // using a plugin
|
||||
*
|
||||
* this.orderService = container.orderService
|
||||
*
|
||||
* // you can also initialize a client that
|
||||
* // communicates with a third-party service.
|
||||
* this.client = new Client(options)
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* export default EmailSenderService
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
abstract sendNotification(
|
||||
event: string,
|
||||
data: unknown,
|
||||
|
||||
@@ -1,24 +1,153 @@
|
||||
import { MoneyAmount } from "../models"
|
||||
import { PriceListType } from "../types/price-list"
|
||||
import { TaxServiceRate } from "../types/tax-service"
|
||||
import { ITransactionBaseService } from "@medusajs/types"
|
||||
import { ITransactionBaseService, MedusaContainer } from "@medusajs/types"
|
||||
import { TransactionBaseService } from "./transaction-base-service"
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* The price selection strategy retrieves the best price for a product variant for a specific context such as selected region, taxes applied,
|
||||
* the quantity in cart, and more.
|
||||
*
|
||||
* Medusa provides a default price selection strategy, but you can override it. A price selecion strategy is a TypeScript or JavaScript file in the `src/strategies` directory of your Medusa backend project. It exports a class that extends the `AbstractPriceSelectionStrategy` class.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```ts title="src/strategies/price.ts"
|
||||
* import {
|
||||
* AbstractPriceSelectionStrategy,
|
||||
* PriceSelectionContext,
|
||||
* PriceSelectionResult,
|
||||
* } from "@medusajs/medusa"
|
||||
*
|
||||
* export default class MyStrategy extends
|
||||
* AbstractPriceSelectionStrategy {
|
||||
*
|
||||
* async calculateVariantPrice(
|
||||
* data: {
|
||||
* variantId: string;
|
||||
* quantity?: number
|
||||
* }[],
|
||||
* context: PriceSelectionContext
|
||||
* ): Promise<Map<string, PriceSelectionResult>> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface IPriceSelectionStrategy extends ITransactionBaseService {
|
||||
/**
|
||||
* Calculate the original and discount price for a given variant in a set of
|
||||
* circumstances described in the context.
|
||||
* @return pricing details in an object containing the calculated lowest price,
|
||||
* the default price an all valid prices for the given variant
|
||||
* This method retrieves one or more product variants' prices. It's used when retrieving product variants or their associated line items.
|
||||
* It's also used when retrieving other entities that product variants and line items belong to, such as products and carts respectively.
|
||||
*
|
||||
* @param data - The necessary data to perform the price selection for each variant ID.
|
||||
* @param context - The context of the price selection.
|
||||
* @returns {Promise<Map<string, PriceSelectionResult>>} A map, each key is an ID of a variant, and its value is an object holding the price selection result.
|
||||
*
|
||||
* @example
|
||||
* For example, here's a snippet of how the price selection strategy is implemented in the Medusa backend:
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* AbstractPriceSelectionStrategy,
|
||||
* CustomerService,
|
||||
* PriceSelectionContext,
|
||||
* PriceSelectionResult,
|
||||
* } from "@medusajs/medusa"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* customerService: CustomerService
|
||||
* }
|
||||
*
|
||||
* export default class MyStrategy extends
|
||||
* AbstractPriceSelectionStrategy {
|
||||
*
|
||||
* async calculateVariantPrice(
|
||||
* data: {
|
||||
* variantId: string
|
||||
* quantity?: number
|
||||
* }[],
|
||||
* context: PriceSelectionContext
|
||||
* ): Promise<Map<string, PriceSelectionResult>> {
|
||||
* const dataMap = new Map(data.map((d) => [d.variantId, d]))
|
||||
*
|
||||
* const cacheKeysMap = new Map(
|
||||
* data.map(({ variantId, quantity }) => [
|
||||
* variantId,
|
||||
* this.getCacheKey(variantId, { ...context, quantity }),
|
||||
* ])
|
||||
* )
|
||||
*
|
||||
* const nonCachedData: {
|
||||
* variantId: string
|
||||
* quantity?: number
|
||||
* }[] = []
|
||||
*
|
||||
* const variantPricesMap = new Map<string, PriceSelectionResult>()
|
||||
*
|
||||
* if (!context.ignore_cache) {
|
||||
* const cacheHits = await promiseAll(
|
||||
* [...cacheKeysMap].map(async ([, cacheKey]) => {
|
||||
* return await this.cacheService_.get<PriceSelectionResult>(cacheKey)
|
||||
* })
|
||||
* )
|
||||
*
|
||||
* if (!cacheHits.length) {
|
||||
* nonCachedData.push(...dataMap.values())
|
||||
* }
|
||||
*
|
||||
* for (const [index, cacheHit] of cacheHits.entries()) {
|
||||
* const variantId = data[index].variantId
|
||||
* if (cacheHit) {
|
||||
* variantPricesMap.set(variantId, cacheHit)
|
||||
* continue
|
||||
* }
|
||||
*
|
||||
* nonCachedData.push(dataMap.get(variantId)!)
|
||||
* }
|
||||
* } else {
|
||||
* nonCachedData.push(...dataMap.values())
|
||||
* }
|
||||
*
|
||||
* let results: Map<string, PriceSelectionResult> = new Map()
|
||||
*
|
||||
* if (
|
||||
* this.featureFlagRouter_.isFeatureEnabled(
|
||||
* TaxInclusivePricingFeatureFlag.key
|
||||
* )
|
||||
* ) {
|
||||
* results = await this.calculateVariantPrice_new(nonCachedData, context)
|
||||
* } else {
|
||||
* results = await this.calculateVariantPrice_old(nonCachedData, context)
|
||||
* }
|
||||
*
|
||||
* await promiseAll(
|
||||
* [...results].map(async ([variantId, prices]) => {
|
||||
* variantPricesMap.set(variantId, prices)
|
||||
* if (!context.ignore_cache) {
|
||||
* await this.cacheService_.set(cacheKeysMap.get(variantId)!, prices)
|
||||
* }
|
||||
* })
|
||||
* )
|
||||
*
|
||||
* return variantPricesMap
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
calculateVariantPrice(
|
||||
data: {
|
||||
/**
|
||||
* The variant id of the variant for which to retrieve prices
|
||||
* The ID of the variant to retrieve its prices.
|
||||
*/
|
||||
variantId: string
|
||||
/**
|
||||
* The variant's quantity.
|
||||
* The variant's quantity in the cart, if available.
|
||||
*/
|
||||
quantity?: number
|
||||
}[],
|
||||
@@ -29,25 +158,114 @@ export interface IPriceSelectionStrategy extends ITransactionBaseService {
|
||||
): Promise<Map<string, PriceSelectionResult>>
|
||||
|
||||
/**
|
||||
* Notify price selection strategy that variants prices have been updated.
|
||||
* @param variantIds The ids of the updated variants
|
||||
* This method is called when prices of product variants have changed.
|
||||
* You can use it to invalidate prices stored in the cache.
|
||||
*
|
||||
* @param {string[]} variantIds - The IDs of the updated variants.
|
||||
* @returns {Promise<void>} Resolves after any necessary actions are performed.
|
||||
*
|
||||
* @example
|
||||
* For example, this is how this method is implemented in the Medusa backend's default
|
||||
* price selection strategy:
|
||||
*
|
||||
* ```ts
|
||||
* import {
|
||||
* AbstractPriceSelectionStrategy,
|
||||
* CustomerService,
|
||||
* } from "@medusajs/medusa"
|
||||
* import { promiseAll } from "@medusajs/utils"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* customerService: CustomerService
|
||||
* }
|
||||
*
|
||||
* export default class MyStrategy extends
|
||||
* AbstractPriceSelectionStrategy {
|
||||
*
|
||||
* public async onVariantsPricesUpdate(variantIds: string[]): Promise<void> {
|
||||
* await promiseAll(
|
||||
* variantIds.map(
|
||||
* async (id: string) => await this.cacheService_.invalidate(`ps:${id}:*`)
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
* Learn more about the cache service in [this documentation](https://docs.medusajs.com/development/cache/overview).
|
||||
*
|
||||
* :::
|
||||
*/
|
||||
onVariantsPricesUpdate(variantIds: string[]): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractPriceSelectionStrategy
|
||||
extends TransactionBaseService
|
||||
implements IPriceSelectionStrategy
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isPriceSelectionStrategy = true
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isPriceSelectionStrategy(object): boolean {
|
||||
return object?.constructor?._isPriceSelectionStrategy
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your price-selection strategy to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this price-selection strategy is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import {
|
||||
* AbstractPriceSelectionStrategy,
|
||||
* CustomerService,
|
||||
* } from "@medusajs/medusa"
|
||||
* type InjectedDependencies = {
|
||||
* customerService: CustomerService
|
||||
* }
|
||||
*
|
||||
* class MyStrategy extends
|
||||
* AbstractPriceSelectionStrategy {
|
||||
*
|
||||
* protected customerService_: CustomerService
|
||||
*
|
||||
* constructor(container: InjectedDependencies) {
|
||||
* super(container)
|
||||
* this.customerService_ = container.customerService
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* export default MyStrategy
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
public abstract calculateVariantPrice(
|
||||
data: {
|
||||
variantId: string
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
taxRates: TaxServiceRate[]
|
||||
quantity?: number
|
||||
}[],
|
||||
@@ -59,18 +277,59 @@ export abstract class AbstractPriceSelectionStrategy
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The context of the price selection.
|
||||
*/
|
||||
export type PriceSelectionContext = {
|
||||
/**
|
||||
* The cart's ID. This is used when the prices are being retrieved for the variant of a line item,
|
||||
* as it is used to determine the current region and currency code of the context.
|
||||
*/
|
||||
cart_id?: string
|
||||
/**
|
||||
* The ID of the customer viewing the variant.
|
||||
*/
|
||||
customer_id?: string
|
||||
/**
|
||||
* The region's ID.
|
||||
*/
|
||||
region_id?: string
|
||||
/**
|
||||
* The quantity of the item in the cart. This is used to filter out price lists that have
|
||||
* `min_quantity` or `max_quantity` conditions set.
|
||||
*/
|
||||
quantity?: number
|
||||
/**
|
||||
* The currency code the customer is using.
|
||||
*/
|
||||
currency_code?: string
|
||||
/**
|
||||
* Whether the price list's prices should be retrieved or not.
|
||||
*/
|
||||
include_discount_prices?: boolean
|
||||
/**
|
||||
* The tax rates to be applied. This is only used for
|
||||
* [Tax-Inclusive Pricing](https://docs.medusajs.com/modules/taxes/inclusive-pricing).
|
||||
*/
|
||||
tax_rates?: TaxServiceRate[]
|
||||
/**
|
||||
* Whether to calculate the prices even if the value of an earlier price calculation
|
||||
* is available in the cache.
|
||||
*/
|
||||
ignore_cache?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @enum
|
||||
*
|
||||
* The type of default price type.
|
||||
*/
|
||||
enum DefaultPriceType {
|
||||
/**
|
||||
* The `calculatedPrice` is the original price.
|
||||
*/
|
||||
DEFAULT = "default",
|
||||
}
|
||||
|
||||
@@ -78,11 +337,38 @@ enum DefaultPriceType {
|
||||
export type PriceType = DefaultPriceType | PriceListType
|
||||
export const PriceType = { ...DefaultPriceType, ...PriceListType }
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The price selection result of a variant.
|
||||
*/
|
||||
export type PriceSelectionResult = {
|
||||
/**
|
||||
* The original price of the variant which depends on the selected region or currency code in the context object.
|
||||
* If both region ID and currency code are available in the context object, the region has higher precedence.
|
||||
*/
|
||||
originalPrice: number | null
|
||||
/**
|
||||
* Whether the original price includes taxes or not. This is only available
|
||||
* for [Tax-Inclusive Pricing](https://docs.medusajs.com/modules/taxes/inclusive-pricing).
|
||||
*/
|
||||
originalPriceIncludesTax?: boolean | null
|
||||
/**
|
||||
* The lowest price among the prices of the product variant retrieved using the context object.
|
||||
*/
|
||||
calculatedPrice: number | null
|
||||
/**
|
||||
* Whether the calculated price includes taxes or not.
|
||||
* This is only available for [Tax-Inclusive Pricing](https://docs.medusajs.com/modules/taxes/inclusive-pricing).
|
||||
*/
|
||||
calculatedPriceIncludesTax?: boolean | null
|
||||
/**
|
||||
* The type of price applied in `calculatedPrice`.
|
||||
*/
|
||||
calculatedPriceType?: PriceType
|
||||
prices: MoneyAmount[] // prices is an array of all possible price for the input customer and region prices
|
||||
/**
|
||||
* All possible prices of the variant that are retrieved using the `context` object.
|
||||
* It can include its original price and its price lists if there are any.
|
||||
*/
|
||||
prices: MoneyAmount[]
|
||||
}
|
||||
|
||||
@@ -3,15 +3,95 @@ import { TaxCalculationContext } from "./tax-service"
|
||||
import { LineItemTaxLine } from "../models/line-item-tax-line"
|
||||
import { ShippingMethodTaxLine } from "../models/shipping-method-tax-line"
|
||||
import { TransactionBaseService } from "./transaction-base-service"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* A tax calculation strategy is used to calculate taxes when calculating a cart's totals. The Medusa
|
||||
* backend provides a tax calculation strategy that handles calculating the taxes, taking into account the
|
||||
* defined tax rates and settings such as whether tax-inclusive pricing is enabled.
|
||||
*
|
||||
* You can override the tax calculation strategy to implement different calculation logic or to
|
||||
* integrate a third-party service that handles the tax calculation. You can override it either
|
||||
* in a Medusa backend setup or in a plugin.
|
||||
*
|
||||
* A tax calculation strategy should be defined in a TypeScript or JavaScript file created under the `src/strategies` directory.
|
||||
* The class must also implement the `ITaxCalculationStrategy` interface imported from the `@medusajs/medusa` package.
|
||||
*
|
||||
* For example, you can create the file `src/strategies/tax-calculation.ts` with the following content:
|
||||
*
|
||||
* ```ts title="src/strategies/tax-calculation.ts"
|
||||
* import {
|
||||
* ITaxCalculationStrategy,
|
||||
* LineItem,
|
||||
* LineItemTaxLine,
|
||||
* ShippingMethodTaxLine,
|
||||
* TaxCalculationContext,
|
||||
* } from "@medusajs/medusa"
|
||||
*
|
||||
* class TaxCalculationStrategy
|
||||
* implements ITaxCalculationStrategy {
|
||||
*
|
||||
* async calculate(
|
||||
* items: LineItem[],
|
||||
* taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[],
|
||||
* calculationContext: TaxCalculationContext
|
||||
* ): Promise<number> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* export default TaxCalculationStrategy
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface ITaxCalculationStrategy {
|
||||
/**
|
||||
* Calculates the tax amount for a given set of line items under applicable
|
||||
* This method calculates the tax amount for a given set of line items under applicable
|
||||
* tax conditions and calculation contexts.
|
||||
* @param items - the line items to calculate the tax total for
|
||||
* @param taxLines - the tax lines that applies to the calculation
|
||||
* @param calculationContext - other details relevant for the calculation
|
||||
* @return the tax total
|
||||
*
|
||||
* This method is used whenever taxes are calculated. If automatic tax calculation is disabled in a region,
|
||||
* then it's only triggered when taxes are calculated manually as explained in
|
||||
* [this guide](https://docs.medusajs.com/modules/taxes/storefront/manual-calculation).
|
||||
*
|
||||
* @param {LineItem[]} items - The line items to calculate the tax total for.
|
||||
* @param {(ShippingMethodTaxLine | LineItemTaxLine)[]} taxLines - The tax lines used for the calculation
|
||||
* @param {TaxCalculationContext} calculationContext - Other details relevant for the calculation
|
||||
* @returns {Promise<number>} The calculated tax total
|
||||
*
|
||||
* @example
|
||||
* An example of the general implementation of this method in the Medusa backend's tax calculation strategy:
|
||||
*
|
||||
* ```ts
|
||||
* async calculate(
|
||||
* items: LineItem[],
|
||||
* taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[],
|
||||
* calculationContext: TaxCalculationContext
|
||||
* ): Promise<number> {
|
||||
* const lineItemsTaxLines = taxLines.filter(
|
||||
* (tl) => "item_id" in tl
|
||||
* ) as LineItemTaxLine[]
|
||||
* const shippingMethodsTaxLines = taxLines.filter(
|
||||
* (tl) => "shipping_method_id" in tl
|
||||
* ) as ShippingMethodTaxLine[]
|
||||
*
|
||||
* const lineItemsTax = this.calculateLineItemsTax(
|
||||
* items,
|
||||
* lineItemsTaxLines,
|
||||
* calculationContext
|
||||
* )
|
||||
*
|
||||
* const shippingMethodsTax = this.calculateShippingMethodsTax(
|
||||
* calculationContext.shipping_methods,
|
||||
* shippingMethodsTaxLines
|
||||
* )
|
||||
*
|
||||
* return Math.round(lineItemsTax + shippingMethodsTax)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
calculate(
|
||||
items: LineItem[],
|
||||
@@ -20,12 +100,21 @@ export interface ITaxCalculationStrategy {
|
||||
): Promise<number>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractTaxCalculationStrategy
|
||||
extends TransactionBaseService
|
||||
implements ITaxCalculationStrategy
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isTaxCalculationStrategy = true
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isTaxCalculationStrategy(object): boolean {
|
||||
return (
|
||||
typeof object.calculate === "function" ||
|
||||
@@ -33,6 +122,46 @@ export abstract class AbstractTaxCalculationStrategy
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your tax calculation strategy to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
* Additionally, if you’re creating your tax calculation strategy as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this tax calculation strategy is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* import {
|
||||
* ITaxCalculationStrategy,
|
||||
* LineItemService,
|
||||
* } from "@medusajs/medusa"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* lineItemService: LineItemService
|
||||
* }
|
||||
*
|
||||
* class TaxCalculationStrategy
|
||||
* implements ITaxCalculationStrategy {
|
||||
*
|
||||
* protected readonly lineItemService_: LineItemService
|
||||
*
|
||||
* constructor({ lineItemService }: InjectedDependencies) {
|
||||
* this.lineItemService_ = lineItemService
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* export default TaxCalculationStrategy
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
abstract calculate(
|
||||
items: LineItem[],
|
||||
taxLines: (ShippingMethodTaxLine | LineItemTaxLine)[],
|
||||
|
||||
@@ -6,22 +6,35 @@ import { Customer } from "../models/customer"
|
||||
import { ProviderTaxLine, TaxServiceRate } from "../types/tax-service"
|
||||
import { LineAllocationsMap } from "../types/totals"
|
||||
import { TransactionBaseService } from "./transaction-base-service"
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* A shipping method and the tax rates that have been configured to apply to the
|
||||
* A shipping method and the tax rates configured to apply to the
|
||||
* shipping method.
|
||||
*/
|
||||
export type ShippingTaxCalculationLine = {
|
||||
/**
|
||||
* The shipping method to calculate taxes for.
|
||||
*/
|
||||
shipping_method: ShippingMethod
|
||||
/**
|
||||
* The rates applicable on the shipping method.
|
||||
*/
|
||||
rates: TaxServiceRate[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A line item and the tax rates that have been configured to apply to the
|
||||
* A line item and the tax rates configured to apply to the
|
||||
* product contained in the line item.
|
||||
*/
|
||||
export type ItemTaxCalculationLine = {
|
||||
/**
|
||||
* The line item to calculate taxes for.
|
||||
*/
|
||||
item: LineItem
|
||||
/**
|
||||
* The rates applicable on the item.
|
||||
*/
|
||||
rates: TaxServiceRate[]
|
||||
}
|
||||
|
||||
@@ -30,27 +43,135 @@ export type ItemTaxCalculationLine = {
|
||||
* the items are going.
|
||||
*/
|
||||
export type TaxCalculationContext = {
|
||||
/**
|
||||
* The shipping address used in the cart.
|
||||
*/
|
||||
shipping_address: Address | null
|
||||
/**
|
||||
* The customer that the cart belongs to.
|
||||
*/
|
||||
customer: Customer
|
||||
/**
|
||||
* The cart's region.
|
||||
*/
|
||||
region: Region
|
||||
/**
|
||||
* Whether the cart is used in a return flow.
|
||||
*/
|
||||
is_return: boolean
|
||||
/**
|
||||
* The shipping methods used in the cart.
|
||||
*/
|
||||
shipping_methods: ShippingMethod[]
|
||||
/**
|
||||
* The gift cards and discounts applied on line items.
|
||||
* Each object key or property is an ID of a line item
|
||||
*/
|
||||
allocation_map: LineAllocationsMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to be implemented by tax provider plugins. The interface defines a
|
||||
* single method `getTaxLines` that returns numerical rates to apply to line
|
||||
* items and shipping methods.
|
||||
* ## Overview
|
||||
*
|
||||
* A tax provider is used to retrieve the tax lines in a cart. The Medusa backend provides a default `system` provider. You can create your own tax provider,
|
||||
* either in a plugin or directly in your Medusa backend, then use it in any region.
|
||||
*
|
||||
* A tax provider class is defined in a TypeScript or JavaScript file under the `src/services` directory and the class must extend the
|
||||
* `AbstractTaxService` class imported from `@medusajs/medusa`. The file's name is the tax provider's class name as a slug and without the word `Service`.
|
||||
*
|
||||
* For example, you can create the file `src/services/my-tax.ts` with the following content:
|
||||
*
|
||||
* ```ts title="src/services/my-tax.ts"
|
||||
* import {
|
||||
* AbstractTaxService,
|
||||
* ItemTaxCalculationLine,
|
||||
* ShippingTaxCalculationLine,
|
||||
* TaxCalculationContext,
|
||||
* } from "@medusajs/medusa"
|
||||
* import {
|
||||
* ProviderTaxLine,
|
||||
* } from "@medusajs/medusa/dist/types/tax-service"
|
||||
*
|
||||
* class MyTaxService extends AbstractTaxService {
|
||||
* async getTaxLines(
|
||||
* itemLines: ItemTaxCalculationLine[],
|
||||
* shippingLines: ShippingTaxCalculationLine[],
|
||||
* context: TaxCalculationContext):
|
||||
* Promise<ProviderTaxLine[]> {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* export default MyTaxService
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* ## Identifier Property
|
||||
*
|
||||
* The `TaxProvider` entity has 2 properties: `identifier` and `is_installed`. The `identifier` property in the tax provider service is used when the tax provider is added to the database.
|
||||
*
|
||||
* The value of this property is also used to reference the tax provider throughout Medusa. For example, it is used to [change the tax provider](https://docs.medusajs.com/modules/taxes/admin/manage-tax-settings#change-tax-provider-of-a-region) to a region.
|
||||
*
|
||||
* ```ts title="src/services/my-tax.ts"
|
||||
* class MyTaxService extends AbstractTaxService {
|
||||
* static identifier = "my-tax"
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export interface ITaxService {
|
||||
/**
|
||||
* Retrieves the numerical tax lines for a calculation context.
|
||||
* @param itemLines - the line item calculation lines
|
||||
* @param itemLines - the shipping calculation lines
|
||||
* @param context - other details relevant to the tax determination
|
||||
* @return numerical tax rates that should apply to the provided calculation
|
||||
* lines
|
||||
* This method is used when retrieving the tax lines for line items and shipping methods.
|
||||
* This occurs during checkout or when calculating totals for orders, swaps, or returns.
|
||||
*
|
||||
* @param {ItemTaxCalculationLine[]} itemLines - The line item lines to calculate taxes for.
|
||||
* @param {ShippingTaxCalculationLine[]} shippingLines - The shipping method lines to calculate taxes for.
|
||||
* @param {TaxCalculationContext} context - Context relevant and useful for the taxes calculation.
|
||||
* @return {Promise<ProviderTaxLine[]>} The list of calculated line item and shipping method tax lines.
|
||||
* If an item in the array has the `shipping_method_id` property, then it's a shipping method tax line. Otherwise, if it has
|
||||
* the `item_id` property, then it's a line item tax line.
|
||||
*
|
||||
* @example
|
||||
* An example of how this method is implemented in the `system` provider implemented in the Medusa backend:
|
||||
*
|
||||
* ```ts
|
||||
* // ...
|
||||
*
|
||||
* class SystemTaxService extends AbstractTaxService {
|
||||
* // ...
|
||||
*
|
||||
* async getTaxLines(
|
||||
* itemLines: ItemTaxCalculationLine[],
|
||||
* shippingLines: ShippingTaxCalculationLine[],
|
||||
* context: TaxCalculationContext
|
||||
* ): Promise<ProviderTaxLine[]> {
|
||||
* let taxLines: ProviderTaxLine[] = itemLines.flatMap((l) => {
|
||||
* return l.rates.map((r) => ({
|
||||
* rate: r.rate || 0,
|
||||
* name: r.name,
|
||||
* code: r.code,
|
||||
* item_id: l.item.id,
|
||||
* }))
|
||||
* })
|
||||
*
|
||||
* taxLines = taxLines.concat(
|
||||
* shippingLines.flatMap((l) => {
|
||||
* return l.rates.map((r) => ({
|
||||
* rate: r.rate || 0,
|
||||
* name: r.name,
|
||||
* code: r.code,
|
||||
* shipping_method_id: l.shipping_method.id,
|
||||
* }))
|
||||
* })
|
||||
* )
|
||||
*
|
||||
* return taxLines
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getTaxLines(
|
||||
itemLines: ItemTaxCalculationLine[],
|
||||
@@ -59,17 +180,70 @@ export interface ITaxService {
|
||||
): Promise<ProviderTaxLine[]>
|
||||
}
|
||||
|
||||
/**
|
||||
* @parentIgnore activeManager_,atomicPhase_,shouldRetryTransaction_,withTransaction
|
||||
*/
|
||||
export abstract class AbstractTaxService
|
||||
extends TransactionBaseService
|
||||
implements ITaxService
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isTaxService = true
|
||||
protected static identifier: string
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isTaxService(object): boolean {
|
||||
return object?.constructor?._isTaxService
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your tax provider to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
* Additionally, if you’re creating your tax provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} config - If this tax provider is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { LineItemService } from "@medusajs/medusa"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* lineItemService: LineItemService
|
||||
* }
|
||||
*
|
||||
* class MyTaxService extends AbstractTaxService {
|
||||
* protected readonly lineItemService_: LineItemService
|
||||
*
|
||||
* constructor({ lineItemService }: InjectedDependencies) {
|
||||
* super(arguments[0])
|
||||
* this.lineItemService_ = lineItemService
|
||||
*
|
||||
* // you can also initialize a client that
|
||||
* // communicates with a third-party service.
|
||||
* this.client = new Client(options)
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* export default MyTaxService
|
||||
*/
|
||||
protected constructor(
|
||||
protected readonly container: MedusaContainer,
|
||||
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {
|
||||
super(container, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
public getIdentifier(): string {
|
||||
if (!(this.constructor as typeof AbstractTaxService).identifier) {
|
||||
throw new Error(`Missing static property "identifier".`)
|
||||
|
||||
@@ -9,8 +9,10 @@ import { ProviderTaxLine } from "../types/tax-service"
|
||||
class SystemTaxService extends AbstractTaxService {
|
||||
static identifier = "system"
|
||||
|
||||
constructor() {
|
||||
super({})
|
||||
constructor(...args: any[]) {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
super(...arguments)
|
||||
}
|
||||
|
||||
async getTaxLines(
|
||||
|
||||
@@ -31,10 +31,25 @@ export type TaxServiceRate = {
|
||||
* The tax line properties for a given shipping method.
|
||||
*/
|
||||
export type ProviderShippingMethodTaxLine = {
|
||||
/**
|
||||
* The tax rate.
|
||||
*/
|
||||
rate: number
|
||||
/**
|
||||
* The tax rate's name.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The tax code.
|
||||
*/
|
||||
code: string | null
|
||||
/**
|
||||
* Holds any necessary additional data to be added to the shipping method tax lines.
|
||||
*/
|
||||
metadata?: Record<string, unknown>
|
||||
/**
|
||||
* The shipping method's ID.
|
||||
*/
|
||||
shipping_method_id: string
|
||||
}
|
||||
|
||||
@@ -42,10 +57,25 @@ export type ProviderShippingMethodTaxLine = {
|
||||
* The tax line properties for a given line item.
|
||||
*/
|
||||
export type ProviderLineItemTaxLine = {
|
||||
/**
|
||||
* The tax rate.
|
||||
*/
|
||||
rate: number
|
||||
/**
|
||||
* The tax rate's name.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The tax code.
|
||||
*/
|
||||
code: string | null
|
||||
/**
|
||||
* The line item's ID.
|
||||
*/
|
||||
item_id: string
|
||||
/**
|
||||
* Holds any necessary additional data to be added to the line item tax lines.
|
||||
*/
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,16 @@ export type DiscountAllocation = {
|
||||
* allocations
|
||||
*/
|
||||
export type LineAllocationsMap = {
|
||||
[K: string]: { gift_card?: GiftCardAllocation; discount?: DiscountAllocation }
|
||||
[K: string]: {
|
||||
/**
|
||||
* The gift card applied on the line item.
|
||||
*/
|
||||
gift_card?: GiftCardAllocation
|
||||
/**
|
||||
* The discount applied on the line item.
|
||||
*/
|
||||
discount?: DiscountAllocation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,56 +5,541 @@ import {
|
||||
InternalModuleDeclaration,
|
||||
} from "../modules-sdk"
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* Options to pass to `express-session`.
|
||||
*/
|
||||
type SessionOptions = {
|
||||
/**
|
||||
* The name of the session ID cookie to set in the response (and read from in the request). The default value is `connect.sid`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#name) for more details.
|
||||
*/
|
||||
name?: string
|
||||
/**
|
||||
* Whether the session should be saved back to the session store, even if the session was never modified during the request. The default value is `true`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#resave) for more details.
|
||||
*/
|
||||
resave?: boolean
|
||||
/**
|
||||
* Whether the session identifier cookie should be force-set on every response. The default value is `false`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#rolling) for more details.
|
||||
*/
|
||||
rolling?: boolean
|
||||
/**
|
||||
* Whether a session that is "uninitialized" is forced to be saved to the store. The default value is `true`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#saveUninitialized) for more details.
|
||||
*/
|
||||
saveUninitialized?: boolean
|
||||
/**
|
||||
* The secret to sign the session ID cookie. By default, the value of `cookie_secret` is used.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#secret) for details.
|
||||
*/
|
||||
secret?: string
|
||||
/**
|
||||
* Used when calculating the `Expires` `Set-Cookie` attribute of cookies. By default, its value is `10 * 60 * 60 * 1000`.
|
||||
* Refer to [express-session’s documentation](https://www.npmjs.com/package/express-session#cookiemaxage) for details.
|
||||
*/
|
||||
ttl?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* HTTP compression configurations.
|
||||
*/
|
||||
export type HttpCompressionOptions = {
|
||||
/**
|
||||
* Whether HTTP compression is enabled. By default, it's `false`.
|
||||
*/
|
||||
enabled?: boolean
|
||||
/**
|
||||
* The level of zlib compression to apply to responses. A higher level will result in better compression but will take longer to complete.
|
||||
* A lower level will result in less compression but will be much faster. The default value is `6`.
|
||||
*/
|
||||
level?: number
|
||||
/**
|
||||
* How much memory should be allocated to the internal compression state. It's an integer in the range of 1 (minimum level) and 9 (maximum level).
|
||||
* The default value is `8`.
|
||||
*/
|
||||
memLevel?: number
|
||||
/**
|
||||
* The minimum response body size that compression is applied on. Its value can be the number of bytes or any string accepted by the
|
||||
* [bytes](https://www.npmjs.com/package/bytes) module. The default value is `1024`.
|
||||
*/
|
||||
threshold?: number | string
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* Essential configurations related to the Medusa backend, such as database and CORS configurations.
|
||||
*/
|
||||
export type ProjectConfigOptions = {
|
||||
redis_url?: string
|
||||
redis_prefix?: string
|
||||
redis_options?: RedisOptions
|
||||
|
||||
session_options?: SessionOptions
|
||||
|
||||
jwt_secret?: string
|
||||
/**
|
||||
* The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes.
|
||||
*
|
||||
* `store_cors` is a string used to specify the accepted URLs or patterns for store API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.
|
||||
*
|
||||
* Every origin in that list must either be:
|
||||
*
|
||||
* 1. A URL. For example, `http://localhost:8000`. The URL must not end with a backslash;
|
||||
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
|
||||
*
|
||||
* @example
|
||||
* Some example values of common use cases:
|
||||
*
|
||||
* ```bash
|
||||
* # Allow different ports locally starting with 800
|
||||
* STORE_CORS=/http:\/\/localhost:800\d+$/
|
||||
*
|
||||
* # Allow any origin ending with vercel.app. For example, storefront.vercel.app
|
||||
* STORE_CORS=/vercel\.app$/
|
||||
*
|
||||
* # Allow all HTTP requests
|
||||
* STORE_CORS=/http:\/\/.+/
|
||||
* ```
|
||||
*
|
||||
* Then, set the configuration in `medusa-config.js`:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* store_cors: process.env.STORE_CORS,
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If you’re adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* store_cors: "/vercel\\.app$/",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
store_cors?: string
|
||||
/**
|
||||
* The Medusa backend’s API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes.
|
||||
*
|
||||
* `admin_cors` is a string used to specify the accepted URLs or patterns for admin API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins.
|
||||
*
|
||||
* Every origin in that list must either be:
|
||||
*
|
||||
* 1. A URL. For example, `http://localhost:7001`. The URL must not end with a backslash;
|
||||
* 2. Or a regular expression pattern that can match more than one origin. For example, `.example.com`. The regex pattern that the backend tests for is `^([\/~@;%#'])(.*?)\1([gimsuy]*)$`.
|
||||
*
|
||||
* @example
|
||||
* Some example values of common use cases:
|
||||
*
|
||||
* ```bash
|
||||
* # Allow different ports locally starting with 700
|
||||
* ADMIN_CORS=/http:\/\/localhost:700\d+$/
|
||||
*
|
||||
* # Allow any origin ending with vercel.app. For example, admin.vercel.app
|
||||
* ADMIN_CORS=/vercel\.app$/
|
||||
*
|
||||
* # Allow all HTTP requests
|
||||
* ADMIN_CORS=/http:\/\/.+/
|
||||
* ```
|
||||
*
|
||||
* Then, set the configuration in `medusa-config.js`:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* admin_cors: process.env.ADMIN_CORS,
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If you’re adding the value directly within `medusa-config.js`, make sure to add an extra escaping `/` for every backslash in the pattern. For example:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* admin_cors: "/http:\\/\\/localhost:700\\d+$/",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
admin_cors?: string
|
||||
/**
|
||||
* A random string used to create cookie tokens. Although this configuration option is not required, it’s highly recommended to set it for better security.
|
||||
*
|
||||
* In a development environment, if this option is not set, the default secret is `supersecret` However, in production, if this configuration is not set, an error is thrown and
|
||||
* the backend crashes.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* cookie_secret: process.env.COOKIE_SECRET ||
|
||||
* "supersecret",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
cookie_secret?: string
|
||||
|
||||
database_url?: string
|
||||
/**
|
||||
* A random string used to create authentication tokens. Although this configuration option is not required, it’s highly recommended to set it for better security.
|
||||
*
|
||||
* In a development environment, if this option is not set the default secret is `supersecret` However, in production, if this configuration is not set an error, an
|
||||
* error is thrown and the backend crashes.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* jwt_secret: process.env.JWT_SECRET ||
|
||||
* "supersecret",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
jwt_secret?: string
|
||||
|
||||
/**
|
||||
* The name of the database to connect to. If specified in `database_url`, then it’s not required to include it.
|
||||
*
|
||||
* Make sure to create the PostgreSQL database before using it. You can check how to create a database in
|
||||
* [PostgreSQL's documentation](https://www.postgresql.org/docs/current/sql-createdatabase.html).
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_database: process.env.DATABASE_DATABASE ||
|
||||
* "medusa-store",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_database?: string
|
||||
|
||||
/**
|
||||
* The connection URL of the database. The format of the connection URL for PostgreSQL is:
|
||||
*
|
||||
* ```bash
|
||||
* postgres://[user][:password]@[host][:port]/[dbname]
|
||||
* ```
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* - `[user]`: (required) your PostgreSQL username. If not specified, the system's username is used by default. The database user that you use must have create privileges. If you're using the `postgres` superuser, then it should have these privileges by default. Otherwise, make sure to grant your user create privileges. You can learn how to do that in [PostgreSQL's documentation](https://www.postgresql.org/docs/current/ddl-priv.html).
|
||||
* - `[:password]`: an optional password for the user. When provided, make sure to put `:` before the password.
|
||||
* - `[host]`: (required) your PostgreSQL host. When run locally, it should be `localhost`.
|
||||
* - `[:post]`: an optional port that the PostgreSQL server is listening on. By default, it's `5432`. When provided, make sure to put `:` before the port.
|
||||
* - `[dbname]`: (required) the name of the database.
|
||||
*
|
||||
* You can learn more about the connection URL format in [PostgreSQL’s documentation](https://www.postgresql.org/docs/current/libpq-connect.html).
|
||||
*
|
||||
* @example
|
||||
* For example, set the following database URL in your environment variables:
|
||||
*
|
||||
* ```bash
|
||||
* DATABASE_URL=postgres://postgres@localhost/medusa-store
|
||||
* ```
|
||||
*
|
||||
* Then, use the value in `medusa-config.js`:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_url: process.env.DATABASE_URL,
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_url?: string
|
||||
/**
|
||||
* The database schema to connect to. This is not required to provide if you’re using the default schema, which is `public`.
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_schema: process.env.DATABASE_SCHEMA ||
|
||||
* "custom",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_schema?: string
|
||||
|
||||
/**
|
||||
* This configuration specifies what database messages to log. Its value can be one of the following:
|
||||
*
|
||||
* - (default) A boolean value that indicates whether any messages should be logged.
|
||||
* - The string value `all` that indicates all types of messages should be logged.
|
||||
* - An array of log-level strings to indicate which type of messages to show in the logs. The strings can be `query`, `schema`, `error`, `warn`, `info`, `log`, or `migration`. Refer to [Typeorm’s documentation](https://typeorm.io/logging#logging-options) for more details on what each of these values means.
|
||||
*
|
||||
* If this configuration isn't set, its default value is `false`, meaning no database messages are logged.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_logging: [
|
||||
* "query", "error",
|
||||
* ],
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_logging: LoggerOptions
|
||||
|
||||
// @deprecated - only postgres is supported, so this config has no effect
|
||||
/**
|
||||
* @ignore
|
||||
* @deprecated
|
||||
*
|
||||
* @privateRemark
|
||||
* only postgres is supported, so this config has no effect
|
||||
*/
|
||||
database_type?: string
|
||||
|
||||
http_compression?: HttpCompressionOptions
|
||||
|
||||
/**
|
||||
* An object that includes additional configurations to pass to the database connection. You can pass any configuration. One defined configuration to pass is
|
||||
* `ssl` which enables support for TLS/SSL connections.
|
||||
*
|
||||
* This is useful for production databases, which can be supported by setting the `rejectUnauthorized` attribute of `ssl` object to `false`.
|
||||
* During development, it’s recommended not to pass this option.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* database_extra:
|
||||
* process.env.NODE_ENV !== "development"
|
||||
* ? { ssl: { rejectUnauthorized: false } }
|
||||
* : {},
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
database_extra?: Record<string, unknown> & {
|
||||
ssl: { rejectUnauthorized: false }
|
||||
/**
|
||||
* Configure support for TLS/SSL connection
|
||||
*/
|
||||
ssl: {
|
||||
/**
|
||||
* Whether to fail connection if the server certificate is verified against the list of supplied CAs and the hostname and no match is found.
|
||||
*/
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
}
|
||||
store_cors?: string
|
||||
admin_cors?: string
|
||||
|
||||
/**
|
||||
* Used to specify the URL to connect to Redis. This is only used for scheduled jobs. If you omit this configuration, scheduled jobs won't work.
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
* You must first have Redis installed. You can refer to [Redis's installation guide](https://redis.io/docs/getting-started/installation/).
|
||||
*
|
||||
* :::
|
||||
*
|
||||
* The Redis connection URL has the following format:
|
||||
*
|
||||
* ```bash
|
||||
* redis[s]://[[username][:password]@][host][:port][/db-number]
|
||||
* ```
|
||||
*
|
||||
* For a local Redis installation, the connection URL should be `redis://localhost:6379` unless you’ve made any changes to the Redis configuration during installation.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* redis_url: process.env.REDIS_URL ||
|
||||
* "redis://localhost:6379",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
redis_url?: string
|
||||
|
||||
/**
|
||||
* The prefix set on all keys stored in Redis. The default value is `sess:`.
|
||||
*
|
||||
* If this configuration option is provided, it is prepended to `sess:`.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* redis_prefix: process.env.REDIS_PREFIX ||
|
||||
* "medusa:",
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
redis_prefix?: string
|
||||
|
||||
/**
|
||||
* An object of options to pass ioredis. You can refer to [ioredis’s RedisOptions documentation](https://redis.github.io/ioredis/index.html#RedisOptions)
|
||||
* for the list of available options.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* redis_options: {
|
||||
* connectionName: process.env.REDIS_CONNECTION_NAME ||
|
||||
* "medusa",
|
||||
* },
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
redis_options?: RedisOptions
|
||||
|
||||
/**
|
||||
* An object of options to pass to [express-session](https://www.npmjs.com/package/express-session).
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* session_options: {
|
||||
* name: process.env.SESSION_NAME ||
|
||||
* "custom",
|
||||
* },
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
session_options?: SessionOptions
|
||||
|
||||
/**
|
||||
* Configure HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there.
|
||||
* However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative.
|
||||
*
|
||||
* Its value is an object that has the following properties:
|
||||
*
|
||||
* If you enable HTTP compression and you want to disable it for specific API Routes, you can pass in the request header `"x-no-compression": true`.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig: {
|
||||
* http_compression: {
|
||||
* enabled: true,
|
||||
* level: 6,
|
||||
* memLevel: 8,
|
||||
* threshold: 1024,
|
||||
* },
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
http_compression?: HttpCompressionOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The configurations for your Medusa backend are in `medusa-config.js` located in the root of your Medusa project. The configurations include database, modules, and plugin configurations, among other configurations.
|
||||
*
|
||||
* `medusa-config.js` exports an object having the following properties:
|
||||
*
|
||||
* - {@link ConfigModule.projectConfig | projectConfig}: (required): An object that holds general configurations related to the Medusa backend, such as database or CORS configurations.
|
||||
* - {@link ConfigModule.plugins | plugins}: An array of plugin configurations that defines what plugins are installed and optionally specifies each of their configurations.
|
||||
* - {@link ConfigModule.modules | modules}: An object that defines what modules are installed and optionally specifies each of their configurations.
|
||||
* - {@link ConfigModule.featureFlags | featureFlags}: An object that enables or disables features guarded by a feature flag.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* projectConfig,
|
||||
* plugins,
|
||||
* modules,
|
||||
* featureFlags,
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* ## Environment Variables
|
||||
*
|
||||
* It's highly recommended to store the values of configurations in environment variables, then reference them within `medusa-config.js`.
|
||||
*
|
||||
* During development, you can set your environment variables in the `.env` file at the root of your Medusa backend project. In production,
|
||||
* setting the environment variables depends on the hosting provider.
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export type ConfigModule = {
|
||||
/**
|
||||
* This property holds essential configurations related to the Medusa backend, such as database and CORS configurations.
|
||||
*/
|
||||
projectConfig: ProjectConfigOptions
|
||||
featureFlags: Record<string, boolean | string>
|
||||
modules?: Record<
|
||||
string,
|
||||
boolean | Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
>
|
||||
|
||||
/**
|
||||
* On your Medusa backend, you can use [Plugins](https://docs.medusajs.com/development/plugins/overview) to add custom features or integrate third-party services.
|
||||
* For example, installing a plugin to use Stripe as a payment processor.
|
||||
*
|
||||
* Aside from installing the plugin with NPM, you need to pass the plugin you installed into the `plugins` array defined in `medusa-config.js`.
|
||||
*
|
||||
* The items in the array can either be:
|
||||
*
|
||||
* - A string, which is the name of the plugin to add. You can pass a plugin as a string if it doesn’t require any configurations.
|
||||
* - An object having the following properties:
|
||||
* - `resolve`: The name of the plugin.
|
||||
* - `options`: An object that includes the plugin’s options. These options vary for each plugin, and you should refer to the plugin’s documentation for available options.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* plugins: [
|
||||
* `medusa-my-plugin-1`,
|
||||
* {
|
||||
* resolve: `medusa-my-plugin`,
|
||||
* options: {
|
||||
* apiKey: process.env.MY_API_KEY ||
|
||||
* `test`,
|
||||
* },
|
||||
* },
|
||||
* // ...
|
||||
* ],
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
plugins: (
|
||||
| {
|
||||
resolve: string
|
||||
@@ -62,4 +547,77 @@ export type ConfigModule = {
|
||||
}
|
||||
| string
|
||||
)[]
|
||||
|
||||
/**
|
||||
* In Medusa, commerce and core logic are modularized to allow developers to extend or replace certain [modules](https://docs.medusajs.com/development/modules/overview)
|
||||
* with custom implementations.
|
||||
*
|
||||
* Aside from installing the module with NPM, you must add it to the exported object in `medusa-config.js`.
|
||||
*
|
||||
* The keys of the `modules` configuration object refer to the type of module. Its value can be one of the following:
|
||||
*
|
||||
* 1. A boolean value indicating whether the module type is enabled;
|
||||
* 2. Or a string value indicating the name of the module to be used for the module type. This can be used if the module does not require any options;
|
||||
* 3. Or an object having the following properties, but typically you would mainly use the `resolve` and `options` properties only:
|
||||
* 1. `resolve`: a string indicating the name of the module.
|
||||
* 2. `options`: an object indicating the options to pass to the module. These options vary for each module, and you should refer to the module’s documentation for details on them.
|
||||
* 3. `resources`: a string indicating whether the module shares the dependency container with the Medusa core. Its value can either be `shared` or `isolated`. Refer to the [Modules documentation](https://docs.medusajs.com/development/modules/create#module-scope) for more details.
|
||||
* 4. `alias`: a string indicating a unique alias to register the module under. Other modules can’t use the same alias.
|
||||
* 5. `main`: a boolean value indicating whether this module is the main registered module. This is useful when an alias is used.
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* modules: {
|
||||
* eventBus: {
|
||||
* resolve: "@medusajs/event-bus-local",
|
||||
* },
|
||||
* cacheService: {
|
||||
* resolve: "@medusajs/cache-redis",
|
||||
* options: {
|
||||
* redisUrl: process.env.CACHE_REDIS_URL,
|
||||
* ttl: 30,
|
||||
* },
|
||||
* },
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
modules?: Record<
|
||||
string,
|
||||
boolean | Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
>
|
||||
|
||||
/**
|
||||
* Some features in the Medusa backend are guarded by a feature flag. This ensures constant shipping of new features while maintaining the engine’s stability.
|
||||
*
|
||||
* You can specify whether a feature should or shouldn’t be used in your backend by enabling its feature flag. Feature flags can be enabled through either environment
|
||||
* variables or through this configuration exported in `medusa-config.js`.
|
||||
*
|
||||
* If you want to use the environment variables method, learn more about it in the [Feature Flags documentation](https://docs.medusajs.com/development/feature-flags/toggle#method-one-using-environment-variables).
|
||||
*
|
||||
* The `featureFlags` configuration is an object. Its properties are the names of the feature flags. Each property’s value is a boolean indicating whether the feature flag is enabled.
|
||||
*
|
||||
* You can find available feature flags and their key name [here](https://github.com/medusajs/medusa/tree/master/packages/medusa/src/loaders/feature-flags).
|
||||
*
|
||||
* @example
|
||||
* ```js title="medusa-config.js"
|
||||
* module.exports = {
|
||||
* featureFlags: {
|
||||
* product_categories: true,
|
||||
* // ...
|
||||
* },
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
* After enabling a feature flag, make sure to [run migrations](https://docs.medusajs.com/development/entities/migrations/overview#migrate-command) as it may require making changes to the database.
|
||||
*
|
||||
* :::
|
||||
*/
|
||||
featureFlags: Record<string, boolean | string>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { AwilixContainer } from "awilix"
|
||||
|
||||
/**
|
||||
* The Medusa Container extends [Awilix](https://github.com/jeffijoe/awilix) to
|
||||
* provide dependency injection functionalities.
|
||||
*/
|
||||
export type MedusaContainer = AwilixContainer & {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
registerAdd: <T>(name: string, registration: T) => MedusaContainer
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
createScope: () => MedusaContainer
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,95 @@
|
||||
import stream from "stream"
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* Details of a file upload's result.
|
||||
*/
|
||||
export type FileServiceUploadResult = {
|
||||
/**
|
||||
* The file's URL.
|
||||
*/
|
||||
url: string
|
||||
/**
|
||||
* The file's key. This key is used in other operations,
|
||||
* such as deleting a file.
|
||||
*/
|
||||
key: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The relevant details to upload a file through a stream.
|
||||
*/
|
||||
export type FileServiceGetUploadStreamResult = {
|
||||
/**
|
||||
* A [PassThrough](https://nodejs.org/api/stream.html#class-streampassthrough) write stream object to be used to write the file.
|
||||
*/
|
||||
writeStream: stream.PassThrough
|
||||
/**
|
||||
* A promise that should resolved when the writing process is done to finish the upload.
|
||||
*/
|
||||
promise: Promise<any>
|
||||
/**
|
||||
* The URL of the file once it’s uploaded.
|
||||
*/
|
||||
url: string
|
||||
/**
|
||||
* The identifier of the file in the storage. For example, for a local file service, this can be the file's name.
|
||||
*/
|
||||
fileKey: string
|
||||
[x: string]: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The details of a file to retrieve.
|
||||
*/
|
||||
export type GetUploadedFileType = {
|
||||
/**
|
||||
* The file's key.
|
||||
*/
|
||||
fileKey: string
|
||||
/**
|
||||
* Whether the file is private.
|
||||
*/
|
||||
isPrivate?: boolean
|
||||
[x: string]: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The details of the file to remove.
|
||||
*/
|
||||
export type DeleteFileType = {
|
||||
/**
|
||||
* The file's key. When uploading a file, the
|
||||
* returned key is used here.
|
||||
*/
|
||||
fileKey: string
|
||||
[x: string]: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* The details of the file being uploaded through a stream.
|
||||
*/
|
||||
export type UploadStreamDescriptorType = {
|
||||
/**
|
||||
* The name of the file.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The extension of the file.
|
||||
*/
|
||||
ext?: string
|
||||
/**
|
||||
* Whether the file should be uploaded to a private bucket or location. By convention, the default value of this property is `true`.
|
||||
*/
|
||||
isPrivate?: boolean
|
||||
[x: string]: unknown
|
||||
}
|
||||
|
||||
@@ -1,53 +1,380 @@
|
||||
import { SearchTypes } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* ## Overview
|
||||
*
|
||||
* A search service class is in a TypeScript or JavaScript file created in the `src/services` directory. The class must extend the `AbstractSearchService` class imported
|
||||
* from the `@medusajs/utils` package.
|
||||
*
|
||||
* Based on services’ naming conventions, the file’s name should be the slug version of the search service’s name without `service`, and the class’s name should be the
|
||||
* pascal case of the search service’s name following by `Service`.
|
||||
*
|
||||
* For example, create the `MySearchService` class in the file `src/services/my-search.ts`:
|
||||
*
|
||||
* ```ts title="src/services/my-search.ts"
|
||||
* import { AbstractSearchService } from "@medusajs/utils"
|
||||
*
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* isDefault = false
|
||||
*
|
||||
* createIndex(indexName: string, options: Record<string, any>) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* getIndex(indexName: string) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* addDocuments(
|
||||
* indexName: string,
|
||||
* documents: Record<string, any>[],
|
||||
* type: string
|
||||
* ) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* replaceDocuments(
|
||||
* indexName: string,
|
||||
* documents: Record<string, any>[],
|
||||
* type: string
|
||||
* ) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* deleteDocument(
|
||||
* indexName: string,
|
||||
* document_id: string | number
|
||||
* ) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* deleteAllDocuments(indexName: string) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
* search(
|
||||
* indexName: string,
|
||||
* query: string,
|
||||
* options: Record<string, any>
|
||||
* ) {
|
||||
* return {
|
||||
* message: "test",
|
||||
* }
|
||||
* }
|
||||
* updateSettings(
|
||||
* indexName: string,
|
||||
* settings: Record<string, any>
|
||||
* ) {
|
||||
* throw new Error("Method not implemented.")
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* export default MySearchService
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* ## Notes About Class Methods
|
||||
*
|
||||
* Although there are several helper methods in this class, the main methods used by the Medusa backend are `addDocuments`, `deleteDocument`, and `search`.
|
||||
* The rest of the methods are provided in case you need them for custom use cases.
|
||||
*
|
||||
* ---
|
||||
*/
|
||||
export abstract class AbstractSearchService
|
||||
implements SearchTypes.ISearchService
|
||||
{
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static _isSearchService = true
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
static isSearchService(obj) {
|
||||
return obj?.constructor?._isSearchService
|
||||
}
|
||||
|
||||
/**
|
||||
* This property is used to pinpoint the default search service defined in the Medusa core. For custom search services, the `isDefault` property must be `false`.
|
||||
*/
|
||||
abstract readonly isDefault
|
||||
/**
|
||||
* If your search service is created in a plugin, the plugin's options will be available in this property.
|
||||
*/
|
||||
protected readonly options_: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
get options(): Record<string, unknown> {
|
||||
return this.options_
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of your search service to access the different services in Medusa through dependency injection.
|
||||
*
|
||||
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs,
|
||||
* you can initialize it in the constructor and use it in other methods in the service.
|
||||
*
|
||||
* Additionally, if you’re creating your search service as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin,
|
||||
* you can access them in the constructor. The default constructor already sets the value of the class proeprty `options_` to the passed options.
|
||||
*
|
||||
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend.
|
||||
* @param {Record<string, unknown>} options - If this search service is created in a plugin, the plugin's options are passed in this parameter.
|
||||
*
|
||||
* @example
|
||||
* // ...
|
||||
* import { ProductService } from "@medusajs/medusa"
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* productService: ProductService
|
||||
* }
|
||||
*
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
* protected readonly productService_: ProductService
|
||||
*
|
||||
* constructor({ productService }: InjectedDependencies) {
|
||||
* // @ts-expect-error prefer-rest-params
|
||||
* super(...arguments)
|
||||
* this.productService_ = productService
|
||||
*
|
||||
* // you can also initialize a client that
|
||||
* // communicates with a third-party service.
|
||||
* this.client = new Client(options)
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
protected constructor(container, options) {
|
||||
this.options_ = options
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to create an index in the search engine.
|
||||
*
|
||||
* @param {string} indexName - The name of the index to create.
|
||||
* @param {unknown} options - Any options that may be relevant to your search service. This parameter doesn't have
|
||||
* any defined format as it depends on your custom implementation.
|
||||
* @returns {unknown} No required format of returned data, as it depends on your custom implementation.
|
||||
*
|
||||
* @example
|
||||
* An example implementation, assuming `client` would interact with a third-party service:
|
||||
*
|
||||
* ```ts title="src/services/my-search.ts"
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
* createIndex(indexName: string, options: Record<string, any>) {
|
||||
* return this.client_.initIndex(indexName)
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Another example of how the [MeiliSearch plugin](https://docs.medusajs.com/plugins/search/meilisearch) uses the
|
||||
* `options` parameter:
|
||||
*
|
||||
* ```ts
|
||||
* class MeiliSearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
* async createIndex(
|
||||
* indexName: string,
|
||||
* options: Record<string, unknown> = { primaryKey: "id" }
|
||||
* ) {
|
||||
* return await this.client_.createIndex(indexName, options)
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
abstract createIndex(indexName: string, options: unknown): unknown
|
||||
|
||||
/**
|
||||
* This method is used to retrieve an index’s results from the search engine.
|
||||
*
|
||||
* @param {string} indexName - The name of the index
|
||||
* @returns {unknown} No required format of returned data, as it depends on your custom implementation.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* getIndex(indexName: string) {
|
||||
* return this.client_.getIndex(indexName)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract getIndex(indexName: string): unknown
|
||||
|
||||
/**
|
||||
* This method is used to add a document to an index in the search engine.
|
||||
*
|
||||
* When the Medusa backend loads, it triggers indexing for all products available in the Medusa backend, which uses this method to add or update documents.
|
||||
* It’s also used whenever a new product is added or a product is updated.
|
||||
*
|
||||
* @param {string} indexName - The name of the index to add the documents to.
|
||||
* @param {unknown} documents - The list of documents to add. For example, an array of {@link entities!Product | products}.
|
||||
* @param {string} type - The type of documents being indexed. For example, `products`.
|
||||
* @returns {unknown} The response of saving the documents in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async addDocuments(
|
||||
* indexName: string,
|
||||
* documents: Record<string, any>[],
|
||||
* type: string
|
||||
* ) {
|
||||
* return await this.client_
|
||||
* .addDocuments(indexName, documents)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract addDocuments(
|
||||
indexName: string,
|
||||
documents: unknown,
|
||||
type: string
|
||||
): unknown
|
||||
|
||||
/**
|
||||
* This method is used to replace existing documents in the search engine of an index with new documents.
|
||||
*
|
||||
* @param {string} indexName - The name of the index that the documents belong to.
|
||||
* @param {unknown} documents - The list of documents to index. For example, it can be an array of {@link entities!Product | products}.
|
||||
* Based on your search engine implementation, the documents should include an identification key that allows replacing the existing documents.
|
||||
* @param {string} type - The type of documents being replaced. For example, `products`.
|
||||
* @returns {unknown} The response of replacing the documents in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async replaceDocuments(
|
||||
* indexName: string,
|
||||
* documents: Record<string, any>[],
|
||||
* type: string
|
||||
* ) {
|
||||
* await this.client_
|
||||
* .removeDocuments(indexName)
|
||||
* return await this.client_
|
||||
* .addDocuments(indexName, documents)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract replaceDocuments(
|
||||
indexName: string,
|
||||
documents: unknown,
|
||||
type: string
|
||||
): unknown
|
||||
|
||||
/**
|
||||
* This method is used to delete a document from an index.
|
||||
*
|
||||
* When a product is deleted in the Medusa backend, this method is used to delete the product from the search engine’s index.
|
||||
*
|
||||
* @param {string} indexName - The name of the index that the document belongs to.
|
||||
* @param {string | number} document_id - The ID of the item indexed. For example, if the deleted item is a product, then this is
|
||||
* the ID of the product.
|
||||
* @returns {unknown} The response of deleting the document in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async deleteDocument(
|
||||
* indexName: string,
|
||||
* document_id: string | number
|
||||
* ) {
|
||||
* return await this.client_
|
||||
* .deleteDocument(indexName, document_id)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract deleteDocument(
|
||||
indexName: string,
|
||||
document_id: string | number
|
||||
): unknown
|
||||
|
||||
/**
|
||||
* This method is used to delete all documents from an index.
|
||||
*
|
||||
* @param {string} indexName - The index's name.
|
||||
* @returns {unknown} The response of deleting the documents of that index in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async deleteAllDocuments(indexName: string) {
|
||||
* return await this.client_
|
||||
* .deleteDocuments(indexName)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract deleteAllDocuments(indexName: string): unknown
|
||||
|
||||
/**
|
||||
* This method is used to search through an index by a query.
|
||||
*
|
||||
* In the Medusa backend, this method is used within the [Search Products API Route](https://docs.medusajs.com/api/store#products_postproductssearch)
|
||||
* to retrieve the search results. The API route's response type is an array of items, though the item's format is not defined as it depends on the
|
||||
* data returned by this method.
|
||||
*
|
||||
* @param {string} indexName - The index's name. In the case of the Search Products API Routes, its value is `products`.
|
||||
* @param {string | null} query - The search query to retrieve results for.
|
||||
* @param {unknown} options -
|
||||
* Options that can configure the search process. The Search Products API route passes an object having the properties:
|
||||
*
|
||||
* - `paginationOptions`: An object having an `offset` and `limit` properties, which are passed in the API Route's body.
|
||||
* - `filter`: Filters that are passed in the API Route's request body. Its format is unknown, so you can pass filters based on your search service.
|
||||
* - `additionalOptions`: Any other parameters that may be passed in the request's body.
|
||||
*
|
||||
* @returns {unknown} The list of results. For example, an array of products.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async search(
|
||||
* indexName: string,
|
||||
* query: string,
|
||||
* options: Record<string, any>
|
||||
* ) {
|
||||
* const hits = await this.client_
|
||||
* .search(indexName, query)
|
||||
* return {
|
||||
* hits,
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract search(
|
||||
indexName: string,
|
||||
query: string | null,
|
||||
options: unknown
|
||||
): unknown
|
||||
|
||||
/**
|
||||
* This method is used to update the settings of an index within the search service. This is useful if you want to update the index settings when the plugin options change.
|
||||
*
|
||||
* For example, in the Algolia plugin, a loader, which runs when the Medusa backend loads, is used to update the settings of indices based on the plugin options.
|
||||
* The loader uses this method to update the settings.
|
||||
*
|
||||
* @param {string} indexName - The index's name to update its settings.
|
||||
* @param {unknown} settings - The settings to update. Its format depends on your use case.
|
||||
* @returns {unknown} The response of updating the index in the search engine, but there’s no required format of the response.
|
||||
*
|
||||
* @example
|
||||
* class MySearchService extends AbstractSearchService {
|
||||
* // ...
|
||||
*
|
||||
* async updateSettings(
|
||||
* indexName: string,
|
||||
* settings: Record<string, any>
|
||||
* ) {
|
||||
* return await this.client_
|
||||
* .updateSettings(indexName, settings)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
abstract updateSettings(indexName: string, settings: unknown): unknown
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user