docs: add routing page (#9550)
- Add a new homepage to `book` project for the routing page - Move all main doc pages to be under `/v2/learn` (and added redirects + fixed links across docs) - Other: add admin components to resources dropdown + fixes to search on mobile. Closes DX-955 Preview: https://docs-v2-git-docs-router-page-medusajs.vercel.app/v2
This commit is contained in:
+84
@@ -0,0 +1,84 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Configure Data Model Properties`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to configure data model properties.
|
||||
|
||||
## Property’s Default Value
|
||||
|
||||
Use the `default` method on a property's definition to specify the default value of a property.
|
||||
|
||||
For example:
|
||||
|
||||
export const defaultHighlights = [
|
||||
["6", "default", "Set the default value to `black`."],
|
||||
["9", "default", "Set the default value to `0`."]
|
||||
]
|
||||
|
||||
```ts highlights={defaultHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
color: model
|
||||
.enum(["black", "white"])
|
||||
.default("black"),
|
||||
age: model
|
||||
.number()
|
||||
.default(0),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
In this example, you set the default value of the `color` enum property to `black`, and that of the `age` number property to `0`.
|
||||
|
||||
---
|
||||
|
||||
## Nullable Property
|
||||
|
||||
Use the `nullable` method to indicate that a property’s value can be `null`.
|
||||
|
||||
For example:
|
||||
|
||||
export const nullableHighlights = [
|
||||
["4", "nullable", "Configure the `price` property to allow `null` values."]
|
||||
]
|
||||
|
||||
```ts highlights={nullableHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
price: model.bigNumber().nullable(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Unique Property
|
||||
|
||||
The `unique` method indicates that a property’s value must be unique in the database through a unique index.
|
||||
|
||||
For example:
|
||||
|
||||
export const uniqueHighlights = [
|
||||
["4", "unique", "Configure the `email` property to allow unique values only."]
|
||||
]
|
||||
|
||||
```ts highlights={uniqueHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const User = model.define("user", {
|
||||
email: model.text().unique(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default User
|
||||
```
|
||||
|
||||
In this example, multiple users can’t have the same email.
|
||||
@@ -0,0 +1,13 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Model Default Properties`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn about the properties available by default in your data model.
|
||||
|
||||
When you create a data model, the following properties are created for you by Medusa:
|
||||
|
||||
- `created_at`: A `dateTime` property that stores when a record of the data model was created.
|
||||
- `updated_at`: A `dateTime` property that stores when a record of the data model was updated.
|
||||
- `deleted_at`: A `dateTime` property that stores when a record of the data model was deleted. When you soft-delete a record, Medusa sets the `deleted_at` property to the current date.
|
||||
@@ -0,0 +1,160 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Model Database Index`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to define a database index on a data model.
|
||||
|
||||
## Define Database Index on Property
|
||||
|
||||
Use the `index` method on a property's definition to define a database index.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["5", "index", "Define an index on the `name` property."],
|
||||
["6", '"IDX_MY_CUSTOM_NAME"', "Index name is optional."]
|
||||
]
|
||||
|
||||
```ts highlights={highlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id().primaryKey(),
|
||||
name: model.text().index(
|
||||
"IDX_MY_CUSTOM_NAME"
|
||||
),
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
The `index` method optionally accepts the name of the index as a parameter.
|
||||
|
||||
In this example, you define an index on the `name` property.
|
||||
|
||||
---
|
||||
|
||||
## Define Database Index on Data Model
|
||||
|
||||
A data model has an `indexes` method that defines database indices on its properties.
|
||||
|
||||
The index can be on multiple columns (composite index). For example:
|
||||
|
||||
export const dataModelIndexHighlights = [
|
||||
["7", "indexes", "Define indices on the data model's properties."],
|
||||
["9", "on", "Specify the properties to define the index on."]
|
||||
]
|
||||
|
||||
```ts highlights={dataModelIndexHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id().primaryKey(),
|
||||
name: model.text(),
|
||||
age: model.number(),
|
||||
}).indexes([
|
||||
{
|
||||
on: ["name", "age"],
|
||||
},
|
||||
])
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
The `indexes` method receives an array of indices as a parameter. Each index is an object with a required `on` property indicating the properties to apply the index on.
|
||||
|
||||
In the above example, you define a composite index on the `name` and `age` properties.
|
||||
|
||||
### Index Conditions
|
||||
|
||||
An index can have conditions. For example:
|
||||
|
||||
export const conditionHighlights = [
|
||||
["10", "where", "Specify conditions on properties."],
|
||||
["11", "", "Create the index when `age` is `30`."]
|
||||
]
|
||||
|
||||
```ts highlights={conditionHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id().primaryKey(),
|
||||
name: model.text(),
|
||||
age: model.number(),
|
||||
}).indexes([
|
||||
{
|
||||
on: ["name", "age"],
|
||||
where: {
|
||||
age: 30,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
The index object passed to `indexes` accepts a `where` property whose value is an object of conditions. The object's key is a property's name, and its value is the condition on that property.
|
||||
|
||||
In the example above, the composite index is created on the `name` and `age` properties when the `age`'s value is `30`.
|
||||
|
||||
A property's condition can be a negation. For example:
|
||||
|
||||
export const negationHighlights = [
|
||||
["12", "", "Create the index when `age` is not `null`."]
|
||||
]
|
||||
|
||||
```ts highlights={negationHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id().primaryKey(),
|
||||
name: model.text(),
|
||||
age: model.number().nullable(),
|
||||
}).indexes([
|
||||
{
|
||||
on: ["name", "age"],
|
||||
where: {
|
||||
age: {
|
||||
$ne: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
A property's value in `where` can be an object having a `$ne` property. `$ne`'s value indicates what the specified property's value shouldn't be.
|
||||
|
||||
In the example above, the composite index is created on the `name` and `age` properties when `age`'s value is not `null`.
|
||||
|
||||
### Unique Database Index
|
||||
|
||||
The object passed to `indexes` accepts a `unique` property indicating that the created index must be a unique index.
|
||||
|
||||
For example:
|
||||
|
||||
export const uniqueHighlights = [
|
||||
["10", "unique", "Specify if the index is a unique index."]
|
||||
]
|
||||
|
||||
```ts highlights={uniqueHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id().primaryKey(),
|
||||
name: model.text(),
|
||||
age: model.number(),
|
||||
}).indexes([
|
||||
{
|
||||
on: ["name", "age"],
|
||||
unique: true,
|
||||
},
|
||||
])
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
This creates a unique composite index on the `name` and `age` properties.
|
||||
@@ -0,0 +1,42 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Infer Type of Data Model`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn how to infer the type of a data model.
|
||||
|
||||
## How to Infer Type of Data Model?
|
||||
|
||||
Consider you have a `MyCustom` data model. You can't reference this data model in a type, such as a workflow input or service method output types, since it's a variable.
|
||||
|
||||
Instead, Medusa provides an `InferTypeOf` utility imported from `@medusajs/framework/types` that transforms your data model to a type.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { InferTypeOf } from "@medusajs/framework/types"
|
||||
import { MyCustom } from "../models/my-custom" // relative path to the model
|
||||
|
||||
export type MyCustom = InferTypeOf<typeof MyCustom>
|
||||
```
|
||||
|
||||
The `InferTypeOf` utility accepts as a type argument the type of the data model.
|
||||
|
||||
Since the `MyCustom` data model is a variable, use the `typeof` operator to pass the data model as a type argument to `InferTypeOf`.
|
||||
|
||||
You can now use the `MyCustom` type to reference a data model in other types, such as in workflow inputs or service method outputs:
|
||||
|
||||
```ts title="Example Service"
|
||||
// other imports...
|
||||
import { InferTypeOf } from "@medusajs/framework/types"
|
||||
import { MyCustom } from "../models/my-custom"
|
||||
|
||||
type MyCustom = InferTypeOf<typeof MyCustom>
|
||||
|
||||
class HelloModuleService extends MedusaService({ MyCustom }) {
|
||||
async doSomething(): Promise<MyCustom> {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
import { BetaBadge } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Manage Relationships`,
|
||||
}
|
||||
|
||||
# {metadata.title} <BetaBadge tooltipText="Data model relationships are in active development and may change." text="Beta" />
|
||||
|
||||
In this chapter, you'll learn how to manage relationships between data models when creating, updating, or retrieving records using the module's main service.
|
||||
|
||||
## Manage One-to-One Relationship
|
||||
|
||||
### BelongsTo Side of One-to-One
|
||||
|
||||
When you create a record of a data model that belongs to another through a one-to-one relation, pass the ID of the other data model's record in the relation property.
|
||||
|
||||
For example, assuming you have the [User and Email data models from the previous chapter](../relationships/page.mdx#one-to-one-relationship), set an email's user ID as follows:
|
||||
|
||||
export const belongsHighlights = [
|
||||
["4", "user", "The ID of the user the email belongs to."],
|
||||
["11", "user", "The ID of the user the email belongs to."]
|
||||
]
|
||||
|
||||
```ts highlights={belongsHighlights}
|
||||
// when creating an email
|
||||
const email = await helloModuleService.createEmails({
|
||||
// other properties...
|
||||
user: "123",
|
||||
})
|
||||
|
||||
// when updating an email
|
||||
const email = await helloModuleService.updateEmails({
|
||||
id: "321",
|
||||
// other properties...
|
||||
user: "123",
|
||||
})
|
||||
```
|
||||
|
||||
In the example above, you pass the `user` property when creating or updating an email to specify the user it belongs to.
|
||||
|
||||
### HasOne Side
|
||||
|
||||
When you create a record of a data model that has one of another, pass the ID of the other data model's record in the relation property.
|
||||
|
||||
For example, assuming you have the [User and Email data models from the previous chapter](../relationships/page.mdx#one-to-one-relationship), set an user's email ID as follows:
|
||||
|
||||
export const hasOneHighlights = [
|
||||
["4", "email", "The ID of the email that the user has."],
|
||||
["11", "email", "The ID of the email that the user has."]
|
||||
]
|
||||
|
||||
```ts highlights={hasOneHighlights}
|
||||
// when creating a user
|
||||
const user = await helloModuleService.createUsers({
|
||||
// other properties...
|
||||
email: "123",
|
||||
})
|
||||
|
||||
// when updating a user
|
||||
const user = await helloModuleService.updateUsers({
|
||||
id: "321",
|
||||
// other properties...
|
||||
email: "123",
|
||||
})
|
||||
```
|
||||
|
||||
In the example above, you pass the `email` property when creating or updating a user to specify the email it has.
|
||||
|
||||
---
|
||||
|
||||
## Manage One-to-Many Relationship
|
||||
|
||||
In a one-to-many relationship, you can only manage the associations from the `belongsTo` side.
|
||||
|
||||
When you create a record of the data model on the `belongsTo` side, pass the ID of the other data model's record in the `{relation}_id` property, where `{relation}` is the name of the relation property.
|
||||
|
||||
For example, assuming you have the [Product and Store data models from the previous chapter](../relationships/page.mdx#one-to-many-relationship), set a product's store ID as follows:
|
||||
|
||||
export const manyBelongsHighlights = [
|
||||
["4", "store_id", "The ID of the store the product belongs to."],
|
||||
["11", "store_id", "The ID of the store the product belongs to."]
|
||||
]
|
||||
|
||||
```ts highlights={manyBelongsHighlights}
|
||||
// when creating a product
|
||||
const product = await helloModuleService.createProducts({
|
||||
// other properties...
|
||||
store_id: "123",
|
||||
})
|
||||
|
||||
// when updating a product
|
||||
const product = await helloModuleService.updateProducts({
|
||||
id: "321",
|
||||
// other properties...
|
||||
store_id: "123",
|
||||
})
|
||||
```
|
||||
|
||||
In the example above, you pass the `store_id` property when creating or updating a product to specify the store it belongs to.
|
||||
|
||||
---
|
||||
|
||||
## Manage Many-to-Many Relationship
|
||||
|
||||
### Create Associations
|
||||
|
||||
When you create a record of a data model that has a many-to-many relationship to another data model, pass an array of IDs of the other data model's records in the relation property.
|
||||
|
||||
For example, assuming you have the [Order and Product data models from the previous chapter](../relationships/page.mdx#many-to-many-relationship), set the association between products and orders as follows:
|
||||
|
||||
export const manyHighlights = [
|
||||
["4", "orders", "The IDs of the orders associated with the product."],
|
||||
["11", "products", "The IDs of the products associated with the order."]
|
||||
]
|
||||
|
||||
```ts highlights={manyHighlights}
|
||||
// when creating a product
|
||||
const product = await helloModuleService.createProducts({
|
||||
// other properties...
|
||||
orders: ["123", "321"],
|
||||
})
|
||||
|
||||
// when creating an order
|
||||
const order = await helloModuleService.createOrders({
|
||||
id: "321",
|
||||
// other properties...
|
||||
products: ["123", "321"],
|
||||
})
|
||||
```
|
||||
|
||||
In the example above, you pass the `orders` property when you create a product, and you pass the `products` property when you create an order.
|
||||
|
||||
### Update Associations
|
||||
|
||||
When you use the `update` methods generated by the service factory, you also pass an array of IDs as the relation property's value to add new associated records.
|
||||
|
||||
However, this removes any existing associations to records whose IDs aren't included in the array.
|
||||
|
||||
For example, assuming you have the [Order and Product data models from the previous chapter](../relationships/page.mdx#many-to-many-relationship), you update the product's related orders as so:
|
||||
|
||||
```ts
|
||||
const product = await helloModuleService.updateProducts({
|
||||
id: "123",
|
||||
// other properties...
|
||||
orders: ["321"],
|
||||
})
|
||||
```
|
||||
|
||||
If the product was associated with an order, and you don't include that order's ID in the `orders` array, the association between the product and order is removed.
|
||||
|
||||
So, to add a new association without removing existing ones, retrieve the product first to pass its associated orders when updating the product:
|
||||
|
||||
export const updateAssociationHighlights = [
|
||||
["1", "retrieveProduct", "Retrieve the product with its orders."],
|
||||
["12", "", "Pass the IDs of the orders previously associated with the product."],
|
||||
["13", "", "Associate the product with a new order."]
|
||||
]
|
||||
|
||||
```ts highlights={updateAssociationHighlights}
|
||||
const product = await helloModuleService.retrieveProduct(
|
||||
"123",
|
||||
{
|
||||
relations: ["orders"],
|
||||
}
|
||||
)
|
||||
|
||||
const updatedProduct = await helloModuleService.updateProducts({
|
||||
id: product.id,
|
||||
// other properties...
|
||||
orders: [
|
||||
...product.orders.map((order) => order.id),
|
||||
"321",
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
This keeps existing associations between the product and orders, and adds a new one.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Records of Relation
|
||||
|
||||
The `list`, `listAndCount`, and `retrieve` methods of a module's main service accept as a second parameter an object of options.
|
||||
|
||||
To retrieve the records associated with a data model's records through a relationship, pass in the second parameter object a `relations` property whose value is an array of relationship names.
|
||||
|
||||
For example, assuming you have the [Order and Product data models from the previous chapter](../relationships/page.mdx#many-to-many-relationship), you retrieve a product's orders as follows:
|
||||
|
||||
export const retrieveHighlights = [
|
||||
["4", `"orders"`, "Retrieve the records associated with the product\nthrough the `orders` relationship."]
|
||||
]
|
||||
|
||||
```ts highlights={retrieveHighlights}
|
||||
const product = await helloModuleService.retrieveProducts(
|
||||
"123",
|
||||
{
|
||||
relations: ["orders"],
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the example above, the retrieved product has an `orders` property, whose value is an array of orders associated with the product.
|
||||
@@ -0,0 +1,15 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Models Advanced Guides`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In the next chapters, you'll learn more about defining data models.
|
||||
|
||||
You'll learn about:
|
||||
|
||||
- The different property types available.
|
||||
- How to set a property as a primary key.
|
||||
- How to create and manage relationships.
|
||||
- How to configure properties, such as making them nullable or searchable.
|
||||
- How to manually write migrations.
|
||||
@@ -0,0 +1,30 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Model’s Primary Key`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to configure the primary key of a data model.
|
||||
|
||||
## primaryKey Method
|
||||
|
||||
To set any `id`, `text`, or `number` property as a primary key, use the `primaryKey` method.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["4", "primaryKey", "Define the `id` property to be the data model's primary key."]
|
||||
]
|
||||
|
||||
```ts highlights={highlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id().primaryKey(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
In the example above, the `id` property is defined as the data model's primary key.
|
||||
@@ -0,0 +1,204 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Model Property Types`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about the types of properties in a data model’s schema.
|
||||
|
||||
These types are available as methods on the `model` utility imported from `@medusajs/framework/utils`.
|
||||
|
||||
## id
|
||||
|
||||
The `id` method defines an automatically generated string ID property. The generated ID is a unique string that has a mix of letters and numbers.
|
||||
|
||||
For example:
|
||||
|
||||
export const idHighlights = [["4", ".id()", "Define an `id` property."]]
|
||||
|
||||
```ts highlights={idHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## text
|
||||
|
||||
The `text` method defines a string property.
|
||||
|
||||
For example:
|
||||
|
||||
export const textHighlights = [["4", "text", "Define a `text` property."]]
|
||||
|
||||
```ts highlights={textHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
name: model.text(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## number
|
||||
|
||||
The `number` method defines a number property.
|
||||
|
||||
For example:
|
||||
|
||||
export const numberHighlights = [["4", "number", "Define a `number` property."]]
|
||||
|
||||
```ts highlights={numberHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
age: model.number(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## bigNumber
|
||||
|
||||
The `bigNumber` method defines a number property that expects large numbers, such as prices.
|
||||
|
||||
For example:
|
||||
|
||||
export const bigNumberHighlights = [["4", "bigNumber", "Define a `bigNumber` property."]]
|
||||
|
||||
```ts highlights={bigNumberHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
price: model.bigNumber(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## boolean
|
||||
|
||||
The `boolean` method defines a boolean property.
|
||||
|
||||
For example:
|
||||
|
||||
export const booleanHighlights = [["4", "boolean", "Define a `boolean` property."]]
|
||||
|
||||
```ts highlights={booleanHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
hasAccount: model.boolean(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### enum
|
||||
|
||||
The `enum` method defines a property whose value can only be one of the specified values.
|
||||
|
||||
For example:
|
||||
|
||||
export const enumHighlights = [["4", "enum", "Define a `enum` property."]]
|
||||
|
||||
```ts highlights={enumHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
color: model.enum(["black", "white"]),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
The `enum` method accepts an array of possible string values.
|
||||
|
||||
---
|
||||
|
||||
## dateTime
|
||||
|
||||
The `dateTime` method defines a timestamp property.
|
||||
|
||||
For example:
|
||||
|
||||
export const dateTimeHighlights = [["4", "dateTime", "Define a `dateTime` property."]]
|
||||
|
||||
```ts highlights={dateTimeHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
date_of_birth: model.dateTime(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## json
|
||||
|
||||
The `json` method defines a property whose value is a stringified JSON object.
|
||||
|
||||
For example:
|
||||
|
||||
export const jsonHighlights = [["4", "json", "Define a `json` property."]]
|
||||
|
||||
```ts highlights={jsonHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
metadata: model.json(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## array
|
||||
|
||||
The `array` method defines an array of strings property.
|
||||
|
||||
For example:
|
||||
|
||||
export const arrHightlights = [["4", "array", "Define an `array` property."]]
|
||||
|
||||
```ts highlights={arrHightlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
names: model.array(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Properties Reference
|
||||
|
||||
Refer to the [Data Model API reference](https://docs.medusajs.com/v2/resources/references/data-model) for a full reference of the properties.
|
||||
@@ -0,0 +1,252 @@
|
||||
import { BetaBadge } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Model Relationships`,
|
||||
}
|
||||
|
||||
# {metadata.title} <BetaBadge text="Beta" tooltipText="Data model relationships are in active development and may change." />
|
||||
|
||||
In this chapter, you’ll learn how to define relationships between data models in your module.
|
||||
|
||||
## What is a Relationship Property?
|
||||
|
||||
A relationship property defines an association in the database between two models. It's created using methods on the `models` utility, such as `hasOne` or `belongsTo`.
|
||||
|
||||
When you generate a migration for these data models, the migrations include foreign key columns or pivot tables, based on the relationship's type.
|
||||
|
||||
<Note title="Use data model relationships when" type="success">
|
||||
|
||||
You want to create a relation between data models in the same module.
|
||||
|
||||
</Note>
|
||||
|
||||
<Note title="Don't use data model relationships if" type="error">
|
||||
|
||||
You want to create a relationship between data models in different modules. Use module links instead.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## One-to-One Relationship
|
||||
|
||||
A one-to-one relationship indicates that one record of a data model belongs to or is associated with another.
|
||||
|
||||
To define a one-to-one relationship, create relationship properties in the data models using the following methods:
|
||||
|
||||
1. `hasOne`: indicates that the model has one record of the specified model.
|
||||
2. `belongsTo`: indicates that the model belongs to one record of the specified model.
|
||||
|
||||
For example:
|
||||
|
||||
export const oneToOneHighlights = [
|
||||
["5", "hasOne", "A user has one email."],
|
||||
["10", "belongsTo", "An email belongs to a user."],
|
||||
["11", `"email"`, "The relationship's name in the `User` data model."]
|
||||
]
|
||||
|
||||
```ts highlights={oneToOneHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const User = model.define("user", {
|
||||
id: model.id().primaryKey(),
|
||||
email: model.hasOne(() => Email),
|
||||
})
|
||||
|
||||
const Email = model.define("email", {
|
||||
id: model.id().primaryKey(),
|
||||
user: model.belongsTo(() => User, {
|
||||
mappedBy: "email",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
In the example above, a user has one email, and an email belongs to one user.
|
||||
|
||||
The `hasOne` and `belongsTo` methods accept a function as a first parameter. The function returns the associated data model.
|
||||
|
||||
The `belongsTo` method also requires passing as a second parameter an object with the property `mappedBy`. Its value is the name of the relationship property in the other data model.
|
||||
|
||||
### Optional Relationship
|
||||
|
||||
To make the relationship optional on the `hasOne` or `belongsTo` side, use the `nullable` method on either properties as explained in [this chapter](../configure-properties/page.mdx#nullable-property).
|
||||
|
||||
### One-to-One Relationship in the Database
|
||||
|
||||
When you generate the migrations of data models that have a one-to-one relationship, the migration adds to the table of the data model that has the `belongsTo` property:
|
||||
|
||||
1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `email` table will have a `user_id` column.
|
||||
2. A foreign key on the `{relation_name}_id` column to the table of the related data model.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## One-to-Many Relationship
|
||||
|
||||
A one-to-many relationship indicates that one record of a data model has many records of another data model.
|
||||
|
||||
To define a one-to-many relationship, create relationship properties in the data models using the following methods:
|
||||
|
||||
1. `hasMany`: indicates that the model has more than one records of the specified model.
|
||||
2. `belongsTo`: indicates that the model belongs to one record of the specified model.
|
||||
|
||||
For example:
|
||||
|
||||
export const oneToManyHighlights = [
|
||||
["5", "hasMany", "A store has many products"],
|
||||
["10", "belongsTo", "A product has one store."],
|
||||
["11", `"products"`, "The relationship's name in the `Store` data model."]
|
||||
]
|
||||
|
||||
```ts highlights={oneToManyHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const Store = model.define("store", {
|
||||
id: model.id().primaryKey(),
|
||||
products: model.hasMany(() => Product),
|
||||
})
|
||||
|
||||
const Product = model.define("product", {
|
||||
id: model.id().primaryKey(),
|
||||
store: model.belongsTo(() => Store, {
|
||||
mappedBy: "products",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
In this example, a store has many products, but a product belongs to one store.
|
||||
|
||||
### Optional Relationship
|
||||
|
||||
To make the relationship optional on the `belongsTo` side, use the `nullable` method on the property as explained in [this chapter](../configure-properties/page.mdx#nullable-property).
|
||||
|
||||
### One-to-Many Relationship in the Database
|
||||
|
||||
When you generate the migrations of data models that have a one-to-many relationship, the migration adds to the table of the data model that has the `belongsTo` property:
|
||||
|
||||
1. A column of the format `{relation_name}_id` to store the ID of the record of the related data model. For example, the `product` table will have a `store_id` column.
|
||||
2. A foreign key on the `{relation_name}_id` column to the table of the related data model.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Many-to-Many Relationship
|
||||
|
||||
A many-to-many relationship indicates that many records of a data model can be associated to many records of another data model.
|
||||
|
||||
To define a many-to-many relationship, create relationship properties in the data models using the `manyToMany` method.
|
||||
|
||||
For example:
|
||||
|
||||
export const manyToManyHighlights = [
|
||||
["5", "manyToMany", "An order is associated with many products."],
|
||||
["12", "manyToMany", "A product is associated with many orders."]
|
||||
]
|
||||
|
||||
```ts highlights={manyToManyHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const Order = model.define("order", {
|
||||
id: model.id().primaryKey(),
|
||||
products: model.manyToMany(() => Product, {
|
||||
mappedBy: "orders",
|
||||
}),
|
||||
})
|
||||
|
||||
const Product = model.define("product", {
|
||||
id: model.id().primaryKey(),
|
||||
orders: model.manyToMany(() => Order, {
|
||||
mappedBy: "products",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
At least one side of the many-to-many relationship must have the `mappedBy` property set in the second object parameter of the `manyToMany` object. Its value is the name of the relationship property in the other data model.
|
||||
|
||||
In this example, an order is associated with many products, and a product is associated with many orders.
|
||||
|
||||
### Many-to-Many Relationship in the Database
|
||||
|
||||
When you generate the migrations of data models that have a many-to-many relationship, the migration adds a new pivot table.
|
||||
|
||||
The pivot table has a column with the name `{data_model}_id` for each of the data model's tables. It also has foreign keys on each of these columns to their respective tables.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Set Relationship Name in the Other Model
|
||||
|
||||
The relationship property methods accept as a second parameter an object of options. The `mappedBy` property defines the name of the relationship in the other data model.
|
||||
|
||||
This is useful if the relationship property’s name is different than that of the associated data model.
|
||||
|
||||
As seen in previous examples, the `mappedBy` option is required for the `belongsTo` method.
|
||||
|
||||
For example:
|
||||
|
||||
export const relationNameHighlights = [
|
||||
["6", `"owner"`, "The relationship's name in the `Email` data model."],
|
||||
["13", `"email"`, "The relationship's name in the `User` data model."]
|
||||
]
|
||||
|
||||
```ts highlights={relationNameHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const User = model.define("user", {
|
||||
id: model.id().primaryKey(),
|
||||
email: model.hasOne(() => Email, {
|
||||
mappedBy: "owner",
|
||||
}),
|
||||
})
|
||||
|
||||
const Email = model.define("email", {
|
||||
id: model.id().primaryKey(),
|
||||
owner: model.belongsTo(() => User, {
|
||||
mappedBy: "email",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
In this example, you specify in the `User` data model’s relationship property that the name of the relationship in the `Email` data model is `owner`.
|
||||
|
||||
---
|
||||
|
||||
## Cascades
|
||||
|
||||
When an operation is performed on a data model, such as record deletion, the relationship cascade specifies what related data model records should be affected by it.
|
||||
|
||||
For example, if a store is deleted, its products should also be deleted.
|
||||
|
||||
The `cascades` method used on a data model configures which child records an operation is cascaded to.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["8", "", "When a store is deleted, delete its associated products."]
|
||||
]
|
||||
|
||||
```ts highlights={highlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const Store = model.define("store", {
|
||||
id: model.id().primaryKey(),
|
||||
products: model.hasMany(() => Product),
|
||||
})
|
||||
.cascades({
|
||||
delete: ["products"],
|
||||
})
|
||||
|
||||
const Product = model.define("product", {
|
||||
id: model.id().primaryKey(),
|
||||
store: model.belongsTo(() => Store, {
|
||||
mappedBy: "products",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
The `cascades` method accepts an object. Its key is the operation’s name, such as `delete`. The value is an array of relationship property names that the operation is cascaded to.
|
||||
|
||||
In the example above, when a store is deleted, its associated products are also deleted.
|
||||
@@ -0,0 +1,50 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Searchable Data Model Property`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn what a searchable property is and how to define it.
|
||||
|
||||
## What is a Searchable Property?
|
||||
|
||||
Methods generated by the [service factory](../../modules/service-factory/page.mdx) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters.
|
||||
|
||||
When the `q` filter is passed, the data model's searchable properties are queried to find matching records.
|
||||
|
||||
---
|
||||
|
||||
## Define a Searchable Property
|
||||
|
||||
Use the `searchable` method on a `text` property to indicate that it's searchable.
|
||||
|
||||
For example:
|
||||
|
||||
export const searchableHighlights = [
|
||||
["4", "searchable", "Define the `name` property as searchable."]
|
||||
]
|
||||
|
||||
```ts highlights={searchableHighlights}
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
name: model.text().searchable(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
In this example, the `name` property is searchable.
|
||||
|
||||
### Search Example
|
||||
|
||||
If you pass a `q` filter to the `listMyCustoms` method:
|
||||
|
||||
```ts
|
||||
const myCustoms = await helloModuleService.listMyCustoms({
|
||||
q: "John",
|
||||
})
|
||||
```
|
||||
|
||||
This retrieves records that include `John` in their `name` property.
|
||||
@@ -0,0 +1,70 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Write Migration`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn how to create a migration and write it manually.
|
||||
|
||||
## What is a Migration?
|
||||
|
||||
A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It has two methods:
|
||||
|
||||
- The `up` method reflects changes on the database.
|
||||
- The `down` method reverts the changes made in the `up` method.
|
||||
|
||||
---
|
||||
|
||||
## How to Write a Migration?
|
||||
|
||||
The Medusa CLI tool provides a [db:generate](!resources!/medusa-cli/commands/db#dbgenerate) command to generate a migration for the specified modules' data models.
|
||||
|
||||
Alternatively, you can manually create a migration file under the `migrations` directory of your module.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/modules/hello/migrations/Migration20240429.ts"
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240702105919 extends Migration {
|
||||
|
||||
async up(): Promise<void> {
|
||||
this.addSql("create table if not exists \"my_custom\" (\"id\" text not null, \"name\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"my_custom_pkey\" primary key (\"id\"));")
|
||||
}
|
||||
|
||||
async down(): Promise<void> {
|
||||
this.addSql("drop table if exists \"my_custom\" cascade;")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The migration's file name should be of the format `Migration{YEAR}{MONTH}{DAY}.ts`. The migration class in the file extends the `Migration` class imported from `@mikro-orm/migrations`.
|
||||
|
||||
In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax.
|
||||
|
||||
In the example above, the `up` method creates the table `my_custom`, and the `down` method drops the table if the migration is reverted.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Refer to [MikroORM's documentation](https://mikro-orm.io/docs/migrations#migration-class) for more details on writing migrations.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Run the Migration
|
||||
|
||||
To run your migration, run the following command:
|
||||
|
||||
<Note>
|
||||
|
||||
This command also syncs module links. If you don't want that, use the `--skip-links` option.
|
||||
|
||||
</Note>
|
||||
|
||||
```bash
|
||||
npx medusa db:migrate
|
||||
```
|
||||
|
||||
This reflects the changes in the database as implemented in the migration's `up` method.
|
||||
Reference in New Issue
Block a user