Przeglądaj źródła

docs: Add docs for implementing `HasCustomFields` (#3794)

Daniel Biegler 4 miesięcy temu
rodzic
commit
aaba82a198

+ 1 - 1
docs/docs/guides/developer-guide/channel-aware/index.md

@@ -64,7 +64,7 @@ export class RequestService {
     }
 }
 ```
-For [Translatable entities](/guides/developer-guide/translations/), the best place to assign the channels is inside the `beforeSave` input of the [TranslateableSave](/reference/typescript-api/service-helpers/translatable-saver/) helper class.
+For [Translatable entities](/guides/developer-guide/translations/), the best place to assign the channels is inside the `beforeSave` input of the [TranslatableSaver](/reference/typescript-api/service-helpers/translatable-saver/) helper class.
 
 
 ## Querying channel-aware entities

+ 6 - 57
docs/docs/guides/developer-guide/database-entity/index.md

@@ -123,65 +123,14 @@ The full list of TypeORM decorators can be found in the [TypeORM decorator refer
 
 Once you have defined a new DB entity, it is likely that you want to expose it in your GraphQL API. Here's how to [define a new type in your GraphQL API](/guides/developer-guide/extend-graphql-api/#defining-a-new-type).
 
-## Supporting custom fields
-
-From Vendure v2.2, it is possible to add support for [custom fields](/guides/developer-guide/custom-fields/) to your custom entities. This
-is useful when you are defining a custom entity as part of a plugin which is intended to be used by other developers. For example, a plugin
-which defines a new entity for storing product reviews might want to allow the developer to add custom fields to the review entity.
-
-First you need to update your entity class to implement the `HasCustomFields` interface, and provide an empty class
-which will be used to store the custom field values:
-
-```ts title="src/plugins/reviews/entities/product-review.entity.ts"
-import {
-    DeepPartial,
-    HasCustomFields,
-    Product,
-    VendureEntity,
-} from '@vendure/core';
-import { Column, Entity, ManyToOne } from 'typeorm';
-
-// highlight-next-line
-export class CustomProductReviewFields {}
-
-@Entity()
-// highlight-next-line
-export class ProductReview extends VendureEntity implements HasCustomFields {
-    constructor(input?: DeepPartial<ProductReview>) {
-        super(input);
-    }
+## Supporting translations
 
-    // highlight-start
-    @Column(type => CustomProductReviewFields)
-    customFields: CustomProductReviewFields;
-    // highlight-end
-    
-    @ManyToOne(type => Product)
-    product: Product;
+In case you'd like to make the `ProductReview` entity support content in multiple languages, here's how to [implement the `Translatable` Interface](/guides/developer-guide/translatable).
 
-    @EntityId()
-    productId: ID;
+## Supporting channels
 
-    @Column()
-    text: string;
+In case you'd like to support separate `ProductReview` entities per Channel, here's how to [implement the `ChannelAware` Interface](/guides/developer-guide/channel-aware).
 
-    @Column()
-    rating: number;
-}
-```
+## Supporting custom fields
 
-Now you'll be able to add custom fields to the `ProductReview` entity via the VendureConfig:
-
-```ts title="src/vendure-config.ts"
-import { VendureConfig } from '@vendure/core';
-
-export const config: VendureConfig = {
-    // ...
-    customFields: {
-        ProductReview: [
-            { name: 'reviewerName', type: 'string' },
-            { name: 'reviewerLocation', type: 'string' },
-        ],
-    },
-};
-```
+Just like you can extend Vendures native entities like `Product` to support your custom needs, you may enable other developers to extend your custom entities too! Here's how to [implement the `HasCustomFields` Interface](/guides/developer-guide/has-custom-fields).

+ 164 - 0
docs/docs/guides/developer-guide/has-custom-fields/index.md

@@ -0,0 +1,164 @@
+---
+title: "Implementing HasCustomFields"
+showtoc: true
+---
+
+From Vendure v2.2, it is possible to add support for [custom fields](/guides/developer-guide/custom-fields/) to your custom entities. This is useful when you are defining a custom entity as part of a plugin which is intended to be used by other developers. For example, a plugin which defines a new entity for storing product reviews might want to allow the developer to add custom fields to the review entity.
+
+## Defining entities that support custom fields
+
+First you need to update your entity class to implement the `HasCustomFields` interface, and provide an empty class
+which will be used to store the custom field values:
+
+```ts title="src/plugins/reviews/entities/product-review.entity.ts"
+import {
+    DeepPartial,
+    HasCustomFields,
+    Product,
+    VendureEntity,
+    ID,
+    EntityId,
+} from '@vendure/core';
+import { Column, Entity, ManyToOne } from 'typeorm';
+
+// highlight-next-line
+export class CustomProductReviewFields {}
+
+@Entity()
+// highlight-next-line
+export class ProductReview extends VendureEntity implements HasCustomFields {
+    constructor(input?: DeepPartial<ProductReview>) {
+        super(input);
+    }
+
+    // highlight-start
+    @Column(() => CustomProductReviewFields)
+    customFields: CustomProductReviewFields;
+    // highlight-end
+    
+    @ManyToOne(() => Product)
+    product: Product;
+
+    @EntityId()
+    productId: ID;
+
+    @Column()
+    text: string;
+
+    @Column()
+    rating: number;
+}
+```
+
+### Type generation
+
+Given the above entity your [API extension](/guides/developer-guide/extend-graphql-api/) might look like this:
+
+```graphql
+type ProductReview implements Node {
+  id: ID!
+  createdAt: DateTime!
+  updatedAt: DateTime!
+
+  product: Product!
+  productId: ID!
+  text: String!
+  rating: Int!
+}
+
+input CreateProductReviewInput {
+  productId: ID!
+  text: String!
+  rating: Int!
+}
+
+input UpdateProductReviewInput {
+  id: ID!
+  productId: ID
+  text: String
+  rating: Int
+}
+```
+
+Notice the lack of manually defining `customFields` on the types, this is because Vendure extends the types automatically once your entity implements `HasCustomFields`.
+
+:::important Naming convention
+In order for Vendure to find the correct input types to extend to, they must conform to the naming convention of `Create<EntityName>Input` and `Update<EntityName>Input`.
+:::
+
+Following this caveat, codegen will now produce correct types including `customFields`-fields like so:
+
+```ts
+export type ProductReview = Node & {
+  // highlight-next-line
+  customFields?: Maybe<Scalars['JSON']['output']>;
+  // Note: Other fields omitted for brevity
+}
+
+export type CreateProductReviewInput = {
+  // highlight-next-line
+  customFields?: InputMaybe<Scalars['JSON']['input']>;
+  // Note: Other fields omitted for brevity
+}
+
+export type UpdateProductReviewInput = {
+  // highlight-next-line
+  customFields?: InputMaybe<Scalars['JSON']['input']>;
+  // Note: Other fields omitted for brevity
+}
+```
+
+## Supporting custom fields in your services
+
+Creating and updating your entity works now by setting the fields like usual, with one important addition being, you mustn't forget to update relations via the `CustomFieldRelationService`. This is needed because a consumer of your plugin may extend the entity with custom fields of type [`relation`](/guides/developer-guide/custom-fields/#properties-for-relation-fields) which need to get saved separately.
+
+```ts title="src/plugins/reviews/services/review.service.ts"
+import { Injectable } from '@nestjs/common';
+import { RequestContext, Product, TransactionalConnection, CustomFieldRelationService } from '@vendure/core';
+
+import { ProductReview } from '../entities/product-review.entity';
+
+@Injectable()
+export class ReviewService {
+    constructor(
+      private connection: TransactionalConnection,
+      // highlight-next-line
+      private customFieldRelationService: CustomFieldRelationService,
+    ) {}
+
+    async create(ctx: RequestContext, input: CreateProductReviewInput) {
+        const product = await this.connection.getEntityOrThrow(ctx, Product, input.productId);
+        // You'll probably want to do more validation/logic here in a real world scenario
+        
+        // highlight-start
+        const review = new ProductReview({ ...input, product });
+        const savedEntity = await this.connection.getRepository(ctx, ProductReview).save(review);
+        await this.customFieldRelationService.updateRelations(ctx, ProductReview, input, savedEntity);
+        // highlight-end
+
+        return savedEntity;
+    }
+}
+```
+
+## Updating config
+
+Now you'll be able to add custom fields to the `ProductReview` entity via the VendureConfig:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+
+export const config: VendureConfig = {
+    // ...
+    customFields: {
+        ProductReview: [
+            { name: 'reviewerName', type: 'string' },
+            { name: 'reviewerLocation', type: 'string' },
+        ],
+    },
+};
+```
+
+## Migrations
+
+Extending entities will alter the database schema requiring a migration. See the [migrations guide](/guides/developer-guide/migrations/) for further details.

