@janbox/contentful-marketplace-sdk 2.0.0 → 3.0.0-alpha.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @janbox/contentful-marketplace-sdk
2
2
 
3
- TypeScript SDK for querying iChiba marketplace content from Contentful GraphQL.
3
+ TypeScript SDK for querying iChiba marketplace content from the internal headless CMS Delivery API (REST, Contentful CDA style).
4
4
 
5
5
  ## Features
6
6
 
@@ -11,17 +11,15 @@ TypeScript SDK for querying iChiba marketplace content from Contentful GraphQL.
11
11
 
12
12
  ## Installation
13
13
 
14
- `contentful` is a peer dependency and must be installed in your app.
15
-
16
14
  ```bash
17
15
  # pnpm
18
- pnpm add @janbox/contentful-marketplace-sdk contentful
16
+ pnpm add @janbox/contentful-marketplace-sdk
19
17
 
20
18
  # npm
21
- npm install @janbox/contentful-marketplace-sdk contentful
19
+ npm install @janbox/contentful-marketplace-sdk
22
20
 
23
21
  # yarn
24
- yarn add @janbox/contentful-marketplace-sdk contentful
22
+ yarn add @janbox/contentful-marketplace-sdk
25
23
  ```
26
24
 
27
25
  ## Quick Start
@@ -31,15 +29,13 @@ import {
31
29
  ContentfulSDK,
32
30
  getBannerCollections,
33
31
  } 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
32
 
42
- ContentfulSDK.configure(clientOptions);
33
+ ContentfulSDK.configure({
34
+ url: "https://headless-cms.ichiba.net/delivery/janbox",
35
+ headers: {
36
+ Authorization: `Bearer ${process.env.CMS_DELIVERY_TOKEN}`,
37
+ },
38
+ });
43
39
 
