@janbox/contentful-marketplace-sdk 0.0.9 → 1.0.1

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 CHANGED
@@ -1,363 +1,205 @@
1
- # iChiba Contentful SDK
1
+ # @janbox/contentful-marketplace-sdk
2
2
 
3
- A TypeScript SDK for accessing iChiba marketplace content from Contentful CMS. This SDK provides type-safe methods to retrieve banners, blog posts, documentation articles, and other content types with market and language filtering support.
3
+ TypeScript SDK for querying iChiba marketplace content from Contentful GraphQL.
4
4
 
5
5
  ## Features
6
6
 
7
- - 🔒 **Type-safe**: Full TypeScript support with auto-generated types from Contentful schemas
8
- - 🌐 **Multi-market**: Built-in filtering by market and language
9
- - 📝 **Content Types**: Support for banners, blog posts, documentation, and more
10
- - 🚀 **Easy to use**: Simple API with intuitive method names
11
- - 🎯 **Selective queries**: Optimized queries with field selection
12
- - ⚡ **Performance**: Built on top of the official Contentful SDK
7
+ - Typed API for marketplace content entries.
8
+ - Built-in market/language filtering in query builders.
9
+ - `find*` methods with 404 handling.
10
+ - Works in both ESM and CJS builds.
13
11
 
14
12
  ## Installation
15
13
 
14
+ `contentful` is a peer dependency and must be installed in your app.
15
+
16
16
  ```bash
17
- # Using npm
18
- npm install @janbox/contentful-marketplace-sdk
17
+ # pnpm
18
+ pnpm add @janbox/contentful-marketplace-sdk contentful
19
19
 
20
- # Using yarn
21
- yarn add @janbox/contentful-marketplace-sdk
20
+ # npm
21
+ npm install @janbox/contentful-marketplace-sdk contentful
22
22
 
23
- # Using pnpm
24
- pnpm add @janbox/contentful-marketplace-sdk
23
+ # yarn
24
+ yarn add @janbox/contentful-marketplace-sdk contentful
25
25
  ```
26
26
 
27
27
  ## Quick Start
28
28
 
29
- ### 1. Configure the SDK
30
-
31
- ```typescript
32
- import { ContentfulSDK } from '@janbox/contentful-marketplace-sdk';
33
-
34
- // Initialize the SDK with your Contentful credentials
35
- ContentfulSDK.configure({
36
- space: 'your-space-id',
37
- accessToken: 'your-access-token',
38
- environment: 'master', // optional, defaults to 'master'
29
+ ```ts
30
+ import {
31
+ ContentfulSDK,
32
+ listBannerCollectionEntries,
33
+ } from "@janbox/contentful-marketplace-sdk";
34
+ import type { CreateClientParams } from "contentful";
35
+
36
+ const clientOptions: CreateClientParams = {
37
+ space: process.env.CONTENTFUL_SPACE_ID!,
38
+ accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
39
+ environment: "master", // optional, defaults to "master"
40
+ };
41
+
42
+ ContentfulSDK.configure(clientOptions);
43
+
44
+ const result = await listBannerCollectionEntries({
45
+ slot: "home_hero",
46
+ marketCode: "vn",
47
+ language: "en",
48
+ platform: "WEB",
49
+ limit: 10,
39
50
  });
51
+
52
+ console.log(result.data);
40
53
  ```
41
54
 
42
- ### 2. Fetch Content
43
-
44
- ```typescript
45
- import {
46
- listBannerEntries,
47
- listBlogPostEntries,
48
- findBlogPostEntry,
49
- listDocCategoryEntries,
50
- listDocArticleEntries
51
- } from '@janbox/contentful-marketplace-sdk';
52
-
53
- // Get banners for a specific market and language
54
- const banners = await listBannerEntries({
55
- marketId: 'vn',
56
- language: 'vi',
57
- query: {
58
- 'fields.platform[in]': 'WEB',
59
- limit: 10
60
- }
61
- });
55
+ ## Configuration
62
56
 