+ 2 - 2
docs/docs/guides/developer-guide/translateable/index.md → docs/docs/guides/developer-guide/translatable/index.md

@@ -100,7 +100,7 @@ type ProductRequest implements Node {
 
 ## Creating translatable entities
 
-Creating a translatable entity is usually done by using the [`TranslateableSaver`](/reference/typescript-api/service-helpers/translatable-saver/). This injectable service provides a `create` and `update` method which can be used to save or update a translatable entity.
+Creating a translatable entity is usually done by using the [`TranslatableSaver`](/reference/typescript-api/service-helpers/translatable-saver/). This injectable service provides a `create` and `update` method which can be used to save or update a translatable entity.
 
 ```ts title="src/plugins/requests/service/product-request.service.ts"
 export class RequestService {
@@ -144,7 +144,7 @@ input CreateProductRequestInput {
 
 ## Updating translatable entities
 
-Updating a translatable entity is done in a similar way as creating one. The [`TranslateableSaver`](/reference/typescript-api/service-helpers/translatable-saver/) provides an `update` method which can be used to update a translatable entity.
+Updating a translatable entity is done in a similar way as creating one. The [`TranslatableSaver`](/reference/typescript-api/service-helpers/translatable-saver/) provides an `update` method which can be used to update a translatable entity.
 
 ```ts title="src/plugins/requests/service/product-request.service.ts"
 export class RequestService {

+ 2 - 1
docs/sidebars.js

@@ -103,7 +103,8 @@ const sidebars = {
                 },
                 'guides/developer-guide/custom-strategies-in-plugins/index',
                 'guides/developer-guide/channel-aware/index',
-                'guides/developer-guide/translateable/index',
+                'guides/developer-guide/translatable/index',
+                'guides/developer-guide/has-custom-fields/index',
                 'guides/developer-guide/cache/index',
                 'guides/developer-guide/dataloaders/index',
                 'guides/developer-guide/db-subscribers/index',

+ 1 - 0
docs/static/_redirects

@@ -20,3 +20,4 @@
 /plugins/plugin-examples                    /guides/developer-guide/plugins
 /plugins/writing-a-vendure-plugin           /guides/developer-guide/plugins
 /deployment/using-docker                    /guides/deployment/using-docker
+/guides/developer-guide/translateable      /guides/developer-guide/translatable