44
40
  const result = await getBannerCollections({
45
41
  slot: "home_hero",
@@ -59,20 +55,34 @@ console.log(result.items);
59
55
 
60
56
  Configure the SDK once before calling entry methods.
61
57
 
58
+ | Parameter | Type | Required | Description |
59
+ |-----------|--------------------------|----------|-------------------------------------------|
60
+ | `url` | `string` | ✅ | Delivery API base URL, e.g. `https://headless-cms.ichiba.net/delivery/janbox` |
61
+ | `method` | `string` | — | Deprecated and ignored: requests are always `GET` |
62
+ | `headers` | `Record<string, string>` | — | Custom headers, usually `Authorization: Bearer cdel_...` |
63
+
62
64
  ```ts
63
65
  ContentfulSDK.configure({
64
- space: "your-space-id",
65
- accessToken: "your-delivery-access-token",
66
- environment: "master",
66
+ url: "https://test-headless-cms.ichiba.net/delivery/janbox", // QC
67
+ headers: {
68
+ Authorization: "Bearer cdel_your-delivery-key",
69
+ },
67
70
  });
68
71
  ```
69
72
 
70
73
  Notes:
71
74
 
72
- - `environment` defaults to `"master"` if omitted.
73
- - The SDK sends requests to `https://graphql.contentful.com/content/v1/...`.
75
+ - The SDK sends `GET {url}/entries?...` with `Accept: application/json`. Custom headers are merged on top.
76
+ - Keep the delivery key server-side: call the SDK from your server (route handler, BFF), never from browser JavaScript.
74
77
  - Runtime requires `fetch` (Node.js 18+ recommended).
75
78
 
79
+ ## Breaking changes in 3.0.0
80
+
81
+ 1. `url` is now the Delivery API base URL of the internal CMS, and `headers` carries the delivery key (`Authorization: Bearer cdel_...`). `method` is still accepted but ignored.
82
+ 2. The delivery key must stay on the server, so the SDK must be called server-side.
83
+ 3. Types built on the `contentful` package are removed: `TypeXxxSkeleton`, the generic `TypeXxx<Modifiers, Locales>` types, and the `DocArticleEntry`, `DocCategoryEntry` and `HyperlinkEntry` aliases. `TypeXxxFields` are kept as plain interfaces.
84
+ 4. Entry ids are the new CMS ids (GUIDs). Old Contentful ids must be migrated by the apps: the CMS rejects them with `400 InvalidQuery`, which the SDK throws as `Error("Request failed: 400 ...")`.
85
+
76
86
  ## API Methods
77
87
 
78
88
  Collection methods return `{ page, total, size, items }`.
@@ -199,6 +209,21 @@ const keywords = await getKeywordCollections({
199
209
  });
200
210
  ```
201
211
 
212
+ ### Policy and Terms
213
+
214
+ - `getPolicyAndTerms(options?)` — returns a single entry (`limit: 1`), throws `404` if not found
215
+
216
+ ```ts
217
+ const policy = await getPolicyAndTerms({
218
+ marketCode: "vn",
219
+ locale: "vi",
220
+ });
221
+
222
+ // policy shape: { sys: { id }, title, locale, content: { json, links } | null }
223
+ ```
224
+
225
+ Filters `marketCode` / `locale` with global fallback (entries where `markets` / `locale` is not set are included).
226
+
202
227
  ## Error Handling
203
228
 
204
229
  `getBlogPostDetail`, `getDocCategoryDetail`, and `getDocArticleDetail` throw:
@@ -230,8 +255,8 @@ This supports global fallback content.
230
255
  The package exports:
231
256
 
232
257
  - client and entry functions from `src/client` and `src/entries`
233
- - Contentful schema types from `src/types`
234
- - entry aliases such as `BlogPostEntry`, `DocArticleEntry`, and `DocCategoryEntry`
258
+ - content model field types (`TypeXxxFields`) and CMS response types (`CmsEntry`, `CmsAsset`, `CmsLink`, `CmsCollection`) from `src/types`
259
+ - response item types such as `BlogPostGraphQLItem` and `DocArticleGraphQLItem` (names kept from 2.x)
235
260
 
236
261
  ## Development
237
262
 
@@ -242,3 +267,10 @@ pnpm install
242
267
  pnpm --filter @janbox/contentful-marketplace-sdk build
243
268
  pnpm --filter @janbox/contentful-marketplace-sdk test
244
269
  ```
270
+
271
+ Unit tests (`*.unit.test.ts`) mock `fetch` and always run. Integration tests (`*.test.ts`) call a real Delivery API and are skipped unless these variables are set (e.g. in `packages/marketplace-sdk/.env`):
272
+
273
+ ```bash
274
+ VITE_CMS_DELIVERY_URL=https://test-headless-cms.ichiba.net/delivery/janbox
275
+ VITE_CMS_DELIVERY_TOKEN=cdel_your-delivery-key
276
+ ```
@@ -1,5 +1,8 @@
1
+ import { CmsCollection, CmsEntry } from '../types';
2
+ import { QueryParams } from '../utils/query';
1
3
  export interface ContentfulSDKConfig {
2
4
  url: string;
5
+ /** @deprecated Ignored: requests are always GET. Kept so existing configs still type-check. */
3
6
  method?: string;
4
7
  headers?: Record<string, string>;
5
8
  }
@@ -7,5 +10,5 @@ export declare class ContentfulSDK {
7
10
  private static _config;
8
11
  static get config(): ContentfulSDKConfig;
9
12
  static configure(params: ContentfulSDKConfig): void;
10
- static graphqlQuery<T extends object>(query: string, variables?: Record<string, any>): Promise<T>;
13
+ static getEntries<T = CmsEntry>(params: QueryParams): Promise<CmsCollection<T>>;
11
14
  }
@@ -1,28 +1,26 @@
1
1
  import { PaginatedResponse } from '../utils';
2
- import { GraphQLAsset, TypeBannerCollectionSkeleton } from '../types';
3
- import { Entry, EntrySys } from 'contentful';
4
- import { HyperlinkEntry } from './hyperlink';
5
- type BannerCollectionEntry = Entry<TypeBannerCollectionSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
6
- type GraphQLEntrySys = Pick<EntrySys, "id">;
2
+ import { GraphQLAsset, TypeBannerCollectionFields } from '../types';
3
+ import { HyperlinkGraphQLItem } from './hyperlink';
4
+ type GraphQLEntrySys = {
5
+ id: string;
6
+ };
7
7
  export type BannerCollectionGraphQLItem = {
8
8
  sys: GraphQLEntrySys;
9
9
  bannersCollection: {
10
10
  items: Array<{
11
11
  sys: GraphQLEntrySys;
12
- hyperlink: ({
13
- sys: GraphQLEntrySys;
14
- } & Pick<HyperlinkEntry["fields"], "label" | "url" | "target" | "marketHandling">) | null;
12
+ hyperlink: HyperlinkGraphQLItem | null;
15
13
  media: GraphQLAsset;
16
14
  name: string;
17
15
  }>;
18
16
  };
19
- } & Pick<BannerCollectionEntry["fields"], "name" | "slot" | "platform">;
17
+ } & Pick<TypeBannerCollectionFields, "name" | "slot" | "platform">;
20
18
  export type GetBannerCollectionsResponse = PaginatedResponse<BannerCollectionGraphQLItem>;
21
19
  export declare const getBannerCollections: ({ slot, page, size, platform, marketCode, locale, }: {
22
- slot: BannerCollectionEntry["fields"]["slot"];
20
+ slot: TypeBannerCollectionFields["slot"];
23
21
  page?: number;
24
22
  size?: number;
25
- platform?: BannerCollectionEntry["fields"]["platform"][number];
23
+ platform?: TypeBannerCollectionFields["platform"][number];
26
24
  marketCode?: string;
27
25
  locale?: string;
28
26
  }) => Promise<GetBannerCollectionsResponse>;
@@ -1,14 +1,12 @@
1
1
  import { PaginatedResponse } from '../utils';
2
- import { GraphQLAsset, TypeBlogCategorySkeleton } from '../types';
2
+ import { GraphQLAsset, TypeBlogCategoryFields } from '../types';
3
3
  import { GraphQLSeo } from './seo';
4
- import { Entry } from 'contentful';
5
- type BlogCategoryEntry = Entry<TypeBlogCategorySkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
6
4
  export type BlogCategoryGraphQLItem = {
7
5
  sys: {
8
6
  id: string;
9
7
  };
10
8
  featuredImage: GraphQLAsset | null;
11
- } & Pick<BlogCategoryEntry["fields"], "title" | "slug">;
9
+ } & Pick<TypeBlogCategoryFields, "title" | "slug">;
12
10
  export interface BlogCategoryDetailGraphQLItem extends BlogCategoryGraphQLItem {
13
11
  description: string | null;
14
12
  seo: GraphQLSeo | null;
@@ -27,4 +25,3 @@ export declare const getBlogCategoryDetail: ({ id, slug, marketCode, locale, }:
27
25
  marketCode?: string;
28
26
  locale?: string;
29
27
  }) => Promise<GetBlogCategoryDetailResponse>;