63
- // Get blog posts
64
- const blogPosts = await listBlogPostEntries({
65
- marketId: 'vn',
66
- language: 'vi',
67
- query: {
68
- order: '-sys.createdAt',
69
- limit: 5
70
- }
71
- });
57
+ ### `ContentfulSDK.configure(params)`
72
58
 
73
- // Find a specific blog post by slug
74
- const blogPost = await findBlogPostEntry({
75
- marketId: 'vn',
76
- language: 'vi',
77
- query: {
78
- 'fields.slug': 'how-to-shop-online'
79
- }
80
- });
59
+ Configure the SDK once before calling entry methods.
81
60
 
82
- // Get documentation categories
83
- const docCategories = await listDocCategoryEntries({
84
- marketId: 'vn',
85
- language: 'vi',
86
- query: {
87
- order: 'fields.order'
88
- }
61
+ ```ts
62
+ ContentfulSDK.configure({
63
+ space: "your-space-id",
64
+ accessToken: "your-delivery-access-token",
65
+ environment: "master",
89
66
  });
90
67
  ```
91
68
 
92
- ## Supported Content Types
93
-
94
- ### 🎯 Banners
95
- - **Type**: `banner`
96
- - **Methods**: `listBannerEntries()`
97
- - **Fields**: name, image, redirectUrl, market, language, platform
98
- - **Platforms**: `MOBILE`, `WEB`
99
-
100
- ### 📝 Blog Posts
101
- - **Type**: `blogPost`
102
- - **Methods**: `listBlogPostEntries()`, `findBlogPostEntry()`
103
- - **Fields**: title, slug, author, featuredImage, content, category, shortDescription, seo
104
- - **Features**: Related posts, categories, SEO metadata
105
-
106
- ### 📚 Documentation
107
- - **Types**: `documentationCategory`, `documentationArticle`
108
- - **Methods**: `listDocCategoryEntries()`, `findDocCategoryEntry()`, `listDocArticleEntries()`, `findDocArticleEntry()`
109
- - **Features**: Hierarchical categories, searchable articles, SVG icons
110
-
111
- ### 👤 Authors
112
- - **Type**: `author`
113
- - **Fields**: name, avatar
69
+ Notes:
114
70
 
115
- ### 🏷️ Categories
116
- - **Types**: `blogCategory`, `documentationCategory`
117
- - **Features**: SEO support, hierarchical organization
71
+ - `environment` defaults to `"master"` if omitted.
72
+ - The SDK sends requests to `https://graphql.contentful.com/content/v1/...`.
73
+ - Runtime requires `fetch` (Node.js 18+ recommended).
118
74
 
119
- ### 🔍 SEO Metadata
120
- - **Type**: `seo`
121
- - **Fields**: metaTitle, metaDescription, metaKeywords, metaRobots
75
+ ## API Methods
122
76
 
123
- ## API Reference
77
+ All methods return `{ data: ... }`.
124
78
 
125
- ### Configuration
79
+ ### Banner Collection
126
80
 
127
- #### `ContentfulSDK.configure(params)`
81
+ - `listBannerCollectionEntries(options)`
128
82
 
129
- Configure the SDK with Contentful credentials.
130
-
131
- ```typescript
132
- ContentfulSDK.configure({
133
- space: string, // Your Contentful space ID
134
- accessToken: string, // Your Contentful access token
135
- environment?: string, // Environment (default: 'master')
136
- host?: string, // Custom host (optional)
137
- // ... other Contentful client options
138
- });
139
- ```
140
-
141
- ### Banner Methods
142
-
143
- #### `listBannerEntries(options)`
144
-
145
- Retrieve multiple banner entries.
146
-
147
- ```typescript
148
- const banners = await listBannerEntries({
149
- marketId: 'vn',
150
- language: 'vi',
151
- query: {
152
- 'fields.platform[in]': 'WEB,MOBILE',
153
- limit: 10,
154
- order: '-sys.createdAt'
155
- }
83
+ ```ts
84
+ await listBannerCollectionEntries({
85
+ slot: "home_hero",
86
+ limit: 20,
87
+ platform: "WEB",
88
+ marketCode: "vn",
89
+ language: "en",
156
90
  });
157
91
  ```
158
92
 
159
- ### Blog Methods
160
-
161
- #### `listBlogPostEntries(options)`
93
+ ### Blog
162
94
 
163
- Retrieve multiple blog post entries with basic fields.
95
+ - `listBlogPostEntries(options?)`
96
+ - `findBlogPostEntry(options)`
164
97
 
165
- ```typescript
98
+ ```ts
166
99
  const posts = await listBlogPostEntries({
167
- marketId: 'vn',
168
- language: 'vi',
169
- query: {
170
- 'fields.category.sys.id': 'category-id',
171
- limit: 10,
172
- skip: 0
173
- }
100
+ marketCode: "vn",
101
+ language: "en",
102
+ limit: 10,
103
+ skip: 0,
174
104
  });
175
- ```
176
105
 
177
- #### `findBlogPostEntry(options)`
178
-
179
- Retrieve a single blog post with full content.
180
-
181
- ```typescript
182
106
  const post = await findBlogPostEntry({
183
- marketId: 'vn',
184
- language: 'vi',
185
- query: {
186
- 'fields.slug': 'my-blog-post'
187
- }
107
+ slug: "how-to-shop-online",
108
+ marketCode: "vn",
109
+ language: "en",
188
110
  });
189
111
  ```
190
112
 
191
- ### Documentation Methods
192
-
193
- #### `listDocCategoryEntries(options)`
113
+ ### Documentation
194
114
 
195
- Retrieve documentation categories.
115
+ - `listDocCategoryEntries(options?)`
116
+ - `findDocCategoryEntry(options)`
117
+ - `listDocArticleEntries(options?)`
118
+ - `findDocArticleEntry(options)`
196
119
 
197
- ```typescript
120
+ ```ts
198
121
  const categories = await listDocCategoryEntries({
199
- marketId: 'vn',
200
- language: 'vi',
201
- query: {
202
- order: 'fields.order'
203
- }
122
+ marketCode: "vn",
123
+ language: "en",
124
+ limit: 10,
204
125
  });
205
- ```
206
-
207
- #### `findDocCategoryEntry(options)`
208
126
 
209
- Find a specific documentation category.
127
+ const articles = await listDocArticleEntries({
128
+ categorySlug: "getting-started",
129
+ marketCode: "vn",
130
+ language: "en",
131
+ limit: 10,
132
+ });
210
133
 
211
- ```typescript
212
- const category = await findDocCategoryEntry({
213
- marketId: 'vn',
214
- language: 'vi',
215
- query: {
216
- 'fields.slug': 'getting-started'
217
- }
134
+ const article = await findDocArticleEntry({
135
+ slug: "payment-methods",
136
+ marketCode: "vn",
137
+ language: "en",
218
138
  });
219
139
  ```
220
140
 
221
- #### `listDocArticleEntries(options)`
141
+ ### Brand / Hyperlink / Keyword Collections
222
142
 
223
- Retrieve documentation articles.
143
+ - `listBrandCollectionEntries(options?)`
144
+ - `listHyperlinkCollectionEntries(options?)`
145
+ - `listKeywordCollectionEntries(options?)`
224
146
 
225
- ```typescript
226
- const articles = await listDocArticleEntries({
227
- marketId: 'vn',
228
- language: 'vi',
229
- query: {
230
- 'fields.category.sys.id': 'category-id',
231
- order: '-sys.updatedAt'
232
- }
147
+ ```ts
148
+ const brands = await listBrandCollectionEntries({
149
+ marketCode: "vn",
150
+ language: "en",
151
+ limit: 20,
233
152
  });
234
- ```
235
-
236
- #### `findDocArticleEntry(options)`
237
153
 
238
- Find a specific documentation article.
154
+ const links = await listHyperlinkCollectionEntries({
155
+ marketCode: "vn",
156
+ language: "en",
157
+ });
239
158
 
240
- ```typescript
241
- const article = await findDocArticleEntry({
242
- marketId: 'vn',
243
- language: 'vi',
244
- query: {
245
- 'fields.slug': 'payment-methods'
246
- }
159
+ const keywords = await listKeywordCollectionEntries({
160
+ marketCode: "vn",
161
+ language: "en",
162
+ limit: 20,
247
163
  });
248
164
  ```
249
165
 
250
166
  ## Error Handling
251
167
 
252
- The SDK includes built-in error handling for common scenarios:
253
-
254
- ```typescript
255
- import { NotFoundResponse } from '@janbox/contentful-marketplace-sdk';
168
+ `findBlogPostEntry`, `findDocCategoryEntry`, and `findDocArticleEntry` throw a `Response` with status `404` when not found.
256
169
 
170
+ ```ts
257
171
  try {
258
- const post = await findBlogPostEntry({
259
- marketId: 'vn',
260
- language: 'vi',
261
- query: { 'fields.slug': 'non-existent-post' }
262
- });
172
+ await findDocArticleEntry({ slug: "unknown-slug" });
263
173
  } catch (error) {
264
- if (error instanceof NotFoundResponse) {
265
- console.log('Blog post not found');
174
+ if (error instanceof Response && error.status === 404) {
175
+ console.log("Entry not found");
266
176
  }
267
177
  }
268
178
  ```
269
179
 
270
- ## Type Definitions
271
-
272
- The SDK exports comprehensive TypeScript types for all content:
273
-
274
- ```typescript
275
- import type {
276
- // Entry Types
277
- BannerEntry,
278
- BlogPostEntry,
279
- DocCategoryEntry,
280
- DocArticleEntry,
281
-
282
- // Skeleton Types
283
- TypeBannerSkeleton,
284
- TypeBlogPostSkeleton,
285
- TypeDocumentationCategorySkeleton,
286
- TypeDocumentationArticleSkeleton,
287
-
288
- // Field Types
289
- TypeBannerFields,
290
- TypeBlogPostFields,
291
- // ... and many more
292
- } from '@janbox/contentful-marketplace-sdk';
293
- ```
180
+ ## Market/Language Filtering Behavior
294
181
 
295
- ## Query Options
182
+ When `marketCode` or `language` is provided, queries include both:
296
183
 
297
- All list methods accept Contentful's standard query parameters:
184
+ - entries matching the given code
185
+ - entries where that field is not set
298
186
 
299
- - `limit`: Number of entries to return (max 1000)
300
- - `skip`: Number of entries to skip
301
- - `order`: Sort order (e.g., '-sys.createdAt', 'fields.title')
302
- - `fields.*`: Filter by field values
303
- - `sys.*`: Filter by system properties
304
- - `include`: Include linked entries (0-10)
305
- - `select`: Choose specific fields to return
187
+ This supports global fallback content.
306
188
 
307
- ## Development
189
+ ## Exported Types
308
190
 
309
- ### Prerequisites
191
+ The package exports:
310
192
 
311
- - Node.js 18+
312
- - pnpm 8+
193
+ - client and entry functions from `src/client` and `src/entries`
194
+ - Contentful schema types from `src/types`
195
+ - entry aliases such as `BlogPostEntry`, `DocArticleEntry`, and `DocCategoryEntry`
313
196
 
314
- ### Setup
197
+ ## Development
315
198
 
316
- ```bash
317
- # Clone the repository
318
- git clone https://github.com/your-org/ichiba-contentful-sdk.git
199
+ From repository root:
319
200
 
320
- # Install dependencies
201
+ ```bash
321
202
  pnpm install
322
-
323
- # Build the SDK
324
- pnpm build:packages
325
- ```
326
-
327
- ### Project Structure
328
-
329
- ```
330
- ichiba-contentful-sdk/
331
- ├── packages/
332
- │ └── marketplace-sdk/
333
- │ ├── src/
334
- │ │ ├── client/ # SDK client configuration
335
- │ │ ├── entries/ # Content type methods
336
- │ │ ├── http-responses/ # Custom error responses
337
- │ │ ├── types/ # TypeScript type definitions
338
- │ │ └── index.ts # Main export file
339
- │ ├── dist/ # Built files
340
- │ └── package.json
341
- ├── examples/ # Usage examples
342
- └── package.json
203
+ pnpm --filter @janbox/contentful-marketplace-sdk build
204
+ pnpm --filter @janbox/contentful-marketplace-sdk test
343
205
  ```
344
-
345
- ## Contributing
346
-
347
- 1. Fork the repository
348
- 2. Create a feature branch: `git checkout -b feature/amazing-feature`
349
- 3. Commit your changes: `git commit -m 'Add amazing feature'`
350
- 4. Push to the branch: `git push origin feature/amazing-feature`
351
- 5. Open a Pull Request
352
-
353
- ## License
354
-
355
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
356
-
357
- ## Support
358
-
359
- For questions and support, please contact the iChiba development team or create an issue in the repository.
360
-
361
- ---
362
-
363
- **Built with ❤️ by the iChiba team**
@@ -1,9 +1,7 @@
1
- import { ContentfulClientApi, CreateClientParams } from 'contentful';
1
+ import { CreateClientParams } from 'contentful';
2
2
  export declare class ContentfulSDK {
3
- private static _client;
4
3
  private static _clientParams;
5
4
  static get clientParams(): CreateClientParams;
6
- static get client(): ContentfulClientApi<undefined>;
7
5
  static configure(params: CreateClientParams): void;
8
6
  static graphqlQuery<T extends object>(query: string, variables?: Record<string, any>): Promise<T>;
9
7
  }
@@ -0,0 +1,34 @@
1
+ import { TypeBannerCollectionSkeleton } from '../types';
2
+ import { Entry, EntrySys } from 'contentful';
3
+ import { HyperlinkEntry } from './hyperlink';
4
+ type BannerCollectionEntry = Entry<TypeBannerCollectionSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
5
+ type GraphQLEntrySys = Pick<EntrySys, "id">;
6
+ export type BannerCollectionGraphQLItem = {
7
+ sys: GraphQLEntrySys;
8
+ bannersCollection: {
9
+ items: Array<{
10
+ sys: GraphQLEntrySys;
11
+ hyperlink: ({
12
+ sys: GraphQLEntrySys;
13
+ } & Pick<HyperlinkEntry["fields"], "label" | "url" | "target" | "includesMarketCode">) | null;
14
+ media: {
15
+ url: string;
16
+ width: number | null;
17
+ height: number | null;
18
+ contentType: string;
19
+ };
20
+ name: string;
21
+ }>;
22
+ };
23
+ } & Pick<BannerCollectionEntry["fields"], "name" | "slot" | "platform">;
24
+ export type ListBannerCollectionEntriesResponse = {
25
+ data: BannerCollectionGraphQLItem[];
26
+ };
27
+ export declare const listBannerCollectionEntries: ({ slot, limit, platform, marketCode, language, }: {
28
+ slot: BannerCollectionEntry["fields"]["slot"];
29
+ limit?: number;
30
+ platform?: BannerCollectionEntry["fields"]["platform"][number];
31
+ marketCode?: string;
32
+ language?: string;
33
+ }) => Promise<ListBannerCollectionEntriesResponse>;
34
+ export {};
@@ -1,13 +1,54 @@
1
- import { EntriesQueries, Entry } from 'contentful';
2
1
  import { TypeBlogPostSkeleton } from '../types';
3
- export type BlogPostEntry = Entry<TypeBlogPostSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
4
- export declare const listBlogPostEntries: ({ query, marketId, language, }: {
5
- marketId: string;
6
- language: string;
7
- query?: Partial<EntriesQueries<TypeBlogPostSkeleton, "WITHOUT_UNRESOLVABLE_LINKS">>;
8
- }) => Promise<import('contentful').EntryCollection<TypeBlogPostSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>>;
9
- export declare const findBlogPostEntry: ({ query, marketId, language, }: {
10
- marketId: string;
11
- language: string;
12
- query: Partial<EntriesQueries<TypeBlogPostSkeleton, "WITHOUT_UNRESOLVABLE_LINKS">>;
13
- }) => Promise<Entry<TypeBlogPostSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>>;
2
+ import { Entry, EntrySys } from 'contentful';
3
+ type BlogPostEntry = Entry<TypeBlogPostSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
4
+ type GraphQLEntrySys = Pick<EntrySys, "id">;
5
+ type GraphQLAsset = {
6
+ url: string;
7
+ width: number | null;
8
+ height: number | null;
9
+ contentType: string;
10
+ };
11
+ export type BlogPostGraphQLItem = {
12
+ sys: GraphQLEntrySys;
13
+ category: {
14
+ sys: GraphQLEntrySys;
15
+ title: string;
16
+ slug: string;
17
+ } | null;
18
+ featuredImage: GraphQLAsset | null;
19
+ } & Pick<BlogPostEntry["fields"], "title" | "shortDescription" | "slug">;
20
+ export type BlogPostDetailGraphQLItem = BlogPostGraphQLItem & {
21
+ author: {
22
+ sys: GraphQLEntrySys;
23
+ name: string;
24
+ avatar: GraphQLAsset | null;
25
+ } | null;
26
+ content: {
27
+ json: BlogPostEntry["fields"]["content"];
28
+ } | null;
29
+ seo: {
30
+ sys: GraphQLEntrySys;
31
+ metaTitle: string;
32
+ metaDescription: string | null;
33
+ metaKeywords: string[] | null;
34
+ metaRobots: string[] | null;
35
+ } | null;
36
+ };
37
+ export type ListBlogPostEntriesResponse = {
38
+ data: BlogPostGraphQLItem[];
39
+ };
40
+ export type FindBlogPostEntryResponse = {
41
+ data: BlogPostDetailGraphQLItem;
42
+ };
43
+ export declare const listBlogPostEntries: ({ marketCode, language, limit, skip, }?: {
44
+ marketCode?: string;
45
+ language?: string;
46
+ limit?: number;
47
+ skip?: number;
48
+ }) => Promise<ListBlogPostEntriesResponse>;
49
+ export declare const findBlogPostEntry: ({ slug, marketCode, language, }: {
50
+ slug: string;
51
+ marketCode?: string;
52
+ language?: string;
53
+ }) => Promise<FindBlogPostEntryResponse>;
54
+ export {};
@@ -0,0 +1,22 @@
1
+ import { TypeBrandCollectionSkeleton, TypeBrandSkeleton } from '../types';
2
+ import { Entry, EntrySys } from 'contentful';
3
+ type BrandEntry = Entry<TypeBrandSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
4
+ type BrandCollectionEntry = Entry<TypeBrandCollectionSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
5
+ type GraphQLEntrySys = Pick<EntrySys, "id">;
6
+ export type BrandCollectionGraphQLItem = {
7
+ sys: GraphQLEntrySys;
8
+ brandsCollection: {
9
+ items: Array<{
10
+ sys: GraphQLEntrySys;
11
+ } & Pick<BrandEntry["fields"], "name" | "slug">>;
12
+ };
13
+ } & Pick<BrandCollectionEntry["fields"], "name" | "slot">;
14
+ export type ListBrandCollectionEntriesResponse = {
15
+ data: BrandCollectionGraphQLItem[];
16
+ };
17
+ export declare const listBrandCollectionEntries: ({ limit, marketCode, language, }?: {
18
+ limit?: number;
19
+ marketCode?: string;
20
+ language?: string;
21
+ }) => Promise<ListBrandCollectionEntriesResponse>;
22
+ export {};