@goldenhippo/builder-cart-schemas 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -41
- package/dist/data/index.d.mts +105 -95
- package/dist/data/index.d.ts +105 -95
- package/dist/data/index.js.map +1 -1
- package/dist/data/index.mjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# @goldenhippo/builder-cart-schemas
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Builder.io model definitions and TypeScript content types for Golden Hippo's cart/commerce integration. Provides factory functions for creating Builder.io content models and strongly-typed interfaces for consuming model data.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Using Content Types](#using-content-types)
|
|
9
|
+
- [Fetching typed content in Angular](#fetching-typed-content-in-angular)
|
|
10
|
+
- [Working with page types](#working-with-page-types)
|
|
11
|
+
- [Using core types](#using-core-types)
|
|
12
|
+
- [Exports](#exports)
|
|
13
|
+
- [Models](#models)
|
|
14
|
+
- [Data Models](#data-models)
|
|
15
|
+
- [Page Models](#page-models)
|
|
16
|
+
- [Section Models](#section-models)
|
|
17
|
+
- [Creating Model Definitions](#creating-model-definitions)
|
|
18
|
+
- [Model Dependencies](#model-dependencies)
|
|
19
|
+
- [Development](#development)
|
|
4
20
|
|
|
5
21
|
## Installation
|
|
6
22
|
|
|
@@ -8,6 +24,94 @@ Shared TypeScript types and Builder.io model definitions for Golden Hippo's cart
|
|
|
8
24
|
npm install @goldenhippo/builder-cart-schemas
|
|
9
25
|
```
|
|
10
26
|
|
|
27
|
+
## Using Content Types
|
|
28
|
+
|
|
29
|
+
Content types represent the shape of data returned from Builder.io's Content API. Use them to get type-safe access to model fields when fetching content.
|
|
30
|
+
|
|
31
|
+
### Fetching typed content in Angular
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Component, OnInit } from '@angular/core';
|
|
35
|
+
import { fetchOneEntry, fetchEntries } from '@builder.io/sdk-angular';
|
|
36
|
+
import type { BuilderProductContent, BuilderBlogCategoryContent } from '@goldenhippo/builder-cart-schemas';
|
|
37
|
+
import type { BuilderPageContent } from '@goldenhippo/builder-cart-schemas/page';
|
|
38
|
+
|
|
39
|
+
@Component({ selector: 'app-product', templateUrl: './product.component.html' })
|
|
40
|
+
export class ProductComponent implements OnInit {
|
|
41
|
+
product: BuilderProductContent | null = null;
|
|
42
|
+
|
|
43
|
+
async ngOnInit() {
|
|
44
|
+
const result = await fetchOneEntry({
|
|
45
|
+
model: 'product',
|
|
46
|
+
apiKey: environment.builderApiKey,
|
|
47
|
+
query: { 'data.gh.slug': 'example-product' },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.product = result as BuilderProductContent;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get displayName(): string {
|
|
54
|
+
return this.product?.data?.displayName ?? '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get featuredImage(): string {
|
|
58
|
+
return this.product?.data?.featuredImage ?? '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get averageRating(): number {
|
|
62
|
+
return this.product?.data?.reviews?.averageRating ?? 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// Fetching multiple entries
|
|
69
|
+
const categories = (await fetchEntries({
|
|
70
|
+
model: 'blog-category',
|
|
71
|
+
apiKey: environment.builderApiKey,
|
|
72
|
+
})) as BuilderBlogCategoryContent[];
|
|
73
|
+
|
|
74
|
+
categories.forEach((cat) => {
|
|
75
|
+
console.log(cat?.data?.name, cat?.data?.displayName);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Working with page types
|
|
80
|
+
|
|
81
|
+
Page content uses a discriminated union based on `pageType`. TypeScript narrows the type automatically:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import type {
|
|
85
|
+
BuilderPageContent,
|
|
86
|
+
BuilderPdpPageContent,
|
|
87
|
+
BuilderBlogPageContent,
|
|
88
|
+
} from '@goldenhippo/builder-cart-schemas/page';
|
|
89
|
+
|
|
90
|
+
function renderPage(page: BuilderPageContent) {
|
|
91
|
+
const pageType = page?.data?.pageType;
|
|
92
|
+
|
|
93
|
+
if (pageType === 'Product') {
|
|
94
|
+
const pdp = page as BuilderPdpPageContent;
|
|
95
|
+
console.log(pdp?.data?.pdp?.product?.name);
|
|
96
|
+
console.log(pdp?.data?.pdp?.offerSelector?.osType);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (pageType === 'Blog') {
|
|
100
|
+
const blog = page as BuilderBlogPageContent;
|
|
101
|
+
console.log(blog?.data?.blog?.author);
|
|
102
|
+
console.log(blog?.data?.blog?.publicationDate);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Using core types
|
|
108
|
+
|
|
109
|
+
Core types like `ModelShape` and `BuilderIOFieldTypes` are available from the `@goldenhippo/builder-types` package:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import type { ModelShape, BuilderIOFieldTypes } from '@goldenhippo/builder-types';
|
|
113
|
+
```
|
|
114
|
+
|
|
11
115
|
## Exports
|
|
12
116
|
|
|
13
117
|
The package provides multiple entry points for granular imports:
|
|
@@ -19,8 +123,6 @@ The package provides multiple entry points for granular imports:
|
|
|
19
123
|
| `@goldenhippo/builder-cart-schemas/page` | Page model creator and content types |
|
|
20
124
|
| `@goldenhippo/builder-cart-schemas/section` | Section/component model creators and content types |
|
|
21
125
|
|
|
22
|
-
Core types (`ModelShape`, `BuilderIOFieldTypes`, etc.) are available from `@goldenhippo/builder-types`.
|
|
23
|
-
|
|
24
126
|
## Models
|
|
25
127
|
|
|
26
128
|
### Data Models
|
|
@@ -28,6 +130,7 @@ Core types (`ModelShape`, `BuilderIOFieldTypes`, etc.) are available from `@gold
|
|
|
28
130
|
| Model | Creator | Content Type |
|
|
29
131
|
| ------------------------- | ---------------------------------------------------------- | -------------------------------------- |
|
|
30
132
|
| Product | `createProductModel(props)` | `BuilderProductContent` |
|
|
133
|
+
| Product Group | `createProductGroupModel(props)` | `BuilderProductGroupContent` |
|
|
31
134
|
| Product Category | `createCategoryModel()` | `BuilderProductCategoryContent` |
|
|
32
135
|
| Product Tag | `createProductTagModel()` | `BuilderProductTagContent` |
|
|
33
136
|
| Product Ingredient | `createIngredientsModel()` | `BuilderIngredientContent` |
|
|
@@ -50,9 +153,7 @@ Core types (`ModelShape`, `BuilderIOFieldTypes`, etc.) are available from `@gold
|
|
|
50
153
|
| Site Banner | `createSiteBannerModel(editUrl)` | `BuilderSiteBannerModelContent` |
|
|
51
154
|
| Default Website Section | `createDefaultWebsiteSectionModel(editUrl)` | `BuilderDefaultWebsiteSectionContent` |
|
|
52
155
|
|
|
53
|
-
##
|
|
54
|
-
|
|
55
|
-
### Creating model definitions
|
|
156
|
+
## Creating Model Definitions
|
|
56
157
|
|
|
57
158
|
Model creators return a `ModelShape` object suitable for registration with Builder.io's API. Some models require IDs of other models they reference.
|
|
58
159
|
|
|
@@ -81,32 +182,6 @@ const pageModel = createPageModel({
|
|
|
81
182
|
});
|
|
82
183
|
```
|
|
83
184
|
|
|
84
|
-
### Using content types
|
|
85
|
-
|
|
86
|
-
Content types extend `BuilderContent` from `@builder.io/sdk` and represent the shape of data returned from Builder.io's Content API.
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
import type { BuilderProductContent, BuilderPageContent } from '@goldenhippo/builder-cart-schemas';
|
|
90
|
-
|
|
91
|
-
// Type-safe access to product content
|
|
92
|
-
function getProductName(product: BuilderProductContent): string {
|
|
93
|
-
return product?.data?.displayName ?? product?.data?.name ?? '';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Page content is a union type based on page type
|
|
97
|
-
function handlePage(page: BuilderPageContent) {
|
|
98
|
-
if (page?.data?.pageType === 'Product') {
|
|
99
|
-
// TypeScript narrows to BuilderPdpPageContent
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Using core types
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
import type { ModelShape, BuilderIOFieldTypes } from '@goldenhippo/builder-types';
|
|
108
|
-
```
|
|
109
|
-
|
|
110
185
|
## Model Dependencies
|
|
111
186
|
|
|
112
187
|
Models that reference other models require their Builder.io model IDs at creation time:
|
|
@@ -130,12 +205,3 @@ npm run typecheck # Type-check with tsc
|
|
|
130
205
|
npm run test # Run tests with vitest
|
|
131
206
|
npm run lint # Lint with eslint
|
|
132
207
|
```
|
|
133
|
-
|
|
134
|
-
## Build Output
|
|
135
|
-
|
|
136
|
-
Built with [tsup](https://tsup.egoist.dev/) producing:
|
|
137
|
-
|
|
138
|
-
- **CJS** (`.cjs`) — CommonJS for Node.js / legacy bundlers
|
|
139
|
-
- **ESM** (`.js`) — ES modules for modern bundlers
|
|
140
|
-
- **Declarations** (`.d.ts`, `.d.cts`) — TypeScript type definitions
|
|
141
|
-
- **Source maps** (`.map`) — for debugging
|
package/dist/data/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { BuilderResponseBaseData, ModelShape } from '@goldenhippo/builder-types';
|
|
1
|
+
import { BuilderResponseBaseData, ModelShape, BuilderContentReference } from '@goldenhippo/builder-types';
|
|
2
2
|
import { BuilderContent } from '@builder.io/sdk';
|
|
3
|
+
import { BuilderBlogPageContent } from '../page/index.mjs';
|
|
3
4
|
|
|
4
5
|
declare enum HeaderType {
|
|
5
6
|
BASIC = "BASIC",
|
|
@@ -76,6 +77,94 @@ declare enum SubscriptionCancelButtonType {
|
|
|
76
77
|
Cancel = "cancel"
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
declare const createCategoryModel: () => ModelShape;
|
|
81
|
+
type BuilderProductCategoryContent = BuilderContent & Partial<{
|
|
82
|
+
data: BuilderResponseBaseData & {
|
|
83
|
+
name: string;
|
|
84
|
+
description?: string;
|
|
85
|
+
desktopImage?: string;
|
|
86
|
+
mobileImage?: string;
|
|
87
|
+
hidden?: boolean;
|
|
88
|
+
slug?: string;
|
|
89
|
+
searchKeys?: string[];
|
|
90
|
+
};
|
|
91
|
+
}>;
|
|
92
|
+
|
|
93
|
+
declare const createIngredientsModel: () => ModelShape;
|
|
94
|
+
type BuilderIngredientContent = BuilderContent & Partial<{
|
|
95
|
+
data: BuilderResponseBaseData & {
|
|
96
|
+
name: string;
|
|
97
|
+
description?: string;
|
|
98
|
+
image?: string;
|
|
99
|
+
shortDescription?: string;
|
|
100
|
+
searchKeys?: string[];
|
|
101
|
+
};
|
|
102
|
+
}>;
|
|
103
|
+
|
|
104
|
+
declare const createProductUseCaseModel: () => ModelShape;
|
|
105
|
+
type BuilderProductUseCaseContent = BuilderContent & Partial<{
|
|
106
|
+
data: BuilderResponseBaseData & {
|
|
107
|
+
name: string;
|
|
108
|
+
description?: string;
|
|
109
|
+
searchKeys?: string[];
|
|
110
|
+
image?: string;
|
|
111
|
+
hidden?: boolean;
|
|
112
|
+
};
|
|
113
|
+
}>;
|
|
114
|
+
|
|
115
|
+
declare const createProductTagModel: () => ModelShape;
|
|
116
|
+
type BuilderProductTagContent = BuilderContent & Partial<{
|
|
117
|
+
data: BuilderResponseBaseData & {
|
|
118
|
+
name: string;
|
|
119
|
+
tagColor: string;
|
|
120
|
+
hidden?: boolean;
|
|
121
|
+
image?: string;
|
|
122
|
+
pluralDisplayName?: string;
|
|
123
|
+
};
|
|
124
|
+
}>;
|
|
125
|
+
|
|
126
|
+
interface ProductFilterModels {
|
|
127
|
+
categoryId: string;
|
|
128
|
+
useCaseId: string;
|
|
129
|
+
ingredientId: string;
|
|
130
|
+
tagId: string;
|
|
131
|
+
}
|
|
132
|
+
declare enum FilterApplicationType {
|
|
133
|
+
Inclusive = "Inclusive",
|
|
134
|
+
Exclusive = "Exclusive"
|
|
135
|
+
}
|
|
136
|
+
declare const createProductGridConfigModel: (models: ProductFilterModels) => ModelShape;
|
|
137
|
+
type BuilderProductGridFilterGroupContent = BuilderContent & Partial<{
|
|
138
|
+
data: BuilderResponseBaseData & {
|
|
139
|
+
displayName: string;
|
|
140
|
+
categories: {
|
|
141
|
+
category: {
|
|
142
|
+
id: string;
|
|
143
|
+
value: BuilderContentReference<BuilderProductCategoryContent>;
|
|
144
|
+
};
|
|
145
|
+
}[];
|
|
146
|
+
ingredients: {
|
|
147
|
+
ingredient: {
|
|
148
|
+
id: string;
|
|
149
|
+
value: BuilderContentReference<BuilderIngredientContent>;
|
|
150
|
+
};
|
|
151
|
+
}[];
|
|
152
|
+
useCases: {
|
|
153
|
+
useCase: {
|
|
154
|
+
id: string;
|
|
155
|
+
value: BuilderContentReference<BuilderProductUseCaseContent>;
|
|
156
|
+
};
|
|
157
|
+
}[];
|
|
158
|
+
tags: {
|
|
159
|
+
tag: {
|
|
160
|
+
id: string;
|
|
161
|
+
value: BuilderContentReference<BuilderProductTagContent>;
|
|
162
|
+
};
|
|
163
|
+
}[];
|
|
164
|
+
filterApplicationType: FilterApplicationType;
|
|
165
|
+
};
|
|
166
|
+
}>;
|
|
167
|
+
|
|
79
168
|
declare const createBrandConfigModel: (gridFilterModelId: string, bannerModelId: string) => ModelShape;
|
|
80
169
|
type BuilderBrandConfigContent = BuilderContent & Partial<{
|
|
81
170
|
data: BuilderResponseBaseData & {
|
|
@@ -106,7 +195,7 @@ type BuilderBrandConfigContent = BuilderContent & Partial<{
|
|
|
106
195
|
banners: {
|
|
107
196
|
banner: {
|
|
108
197
|
id: string;
|
|
109
|
-
|
|
198
|
+
value: BuilderContentReference<BuilderContent>;
|
|
110
199
|
};
|
|
111
200
|
alwaysShow: boolean;
|
|
112
201
|
}[];
|
|
@@ -251,6 +340,7 @@ type BuilderBrandConfigContent = BuilderContent & Partial<{
|
|
|
251
340
|
productGridFilterGroups: {
|
|
252
341
|
filterConfig: {
|
|
253
342
|
id: string;
|
|
343
|
+
value: BuilderContentReference<BuilderProductGridFilterGroupContent>;
|
|
254
344
|
};
|
|
255
345
|
}[];
|
|
256
346
|
productGridHideRestricted: boolean;
|
|
@@ -422,6 +512,7 @@ type BuilderBlogCommentContent = BuilderContent & Partial<{
|
|
|
422
512
|
blog: {
|
|
423
513
|
id: string;
|
|
424
514
|
name: string;
|
|
515
|
+
value: BuilderContentReference<BuilderBlogPageContent>;
|
|
425
516
|
};
|
|
426
517
|
};
|
|
427
518
|
}>;
|
|
@@ -459,26 +550,26 @@ type BuilderProductContent = BuilderContent & Partial<{
|
|
|
459
550
|
};
|
|
460
551
|
tags?: {
|
|
461
552
|
tag: {
|
|
462
|
-
|
|
463
|
-
|
|
553
|
+
id: string;
|
|
554
|
+
value: BuilderContentReference<BuilderProductTagContent>;
|
|
464
555
|
};
|
|
465
556
|
}[];
|
|
466
557
|
categories?: {
|
|
467
558
|
category: {
|
|
468
|
-
|
|
469
|
-
|
|
559
|
+
id: string;
|
|
560
|
+
value: BuilderContentReference<BuilderProductCategoryContent>;
|
|
470
561
|
};
|
|
471
562
|
}[];
|
|
472
563
|
ingredients?: {
|
|
473
564
|
ingredient: {
|
|
474
|
-
|
|
475
|
-
|
|
565
|
+
id: string;
|
|
566
|
+
value: BuilderContentReference<BuilderIngredientContent>;
|
|
476
567
|
};
|
|
477
568
|
}[];
|
|
478
569
|
useCases?: {
|
|
479
570
|
useCase: {
|
|
480
|
-
|
|
481
|
-
|
|
571
|
+
id: string;
|
|
572
|
+
value: BuilderContentReference<BuilderProductUseCaseContent>;
|
|
482
573
|
};
|
|
483
574
|
}[];
|
|
484
575
|
gh: {
|
|
@@ -502,7 +593,10 @@ type BuilderProductGroupContent = BuilderContent & Partial<{
|
|
|
502
593
|
gridTagline?: string;
|
|
503
594
|
shortDescription?: string;
|
|
504
595
|
products: {
|
|
505
|
-
product:
|
|
596
|
+
product: {
|
|
597
|
+
id: string;
|
|
598
|
+
value: BuilderContentReference<BuilderProductContent>;
|
|
599
|
+
};
|
|
506
600
|
displayName?: string;
|
|
507
601
|
isTrialSize?: boolean;
|
|
508
602
|
}[];
|
|
@@ -517,88 +611,4 @@ type BuilderProductGroupContent = BuilderContent & Partial<{
|
|
|
517
611
|
};
|
|
518
612
|
}>;
|
|
519
613
|
|
|
520
|
-
declare const createCategoryModel: () => ModelShape;
|
|
521
|
-
type BuilderProductCategoryContent = BuilderContent & Partial<{
|
|
522
|
-
data: BuilderResponseBaseData & {
|
|
523
|
-
name: string;
|
|
524
|
-
description?: string;
|
|
525
|
-
desktopImage?: string;
|
|
526
|
-
mobileImage?: string;
|
|
527
|
-
hidden?: boolean;
|
|
528
|
-
slug?: string;
|
|
529
|
-
searchKeys?: string[];
|
|
530
|
-
};
|
|
531
|
-
}>;
|
|
532
|
-
|
|
533
|
-
interface ProductFilterModels {
|
|
534
|
-
categoryId: string;
|
|
535
|
-
useCaseId: string;
|
|
536
|
-
ingredientId: string;
|
|
537
|
-
tagId: string;
|
|
538
|
-
}
|
|
539
|
-
declare enum FilterApplicationType {
|
|
540
|
-
Inclusive = "Inclusive",
|
|
541
|
-
Exclusive = "Exclusive"
|
|
542
|
-
}
|
|
543
|
-
declare const createProductGridConfigModel: (models: ProductFilterModels) => ModelShape;
|
|
544
|
-
type BuilderProductGridFilterGroupContent = BuilderContent & Partial<{
|
|
545
|
-
data: BuilderResponseBaseData & {
|
|
546
|
-
displayName: string;
|
|
547
|
-
categories: {
|
|
548
|
-
category: {
|
|
549
|
-
id: string;
|
|
550
|
-
} & Record<string, any>;
|
|
551
|
-
}[];
|
|
552
|
-
ingredients: {
|
|
553
|
-
ingredient: {
|
|
554
|
-
id: string;
|
|
555
|
-
} & Record<string, any>;
|
|
556
|
-
}[];
|
|
557
|
-
useCases: {
|
|
558
|
-
useCase: {
|
|
559
|
-
id: string;
|
|
560
|
-
} & Record<string, any>;
|
|
561
|
-
}[];
|
|
562
|
-
tags: {
|
|
563
|
-
tag: {
|
|
564
|
-
id: string;
|
|
565
|
-
} & Record<string, any>;
|
|
566
|
-
}[];
|
|
567
|
-
filterApplicationType: FilterApplicationType;
|
|
568
|
-
};
|
|
569
|
-
}>;
|
|
570
|
-
|
|
571
|
-
declare const createIngredientsModel: () => ModelShape;
|
|
572
|
-
type BuilderIngredientContent = BuilderContent & Partial<{
|
|
573
|
-
data: BuilderResponseBaseData & {
|
|
574
|
-
name: string;
|
|
575
|
-
description?: string;
|
|
576
|
-
image?: string;
|
|
577
|
-
shortDescription?: string;
|
|
578
|
-
searchKeys?: string[];
|
|
579
|
-
};
|
|
580
|
-
}>;
|
|
581
|
-
|
|
582
|
-
declare const createProductTagModel: () => ModelShape;
|
|
583
|
-
type BuilderProductTagContent = BuilderContent & Partial<{
|
|
584
|
-
data: BuilderResponseBaseData & {
|
|
585
|
-
name: string;
|
|
586
|
-
tagColor: string;
|
|
587
|
-
hidden?: boolean;
|
|
588
|
-
image?: string;
|
|
589
|
-
pluralDisplayName?: string;
|
|
590
|
-
};
|
|
591
|
-
}>;
|
|
592
|
-
|
|
593
|
-
declare const createProductUseCaseModel: () => ModelShape;
|
|
594
|
-
type BuilderProductUseCaseContent = BuilderContent & Partial<{
|
|
595
|
-
data: BuilderResponseBaseData & {
|
|
596
|
-
name: string;
|
|
597
|
-
description?: string;
|
|
598
|
-
searchKeys?: string[];
|
|
599
|
-
image?: string;
|
|
600
|
-
hidden?: boolean;
|
|
601
|
-
};
|
|
602
|
-
}>;
|
|
603
|
-
|
|
604
614
|
export { type BuilderBlogCategoryContent, type BuilderBlogCommentContent, type BuilderBrandConfigContent, type BuilderIngredientContent, type BuilderProductCategoryContent, type BuilderProductContent, type BuilderProductGridFilterGroupContent, type BuilderProductGroupContent, type BuilderProductTagContent, type BuilderProductUseCaseContent, createBlogCategoryModel, createBlogCommentModel, createBrandConfigModel, createCategoryModel, createIngredientsModel, createProductGridConfigModel, createProductGroupModel, createProductModel, createProductTagModel, createProductUseCaseModel };
|