From f1605ae2a167f5693eb333e875c918838cd73020 Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Tue, 11 Oct 2022 16:36:08 +0300 Subject: [PATCH] docs: migrated from javascript to typescript (#2398) --- .../advanced/backend/cron-jobs/create.md | 10 +- .../content/advanced/backend/endpoints/add.md | 54 +++--- .../how-to-create-notification-provider.md | 91 +++++----- .../payment/how-to-create-payment-provider.md | 156 +++++++++++++----- .../backend/services/create-service.md | 32 ++-- .../shipping/add-fulfillment-provider.md | 57 ++++--- .../backend/subscribers/create-subscriber.md | 35 ++-- 7 files changed, 256 insertions(+), 179 deletions(-) diff --git a/docs/content/advanced/backend/cron-jobs/create.md b/docs/content/advanced/backend/cron-jobs/create.md index a13f54163e..1b63c8ae28 100644 --- a/docs/content/advanced/backend/cron-jobs/create.md +++ b/docs/content/advanced/backend/cron-jobs/create.md @@ -20,17 +20,17 @@ Redis is required for cron jobs to work. Make sure you [install Redis](../../../ ## 1. Create a File -Each cron job should reside in a file under the `src/loaders` directory. +Each cron job should reside in a TypeScript or JavaScript file under the `src/loaders` directory. Start by creating the `src/loaders` directory. Then, inside that directory, create the JavaScript or TypeScript file that you’ll add the cron job in. You can use any name for the file. -For the example in this tutorial, you can create the file `src/loaders/publish.js`. +For the example in this tutorial, you can create the file `src/loaders/publish.ts`. ## 2. Create Cron Job -To create a cron job, add the following code in the file you created, which is `src/loaders/publish.js` in this example: +To create a cron job, add the following code in the file you created, which is `src/loaders/publish.ts` in this example: -```jsx +```ts const publishJob = async (container, options) => { const eventBus = container.resolve("eventBusService"); eventBus.createCronJob("publish-products", {}, "0 0 * * *", async () => { @@ -72,7 +72,7 @@ To pass data to your cron job, you can add them to the object passed as a second For example: -```jsx +```ts eventBus.createCronJob("publish-products", { data: { productId diff --git a/docs/content/advanced/backend/endpoints/add.md b/docs/content/advanced/backend/endpoints/add.md index 153484d796..9e78a59e95 100644 --- a/docs/content/advanced/backend/endpoints/add.md +++ b/docs/content/advanced/backend/endpoints/add.md @@ -4,13 +4,13 @@ In this document, you’ll learn how to create endpoints in your Medusa server. ## Overview -Custom endpoints reside under the `src/api` directory in your Medusa Backend. To define a new endpoint, you can add the file `index.js` under the `src/api` directory. This file should export a function that returns an Express router. +Custom endpoints reside under the `src/api` directory in your Medusa Backend. They're defined in a TypeScript or JavaScript file that is named `index` (for example, `index.ts`). This file should export a function that returns an Express router. ## Implementation -To create a new endpoint, start by creating a new file in `src/api` called `index.js`. At its basic format, `index.js` should look something like this: +To create a new endpoint, start by creating a new file in `src/api` called `index.ts`. At its basic format, `index.ts` should look something like this: -```jsx +```ts import { Router } from "express" export default (rootDirectory, pluginOptions) => { @@ -26,21 +26,21 @@ export default (rootDirectory, pluginOptions) => { } ``` -This exports a function that returns an Express router. The function receives two parameters: +This exports a function that returns an Express router. The function receives two parameters: - `rootDirectory` is the absolute path to the root directory that your server is running from. - `pluginOptions` is an object that has your plugin's options. If your API route is not implemented in a plugin, then it will be an empty object. ### Endpoints Path -Your endpoint can be under any path you wish. +Your endpoint can be under any path you wish. By Medusa’s conventions: - All Storefront REST APIs are prefixed by `/store`. For example, the `/store/products` endpoint lets you retrieve the products to display them on your storefront. - All Admin REST APIs are prefixed by `/admin`. For example, the `/admin/products` endpoint lets you retrieve the products to display them on your Admin. -You can also create endpoints that do not reside under these two prefixes, similar to the `hello` endpoint in the previous example. +You can also create endpoints that don't reside under these two prefixes, similar to the `hello` endpoint in the previous example. ## CORS Configuration @@ -48,14 +48,14 @@ If you’re adding a storefront or admin endpoint and you want to access these e First, you need to import your Medusa configurations along with the `cors` library: -```jsx +```ts import cors from "cors" import { projectConfig } from "../../medusa-config" ``` Then, create an object that will hold the Cross-Origin Resource Sharing (CORS) configurations. If it’s a storefront endpoint, pass the `origin` property storefront options: -```jsx +```ts const corsOptions = { origin: projectConfig.store_cors.split(","), credentials: true, @@ -64,7 +64,7 @@ const corsOptions = { If it’s an admin endpoint, pass the `origin` property admin options: -```jsx +```ts const corsOptions = { origin: projectConfig.admin_cors.split(","), credentials: true, @@ -73,7 +73,7 @@ const corsOptions = { Finally, for each route you add, create an `OPTIONS` request and add `cors` as a middleware for the route: -```jsx +```ts router.options("/admin/hello", cors(corsOptions)) router.get("/admin/hello", cors(corsOptions), (req, res) => { //... @@ -84,9 +84,9 @@ router.get("/admin/hello", cors(corsOptions), (req, res) => { ### Same File -You can add more than one endpoint in `src/api/index.js`: +You can add more than one endpoint in `src/api/index.ts`: -```jsx +```ts router.options("/store/hello", cors(storeCorsOptions)) router.get("/store/hello", cors(storeCorsOptions), (req, res) => { res.json({ @@ -106,11 +106,11 @@ router.get("/admin/hello", cors(adminCorsOptions), (req, res) => { Alternatively, you can add multiple files for each endpoint or set of endpoints for readability and easy maintenance. -To do that with the previous example, first, create the file `src/api/store.js` with the following content: +To do that with the previous example, first, create the file `src/api/store.ts` with the following content: -```jsx +```ts import cors from "cors" -import { projectConfig } from "../../../medusa-config" +import { projectConfig } from "../../medusa-config" export default (router) => { const storeCorsOptions = { @@ -128,11 +128,11 @@ export default (router) => { You export a function that receives an Express router as a parameter and adds the endpoint `store/hello` to it. -Next, create the file `src/api/admin.js` with the following content: +Next, create the file `src/api/admin.ts` with the following content: -```jsx +```ts import cors from "cors" -import { projectConfig } from "../../../medusa-config" +import { projectConfig } from "../../medusa-config" export default (router) => { const adminCorsOptions = { @@ -150,16 +150,16 @@ export default (router) => { Again, you export a function that receives an Express router as a parameter and adds the endpoint `admin/hello` to it. -Finally, in `src/api/index.js` import the two functions at the beginning of the file: +Finally, in `src/api/index.ts` import the two functions at the beginning of the file: -```jsx +```ts import storeRoutes from "./store" import adminRoutes from "./admin" ``` and in the exported function, call each of the functions passing them the Express router: -```jsx +```ts export default () => { const router = Router() @@ -178,13 +178,13 @@ Protected routes are routes that should be accessible by logged-in customers or To make a storefront route protected, first, import the `authenticate-customer` middleware: -```jsx +```ts import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate-customer" ``` Then, add the middleware to your route: -```jsx +```ts router.options("/store/hello", cors(corsOptions)) router.get("/store/hello", cors(corsOptions), authenticate(), async (req, res) => { if (req.user) { @@ -203,15 +203,15 @@ To disallow guest customers from accessing the endpoint, you can throw an error To make an admin route protected, first, import the `authenticate` middleware: -```jsx +```ts import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate" ``` Then, add the middleware to your route: -```jsx +```ts router.options("/admin/products/count", cors(corsOptions)) -router.get("/admin/products/count", cors(corsOptions), authenticate(), (req, res) => { +router.get("/admin/products/count", cors(corsOptions), authenticate(), async (req, res) => { //access current user const id = req.user.userId const userService = req.scope.resolve("userService") @@ -231,7 +231,7 @@ You can retrieve any registered service in your endpoint using `req.scope.resol Here’s an example of an endpoint that retrieves the count of products in your store: -```jsx +```ts router.get("/admin/products/count", cors(corsOptions), authenticate(), (req, res) => { const productService = req.scope.resolve("productService") diff --git a/docs/content/advanced/backend/notification/how-to-create-notification-provider.md b/docs/content/advanced/backend/notification/how-to-create-notification-provider.md index ce52e64ec4..7f758885ed 100644 --- a/docs/content/advanced/backend/notification/how-to-create-notification-provider.md +++ b/docs/content/advanced/backend/notification/how-to-create-notification-provider.md @@ -1,43 +1,39 @@ # How to Create a Notification Provider -In this document, you’ll learn how to add a Notification Provider to your Medusa server. If you’re unfamiliar with the Notification architecture in Medusa, it is recommended to check out the [architecture overview](overview.md) first. +In this document, you’ll learn how to add a Notification Provider to your Medusa server. -## Overview +:::note -A Notification Provider is the custom or third-party service used to handle sending alerts to customers or users when an event occurs. - -For example, you can use SendGrid to send a confirmation email to a customer after they place an order. SendGrid in this example is a Notification Provider. - -:::tip - -If you’re interested in using SendGrid to send Notifications, you can use the [SendGrid](../../../add-plugins/sendgrid.mdx) plugin instead of using your own. +If you’re unfamiliar with the Notification architecture in Medusa, it is recommended to check out the [architecture overview](overview.md) first. ::: -Adding a Notification Provider is as simple as creating a [Service](../services/create-service.md) file in `src/services`. A Notification Provider is essentially a Service that extends the `NotificationService` from `medusa-interfaces`. - -:::info - -Notification Providers are loaded and installed at the server startup. - -::: - -After creating the Notification Provider Service, you must create a [Subscriber](../subscribers/create-subscriber.md) that subscribes the Notification Provider to specific events. - ## Prerequisites Before you start creating a Notification Provider, you need to install a [Medusa server](../../../quickstart/quick-start.md). -You also need to install and configure Redis. You can check out the documentation “[Set Up Your Development Environment](../../../tutorial/0-set-up-your-development-environment.mdx)” for help. +You also need to (../../../tutorial/0-set-up-your-development-environment.mdx#redis) and [configure it with the Medusa server](../../../usage/configurations.md#redis). ## Create a Notification Provider -The first step to creating a Notification Provider is to create a file in `src/services` with the following content: +Creating a Notification Provider is as simple as creating a TypeScript or JavaScript file in `src/services`. The name of the file is the name of the provider (for example, `sendgrid.ts`). A Notification Provider is essentially a Service that extends the `NotificationService` from `medusa-interfaces`. -```jsx -import { NotificationService } from "medusa-interfaces"; +For example, create the file `src/services/email-sender.ts` with the following content: -class EmailSenderService extends NotificationService { +```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; }> { + throw new Error("Method not implemented."); + } + resendNotification(notification: unknown, config: unknown, attachmentGenerator: unknown): Promise<{ to: string; status: string; data: Record; }> { + throw new Error("Method not implemented."); + } } @@ -64,7 +60,7 @@ The value of this property is also used later when you want to subscribe the Not For example, in the class you created in the previous code snippet you can add the following property: -```jsx +```ts static identifier = "email-sender"; ``` @@ -84,13 +80,26 @@ You can learn more about plugins and how to create them in the [Plugins](../plug Continuing on with the previous example, if you want to use the [`OrderService`](../../../references/services/classes/OrderService.md) later when sending notifications, you can inject it into the constructor: -```jsx -constructor({ orderService }, options) { - //you can access options here in case you're - //using a plugin - super(); +```ts +import { AbstractNotificationService, OrderService } from "@medusajs/medusa"; - this.orderService = orderService; +class EmailSenderService extends AbstractNotificationService { + protected manager_: EntityManager; + protected transactionManager_: EntityManager; + static identifier = "email-sender"; + protected orderService: OrderService; + + // highlight-start + constructor(container, options) { + super(container); + //you can access options here in case you're + //using a plugin + + this.orderService = container.orderService; + } + // highlight-end + + //... } ``` @@ -119,16 +128,17 @@ This method must return an object containing two properties: Continuing with the previous example you can have the following implementation of the `sendNotification` method: -```jsx -async sendNotification(eventName, eventData, attachmentGenerator) { - if (eventName === 'order.placed') { +```ts +async sendNotification(event: string, data: any, attachmentGenerator: unknown): Promise<{ to: string; status: string; data: Record; }> { + if (event === 'order.placed') { //retrieve order - const order = await this.orderService.retrieve(eventData.id); + 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: @@ -169,16 +179,17 @@ Similarly to the `sendNotification` method, this method must return an object co Continuing with the previous example you can have the following implementation of the `resendNotification` method: -```jsx -resendNotification(notification, config, attachmentGenerator) { +```ts +async resendNotification(notification: any, config: any, attachmentGenerator: unknown): Promise<{ to: string; status: string; data: Record; }> { //check if the receiver of the notification should be changed - const to = config.to ? config.to : notification.to; + const to: string = config.to ? 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 //you can also make changes to the data } } @@ -208,7 +219,7 @@ This section will not cover the basics of Subscribers. You can read the [Subscri Following the previous example, to make sure the `email-sender` Notification Provider handles the `order.placed` event, create the file `src/subscribers/notification.js` with the following content: -```jsx +```ts class NotificationSubscriber { constructor({ notificationService }) { notificationService.subscribe('order.placed', 'email-sender'); @@ -232,7 +243,7 @@ Make sure you've configured Redis with your Medusa server as explained in the Pr Then, start by running your Medusa server: -```bash +```bash npm2yarn npm run start ``` diff --git a/docs/content/advanced/backend/payment/how-to-create-payment-provider.md b/docs/content/advanced/backend/payment/how-to-create-payment-provider.md index 043cdc1f5d..59986d9098 100644 --- a/docs/content/advanced/backend/payment/how-to-create-payment-provider.md +++ b/docs/content/advanced/backend/payment/how-to-create-payment-provider.md @@ -10,7 +10,7 @@ By default, Medusa has a [manual payment provider](https://github.com/medusajs/m Adding a Payment Provider is as simple as creating a [service](../services/create-service.md) file in `src/services`. A Payment Provider is essentially a service that extends `AbstractPaymentService` from the core Medusa package `@medusajs/medusa`. -Payment Provider Services must have a static property `identifier`. It is the name that will be used to install and refer to the Payment Provider in the Medusa server. +Payment Provider Services must have a static property `identifier`. It's the name that will be used to install and refer to the Payment Provider in the Medusa server. :::tip @@ -44,12 +44,51 @@ These methods are used at different points in the Checkout flow as well as when ## Create a Payment Provider -The first step to create a payment provider is to create a file in `src/services` with the following content: +The first step to create a payment provider is to create a JavaScript or TypeScript file in `src/services`. The file's name should be the name of the payment provider. -```jsx -import { AbstractPaymentService } from "@medusajs/medusa" +For example, create the file `src/services/my-payment.ts` with the following content: -class MyPaymentService extends AbstractPaymentService { +```ts +import { AbstractPaymentService, Cart, Data, Payment, PaymentSession, PaymentSessionStatus, TransactionBaseService } from "@medusajs/medusa" +import { EntityManager } from "typeorm"; + +class MyPaymentService extends AbstractPaymentService { + protected manager_: EntityManager; + protected transactionManager_: EntityManager; + + getPaymentData(paymentSession: PaymentSession): Promise { + throw new Error("Method not implemented."); + } + updatePaymentData(paymentSessionData: Data, data: Data): Promise { + throw new Error("Method not implemented."); + } + createPayment(cart: Cart): Promise { + throw new Error("Method not implemented."); + } + retrievePayment(paymentData: Data): Promise { + throw new Error("Method not implemented."); + } + updatePayment(paymentSessionData: Data, cart: Cart): Promise { + throw new Error("Method not implemented."); + } + authorizePayment(paymentSession: PaymentSession, context: Data): Promise<{ data: Data; status: PaymentSessionStatus; }> { + throw new Error("Method not implemented."); + } + capturePayment(payment: Payment): Promise { + throw new Error("Method not implemented."); + } + refundPayment(payment: Payment, refundAmount: number): Promise { + throw new Error("Method not implemented."); + } + cancelPayment(payment: Payment): Promise { + throw new Error("Method not implemented."); + } + deletePayment(paymentSession: PaymentSession): Promise { + throw new Error("Method not implemented."); + } + getStatus(data: Data): Promise { + throw new Error("Method not implemented."); + } } @@ -82,8 +121,9 @@ You can also use the constructor to initialize your integration with the third-p Additionally, if you’re creating your Payment Provider as an external plugin to be installed on any Medusa server and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter: -```jsx -constructor({}, options) { +```ts +constructor({ productService }, options) { + super(); //you can access options here } ``` @@ -98,8 +138,11 @@ This method must return an object that is going to be stored in the `data` field An example of a minimal implementation of `createPayment` that does not interact with any third-party providers: -```jsx -async createPayment(cart) { +```ts +import { Cart, Data } from "@medusajs/medusa" +//... + +async createPayment(cart: Cart): Promise { return { id: 'test-payment', status: 'pending' @@ -117,8 +160,11 @@ This method must return an object containing the data from the third-party provi An example of a minimal implementation of `retrievePayment` where you don’t need to interact with the third-party provider: -```jsx -async retrievePayment(cart) { +```ts +import { Data } from "@medusajs/medusa" +//... + +async retrievePayment(paymentData: Data): Promise { return {}; } ``` @@ -141,9 +187,12 @@ This method returns a string that represents the status. The status must be one An example of a minimal implementation of `getStatus` where you don’t need to interact with the third-party provider: -```jsx -async getStatus (data) { - return data.status; +```ts +import { Data, PaymentSessionStatus } from "@medusajs/medusa" +//... + +async getStatus(data: Data): Promise { + return PaymentSessionStatus.AUTHORIZED; } ``` @@ -171,9 +220,12 @@ This method must return an object that will be stored in the `data` field of the An example of a minimal implementation of `updatePayment` that does not need to make any updates on the third-party provider or the `data` field of the Payment Session: -```jsx -async updatePayment(sessionData, cart) { - return sessionData; +```ts +import { Cart, Data } from "@medusajs/medusa"; +//... + +async updatePayment(paymentSessionData: Data, cart: Cart): Promise { + return paymentSessionData; } ``` @@ -189,8 +241,11 @@ This method must return an object that will be stored in the `data` field of the An example of a minimal implementation of `updatePaymentData` that returns the `updatedData` passed in the body of the request as-is to update the `data` field of the Payment Session. -```jsx -async updatePaymentData(sessionData, updatedData) { +```ts +import { Data } from "@medusajs/medusa"; +//... + +async updatePaymentData(paymentSessionData: Data, updatedData: Data): Promise { return updatedData; } ``` @@ -210,8 +265,11 @@ You can use this method to interact with the third-party provider to delete data An example of a minimal implementation of `deletePayment` where no interaction with a third-party provider is required: -```jsx -async deletePayment(paymentSession) { +```ts +import { PaymentSession } from "@medusajs/medusa"; +//... + +async deletePayment(paymentSession: PaymentSession): Promise { return; } ``` @@ -245,15 +303,18 @@ You can utilize this method to interact with the third-party provider and perfor An example of a minimal implementation of `authorizePayment` that doesn’t need to interact with any third-party provider: -```jsx -async authorizePayment(paymentSession, context) { - return { - status: 'authorized', - data: { - id: 'test' - } - }; - } +```ts +import { Data, PaymentSession, PaymentSessionStatus } from "@medusajs/medusa"; +//... + +async authorizePayment(paymentSession: PaymentSession, context: Data): Promise<{ data: Data; status: PaymentSessionStatus; }> { + return { + status: PaymentSessionStatus.AUTHORIZED, + data: { + id: 'test' + } + }; +} ``` ### getPaymentData @@ -266,8 +327,11 @@ This method must return an object to be stored in the `data` field of the Paymen An example of a minimal implementation of `getPaymentData`: -```jsx -async getPaymentData(paymentSession) { +```ts +import { Data, PaymentSession } from "@medusajs/medusa"; +//... + +async getPaymentData(paymentSession: PaymentSession): Promise { return paymentSession.data; } ``` @@ -286,8 +350,11 @@ This method must return an object that will be stored in the `data` field of the An example of a minimal implementation of `capturePayment` that doesn’t need to interact with a third-party provider: -```jsx -async capturePayment(payment) { +```ts +import { Data, Payment } from "@medusajs/medusa"; +//... + +async capturePayment(payment: Payment): Promise { return { status: 'captured' }; @@ -308,8 +375,11 @@ This method must return an object that is stored in the `data` field of the Paym An example of a minimal implementation of `refundPayment` that doesn’t need to interact with a third-party provider: -```jsx -async refundPayment(payment, amount) { +```ts +import { Data, Payment } from "@medusajs/medusa"; +//... + +async refundPayment(payment: Payment, refundAmount: number): Promise { return { id: 'test' } @@ -333,8 +403,11 @@ This method must return an object that is stored in the `data` field of the Paym An example of a minimal implementation of `cancelPayment` that doesn’t need to interact with a third-party provider: -```jsx -async cancelPayment(payment) { +```ts +import { Data, Payment } from "@medusajs/medusa"; +//... + +async cancelPayment(payment: Payment): Promise { return { id: 'test' } @@ -361,13 +434,16 @@ If you’re using Medusa’s [Next.js](../../../starters/nextjs-medusa-starter.m An example of the implementation of `retrieveSavedMethods` taken from Stripe’s Payment Provider: -```jsx +```ts +import { Customer, Data } from "@medusajs/medusa" +//... + /** * Fetches a customers saved payment methods if registered in Stripe. * @param {object} customer - customer to fetch saved cards for * @returns {Promise>} saved payments methods */ -async retrieveSavedMethods(customer) { +async retrieveSavedMethods(customer: Customer): Promise { if (customer.metadata && customer.metadata.stripe_id) { const methods = await this.stripe_.paymentMethods.list({ customer: customer.metadata.stripe_id, diff --git a/docs/content/advanced/backend/services/create-service.md b/docs/content/advanced/backend/services/create-service.md index 1d6b57128a..67add52018 100644 --- a/docs/content/advanced/backend/services/create-service.md +++ b/docs/content/advanced/backend/services/create-service.md @@ -4,14 +4,16 @@ In this document, you’ll learn how you can create a [Service](./overview.md) a ## Implementation -To create a service, you should create a JavaScript file in `src/services` to hold the service. The name of the file should be the registration name of the service without `Service` as it will be appended to it by default. +To create a service, create a TypeScript or JavaScript file in `src/services` to hold the service. The name of the file should be the registration name of the service without `Service` as it will be appended to it by default. -For example, if you want to create a service `helloService`, create the file `hello.js` in `src/services` with the following content: +For example, if you want to create a service `helloService`, create the file `hello.ts` in `src/services` with the following content: -```js +```ts import { TransactionBaseService } from '@medusajs/medusa'; class HelloService extends TransactionBaseService { + protected manager_: EntityManager; + protected transactionManager_: EntityManager; getMessage() { return `Welcome to My Store!` } @@ -26,18 +28,18 @@ As the service extends the `TransactionBaseService` class, all services in Medus So, if you want your service to use another service, simply add it as part of your constructor’s dependencies and set it to a field inside your service’s class: -```js -productService; +```ts +private productService: ProductService; -constructor({ productService }) { - super(); - this.productService = productService; +constructor(container) { + super(container); + this.productService = container.productService; } ``` Then, you can use that service anywhere in your custom service: -```js +```ts async getProductCount() { return await this.productService.count(); } @@ -61,10 +63,10 @@ npm run build To use your custom service in another custom service, you can have easy access to it in the dependencies injected to the constructor of your service: -```js -constructor({ helloService }) { - super(); - this.helloService = helloService; +```ts +constructor(container) { + super(container); + this.helloService = container.helloService; } ``` @@ -72,7 +74,7 @@ constructor({ helloService }) { To use your custom service in an endpoint, you can use `req.scope.resolve` passing it the service’s registration name: -```js +```ts const helloService = req.scope.resolve("helloService") res.json({ @@ -84,7 +86,7 @@ res.json({ To use your custom service in a subscriber, you can have easy access to it in the subscriber’s dependencies injected to the constructor of your subscriber: -```js +```ts constructor({ helloService, eventBusService }) { this.helloService = helloService; } diff --git a/docs/content/advanced/backend/shipping/add-fulfillment-provider.md b/docs/content/advanced/backend/shipping/add-fulfillment-provider.md index f2c04f1807..999e5f7006 100644 --- a/docs/content/advanced/backend/shipping/add-fulfillment-provider.md +++ b/docs/content/advanced/backend/shipping/add-fulfillment-provider.md @@ -21,9 +21,9 @@ Fulfillment providers are loaded and installed on the server startup. ## Create a Fulfillment Provider -The first step is to create the file that will hold the fulfillment provider class in `src/services`: +The first step is to create a JavaScript or TypeScript file under `src/services`. For example, create the file `src/services/my-fulfillment.ts` with the following content: -```jsx +```ts import { FulfillmentService } from "medusa-interfaces" class MyFulfillmentService extends FulfillmentService { @@ -33,7 +33,7 @@ class MyFulfillmentService extends FulfillmentService { export default MyFulfillmentService; ``` -Fulfillment provider services should extend `FulfillmentService` imported from `medusa-interfaces`. +Fulfillment provider services must extend the `FulfillmentService` class imported from `medusa-interfaces`. :::note @@ -49,7 +49,7 @@ The `FulfillmentProvider` entity has 2 properties: `identifier` and `is_installe The value of this property will also be used to reference the fulfillment provider throughout Medusa. For example, it is used to [add a fulfillment provider](https://docs.medusajs.com/api/admin/#tag/Region/operation/PostRegionsRegionFulfillmentProviders) to a region. -```jsx +```ts import { FulfillmentService } from "medusa-interfaces" class MyFulfillmentService extends FulfillmentService { @@ -61,14 +61,17 @@ export default MyFulfillmentService; ### constructor -You can use the `constructor` of your fulfillment provider to have access to different services in Medusa through dependency injection. +You can use the `constructor` of your fulfillment provider to have access to different services in Medusa through dependency injection. You can access any services you create in Medusa in the first parameter. 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 server and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter: +Additionally, if you’re creating your fulfillment provider as an external plugin to be installed on any Medusa server and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter. -```jsx -constructor({}, options) { +For example: + +```ts +constructor({ productService }, options) { + super(); //you can access options here } ``` @@ -83,22 +86,22 @@ These fulfillment options are defined in the `getFulfillmentOptions` method. Thi For example: -```jsx +```ts async getFulfillmentOptions () { - return [ - { - id: 'my-fulfillment' - }, - { - id: 'my-fulfillment-dynamic' - } - ]; - } + return [ + { + id: 'my-fulfillment' + }, + { + id: 'my-fulfillment-dynamic' + } + ]; +} ``` When the admin chooses one of those fulfillment options, the data of the chosen fulfillment option is stored in the `data` property of the shipping option created. This property is used to add any additional data you need to fulfill the order with the third-party provider. -For that reason, the fulfillment option does not have any required structure and can be of any format that works for your integration. +For that reason, the fulfillment option doesn't have any required structure and can be of any format that works for your integration. ### validateOption @@ -110,7 +113,7 @@ This method returns a boolean. If the result is false, an error is thrown and th For example, you can use this method to ensure that the `id` in the `data` object is correct: -```jsx +```ts async validateOption (data) { return data.id == 'my-fulfillment'; } @@ -138,7 +141,7 @@ If everything is valid, this method must return a value that will be stored in t For example: -```jsx +```ts async validateFulfillmentData(optionData, data, cart) { if (data.id !== "my-fulfillment") { throw new Error("invalid data"); @@ -167,7 +170,7 @@ You can use the `data` property in the shipping method (first parameter) to acce Here is a basic implementation of `createFulfillment` for a fulfillment provider that does not interact with any third-party provider to create the fulfillment: -```jsx +```ts createFulfillment( methodData, fulfillmentItems, @@ -200,7 +203,7 @@ This method receives as a parameter the `data` object sent with the request that For example: -```jsx +```ts canCalculate(data) { return data.id === 'my-fulfillment-dynamic'; } @@ -218,7 +221,7 @@ This method receives three parameters: If your fulfillment provider does not provide any dynamically calculated rates you can keep the function empty: -```jsx +```ts calculatePrice() { } @@ -226,7 +229,7 @@ calculatePrice() { Otherwise, you can use it to calculate the price with a custom logic. For example: -```jsx +```ts calculatePrice (optionData, data, cart) { return cart.items.length * 1000; } @@ -244,7 +247,7 @@ It receives the return created as a parameter. The value it returns is set to th This is the basic implementation of the method for a fulfillment provider that does not contact with a third-party provider to fulfill the return: -```jsx +```ts createReturn(returnOrder) { return Promise.resolve({}) } @@ -260,7 +263,7 @@ This method receives the fulfillment being cancelled as a parameter. This is the basic implementation of the method for a fulfillment provider that does not interact with a third-party provider to cancel the fulfillment: -```jsx +```ts cancelFulfillment(fulfillment) { return Promise.resolve({}) } diff --git a/docs/content/advanced/backend/subscribers/create-subscriber.md b/docs/content/advanced/backend/subscribers/create-subscriber.md index 6005a39206..9f091c1199 100644 --- a/docs/content/advanced/backend/subscribers/create-subscriber.md +++ b/docs/content/advanced/backend/subscribers/create-subscriber.md @@ -4,36 +4,21 @@ In this document, you’ll learn how to create a [Subscriber](overview.md) in yo ## Prerequisites -Medusa's event system works by pushing data to a Queue that each handler then gets notified of. The queuing system is based on Redis and you will therefore need to make sure that [Redis](https://redis.io) is installed and configured for your Medusa project. +Medusa's event system works by pushing data to a Queue that each handler then gets notified of. The queuing system is based on Redis, so it's required for subscribers to work. -Then, you need to set your Redis URL in your Medusa server. By default, the Redis URL is `redis://localhost:6379`. If you use a different one, set the following environment variable in `.env`: - -```bash -REDIS_URL= -``` - -After that, in `medusa-config.js`, you’ll need to comment out the following line: - -```jsx -module.exports = { - projectConfig: { - redis_url: REDIS_URL, //this line is commented out - ... - } -} -``` - -After that, you are able to listen to events on your server. +You can learn how to [install Redis](../../../tutorial/0-set-up-your-development-environment.mdx#redis) and [configure it with Medusa](../../../usage/configurations.md#redis) before you get started. ## Implementation -After creating the file under `src/subscribers`, in the constructor of your subscriber, you should listen to events using `eventBusService.subscribe` , where `eventBusService` is a service injected into your subscriber’s constructor. +A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. Its file name, by convension, should be the class name of the subscriber without the word `Subscriber`. For example, if the subscriber is `HelloSubscriber`, the file name should be `hello.ts`. + +After creating the file under `src/subscribers`, in the constructor of your subscriber, listen to events using `eventBusService.subscribe` , where `eventBusService` is a service injected into your subscriber’s constructor. The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event. For example, here is the `OrderNotifierSubscriber` class which is created in `src/subscribers/orderNotifier.js`: -```jsx +```ts class OrderNotifierSubscriber { constructor({ eventBusService }) { eventBusService.subscribe("order.placed", this.handleOrder); @@ -47,21 +32,21 @@ class OrderNotifierSubscriber { export default OrderNotifierSubscriber; ``` -This subscriber will register the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed, and it will receive the order ID in the `data` parameter. You can then use the order’s details to perform any kind of task you need. +This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the order’s details to perform any kind of task you need. :::note -The `data` object will not contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`. +The `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`. ::: ## Using Services in Subscribers -You can access any service through the dependencies injected to your subscriber’s constructor. +You can access any service through the dependencies injected to your subscriber’s constructor. For example: -```jsx +```ts constructor({ productService, eventBusService }) { this.productService = productService;