30
- export {};
@@ -1,7 +1,5 @@
1
- import { TypeBlogCollectionSkeleton, GraphQLAsset } from '../types';
1
+ import { GraphQLAsset, TypeBlogCollectionFields } from '../types';
2
2
  import { PaginatedResponse } from '../utils';
3
- import { Entry } from 'contentful';
4
- type BlogCollectionEntry = Entry<TypeBlogCollectionSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
5
3
  type BlogCollectionCategoryGraphQLItem = {
6
4
  sys: {
7
5
  id: string;
@@ -17,7 +15,7 @@ export type BlogCollectionGraphQLItem = {
17
15
  featuredImage: GraphQLAsset | null;
18
16
  categoryId: string | null;
19
17
  collectionId: string | null;
20
- } & Pick<BlogCollectionEntry["fields"], "name" | "type" | "order">;
18
+ } & Pick<TypeBlogCollectionFields, "name" | "type" | "order">;
21
19
  export type GetBlogCollectionsResponse = PaginatedResponse<BlogCollectionGraphQLItem>;
22
20
  export declare const getBlogCollections: ({ marketCode, locale, page, size, }?: {
23
21
  marketCode?: string;
@@ -1,9 +1,11 @@
1
- import { GraphQLAsset, GraphQLAssetLink, TypeBlogPostSkeleton } from '../types';
1
+ import { GraphQLAsset, GraphQLAssetLink, TypeBlogPostFields } from '../types';
2
2
  import { PaginatedResponse } from '../utils';
3
- import { Entry, EntrySys } from 'contentful';
4
3
  import { GraphQLSeo } from './seo';
5
- type BlogPostEntry = Entry<TypeBlogPostSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
6
- type GraphQLEntrySys = Pick<EntrySys, "id" | "createdAt" | "updatedAt">;
4
+ type GraphQLEntrySys = {
5
+ id: string;
6
+ createdAt: string;
7
+ updatedAt: string;
8
+ };
7
9
  type BlogPostCategoryGraphQLItem = {
8
10
  sys: Pick<GraphQLEntrySys, "id">;
9
11
  title: string;
@@ -15,14 +17,14 @@ export type BlogPostGraphQLItem = {
15
17
  categoriesCollection: {
16
18
  items: BlogPostCategoryGraphQLItem[];
17
19
  } | null;
18
- } & Pick<BlogPostEntry["fields"], "title" | "shortDescription" | "slug">;
20
+ } & Pick<TypeBlogPostFields, "title" | "shortDescription" | "slug">;
19
21
  export interface BlogPostDetailGraphQLItem extends BlogPostGraphQLItem {
20
22
  author: {
21
23
  name: string;
22
24
  avatar: GraphQLAsset | null;
23
25
  } | null;
24
26
  content: {
25
- json: BlogPostEntry["fields"]["content"];
27
+ json: TypeBlogPostFields["content"];
26
28
  links: {
27
29
  assets: {
28
30
  block: GraphQLAssetLink[];
@@ -1,10 +1,8 @@
1
1
  import { PaginatedResponse } from '../utils';
2
- import { GraphQLAsset, TypeBrandCollectionSkeleton, TypeBrandSkeleton } from '../types';
3
- import { Entry, EntrySys } from 'contentful';
4
- import { HyperlinkEntry } from './hyperlink';
5
- type BrandEntry = Entry<TypeBrandSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
6
- type BrandCollectionEntry = Entry<TypeBrandCollectionSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
7
- type GraphQLEntrySys = Pick<EntrySys, "id">;
2
+ import { GraphQLAsset, TypeBrandCollectionFields, TypeBrandFields, TypeHyperlinkFields } from '../types';
3
+ type GraphQLEntrySys = {
4
+ id: string;
5
+ };
8
6
  export type BrandCollectionGraphQLItem = {
9
7
  sys: GraphQLEntrySys;
10
8
  brandsCollection: {
@@ -13,10 +11,10 @@ export type BrandCollectionGraphQLItem = {
13
11
  logo: GraphQLAsset | null;
14
12
  hyperlink: ({
15
13
  sys: GraphQLEntrySys;
16
- } & Pick<HyperlinkEntry["fields"], "label" | "url" | "target" | "marketHandling">) | null;
17
- } & Pick<BrandEntry["fields"], "name">>;
14
+ } & Pick<TypeHyperlinkFields, "label" | "url" | "target" | "marketHandling">) | null;
15
+ } & Pick<TypeBrandFields, "name">>;
18
16
  };
19
- } & Pick<BrandCollectionEntry["fields"], "name" | "slot">;
17
+ } & Pick<TypeBrandCollectionFields, "name" | "slot">;
20
18
  export type GetBrandCollectionsResponse = PaginatedResponse<BrandCollectionGraphQLItem>;
21
19
  export declare const getBrandCollections: ({ page, size, marketCode, locale, }?: {
22
20
  page?: number;
@@ -1,15 +1,16 @@
1
- import { GraphQLAsset, GraphQLAssetLink, TypeDocumentationArticleSkeleton, TypeDocumentationCategorySkeleton } from '../types';
1
+ import { GraphQLAsset, GraphQLAssetLink, TypeDocumentationArticleFields, TypeDocumentationCategoryFields } from '../types';
2
2
  import { PaginatedResponse } from '../utils';
3
- import { Entry, EntrySys } from 'contentful';
4
3
  import { GraphQLSeo } from './seo';
5
- export type DocCategoryEntry = Entry<TypeDocumentationCategorySkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
6
- export type DocArticleEntry = Entry<TypeDocumentationArticleSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
7
- type GraphQLEntrySys = Pick<EntrySys, "id" | "createdAt" | "updatedAt">;
4
+ type GraphQLEntrySys = {
5
+ id: string;
6
+ createdAt: string;
7
+ updatedAt: string;
8
+ };
8
9
  export type DocCategoryGraphQLItem = {
9
10
  sys: GraphQLEntrySys;
10
11
  seo: GraphQLSeo | null;
11
- } & Pick<DocCategoryEntry["fields"], "title" | "slug" | "description" | "order">;
12
- type DocArticleCategoryGraphQLItem = Pick<DocCategoryEntry["fields"], "title" | "slug" | "description" | "order">;
12
+ } & Pick<TypeDocumentationCategoryFields, "title" | "slug" | "description" | "order">;
13
+ type DocArticleCategoryGraphQLItem = Pick<TypeDocumentationCategoryFields, "title" | "slug" | "description" | "order">;
13
14
  export type DocArticleGraphQLItem = {
14
15
  sys: GraphQLEntrySys;
15
16
  category: DocArticleCategoryGraphQLItem;
@@ -17,10 +18,10 @@ export type DocArticleGraphQLItem = {
17
18
  name: string;
18
19
  avatar: GraphQLAsset | null;
19
20
  } | null;
20
- } & Pick<DocArticleEntry["fields"], "title" | "slug">;
21
+ } & Pick<TypeDocumentationArticleFields, "title" | "slug">;
21
22
  export interface DocArticleDetailGraphQLItem extends DocArticleGraphQLItem {
22
23
  content: {
23
- json: DocArticleEntry["fields"]["content"];
24
+ json: TypeDocumentationArticleFields["content"];
24
25
  links: {
25
26
  assets: {
26
27
  block: GraphQLAssetLink[];
@@ -1,17 +1,16 @@
1
1
  import { PaginatedResponse } from '../utils';
2
- import { TypeHyperlinkCollectionSkeleton } from '../types';
3
- import { Entry, EntrySys } from 'contentful';
4
- import { HyperlinkEntry } from './hyperlink';
5
- type HyperlinkCollectionEntry = Entry<TypeHyperlinkCollectionSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
6
- type GraphQLEntrySys = Pick<EntrySys, "id">;
2
+ import { TypeHyperlinkCollectionFields, TypeHyperlinkFields } from '../types';
3
+ type GraphQLEntrySys = {
4
+ id: string;
5
+ };
7
6
  export type HyperlinkCollectionGraphQLItem = {
8
7
  sys: GraphQLEntrySys;
9
8
  linksCollection: {
10
9
  items: Array<{
11
10
  sys: GraphQLEntrySys;
12
- } & Pick<HyperlinkEntry["fields"], "label" | "url" | "target" | 'marketHandling'>>;
11
+ } & Pick<TypeHyperlinkFields, "label" | "url" | "target" | 'marketHandling'>>;
13
12
  };
14
- } & Pick<HyperlinkCollectionEntry["fields"], "name" | "slot" | "order">;
13
+ } & Pick<TypeHyperlinkCollectionFields, "name" | "slot" | "order">;
15
14
  export type GetHyperlinkCollectionsResponse = PaginatedResponse<HyperlinkCollectionGraphQLItem>;
16
15
  export declare const getHyperlinkCollections: ({ page, size, marketCode, locale, }?: {
17
16
  page?: number;
@@ -1,3 +1,6 @@
1
- import { TypeHyperlinkSkeleton } from '../types';
2
- import { Entry } from 'contentful';
3
- export type HyperlinkEntry = Entry<TypeHyperlinkSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
1
+ import { TypeHyperlinkFields } from '../types';
2
+ export type HyperlinkGraphQLItem = {
3
+ sys: {
4
+ id: string;
5
+ };
6
+ } & Pick<TypeHyperlinkFields, "label" | "url" | "target" | "marketHandling">;
@@ -7,4 +7,5 @@ export * from './documentation';
7
7
  export * from './hyperlink-collection';
8
8
  export * from './hyperlink';
9
9
  export * from './keyword-collection';
10
+ export * from './policy-and-terms';
10
11
  export * from './seo';
@@ -1,11 +1,11 @@
1
1
  import { PaginatedResponse } from '../utils';
2
- import { TypeKeywordCollectionSkeleton } from '../types';
3
- import { Entry, EntrySys } from 'contentful';
4
- type KeywordCollectionEntry = Entry<TypeKeywordCollectionSkeleton, "WITHOUT_UNRESOLVABLE_LINKS", string>;
5
- type GraphQLEntrySys = Pick<EntrySys, "id">;
2
+ import { TypeKeywordCollectionFields } from '../types';
3
+ type GraphQLEntrySys = {
4
+ id: string;
5
+ };
6
6
  export type KeywordCollectionGraphQLItem = {
7
7
  sys: GraphQLEntrySys;
8
- } & Pick<KeywordCollectionEntry["fields"], "name" | "slot" | "keywords">;
8
+ } & Pick<TypeKeywordCollectionFields, "name" | "slot" | "keywords">;
9
9
  export type GetKeywordCollectionsResponse = PaginatedResponse<KeywordCollectionGraphQLItem>;
10
10
  export declare const getKeywordCollections: ({ page, size, marketCode, locale, }?: {
11
11
  page?: number;
@@ -0,0 +1,20 @@
1
+ import { GraphQLAssetLink, TypePolicyAndTermsFields } from '../types';
2
+ type GraphQLEntrySys = {
3
+ id: string;
4
+ };
5
+ export type PolicyAndTermsGraphQLItem = {
6
+ sys: GraphQLEntrySys;
7
+ content: {
8
+ json: TypePolicyAndTermsFields["content"];
9
+ links: {
10
+ assets: {
11
+ block: GraphQLAssetLink[];
12
+ };
13
+ };
14
+ } | null;
15
+ } & Pick<TypePolicyAndTermsFields, "title" | "locale">;
16
+ export declare const getPolicyAndTerms: ({ marketCode, locale, }?: {
17
+ marketCode?: string;
18
+ locale?: string;
19
+ }) => Promise<PolicyAndTermsGraphQLItem>;
20
+ export {};