@better-i18n/sdk 0.4.0 → 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.
@@ -0,0 +1,21 @@
1
+ import type { ClientConfig, ContentClient } from "./types.js";
2
+ /**
3
+ * Creates a Better i18n content client.
4
+ *
5
+ * Fetches content models and entries from the REST API.
6
+ * Requires an API key for authentication.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const client = createClient({
11
+ * project: "acme/web-app",
12
+ * apiKey: "bi18n_...",
13
+ * });
14
+ *
15
+ * const models = await client.getModels();
16
+ * const posts = await client.getEntries("blog-posts", { language: "fr" });
17
+ * const post = await client.getEntry("blog-posts", "hello-world");
18
+ * ```
19
+ */
20
+ export declare function createClient(config: ClientConfig): ContentClient;
21
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAK9D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAoBhE"}
package/dist/client.js ADDED
@@ -0,0 +1,35 @@
1
+ import { createContentAPIClient } from "./content-api.js";
2
+ const DEFAULT_API_BASE = "https://content.better-i18n.com";
3
+ /**
4
+ * Creates a Better i18n content client.
5
+ *
6
+ * Fetches content models and entries from the REST API.
7
+ * Requires an API key for authentication.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const client = createClient({
12
+ * project: "acme/web-app",
13
+ * apiKey: "bi18n_...",
14
+ * });
15
+ *
16
+ * const models = await client.getModels();
17
+ * const posts = await client.getEntries("blog-posts", { language: "fr" });
18
+ * const post = await client.getEntry("blog-posts", "hello-world");
19
+ * ```
20
+ */
21
+ export function createClient(config) {
22
+ const apiBase = (config.apiBase || DEFAULT_API_BASE).replace(/\/$/, "");
23
+ const parts = config.project.split("/");
24
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
25
+ throw new Error(`Invalid project format "${config.project}". Expected "org/project" (e.g., "acme/web-app").`);
26
+ }
27
+ const [org, project] = parts;
28
+ if (!config.apiKey) {
29
+ throw new Error("API key is required for content API access.\n" +
30
+ "Set apiKey in your client config:\n\n" +
31
+ ' createClient({ project: "acme/web-app", apiKey: "bi18n_..." })');
32
+ }
33
+ return createContentAPIClient(apiBase, org, project, config.apiKey, config.debug);
34
+ }
35
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,MAAM,gBAAgB,GAAG,iCAAiC,CAAC;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAExE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CACb,2BAA2B,MAAM,CAAC,OAAO,mDAAmD,CAC7F,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC;IAE7B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,+CAA+C;YAC7C,uCAAuC;YACvC,kEAAkE,CACrE,CAAC;IACJ,CAAC;IAED,OAAO,sBAAsB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;AACpF,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { ContentClient } from "./types.js";
2
+ /**
3
+ * Creates a content client that fetches from the REST API.
4
+ *
5
+ * This mode supports filtering, pagination, and real-time data (no caching lag).
6
+ * Requires an API key for authentication via the `x-api-key` header.
7
+ *
8
+ * URL patterns:
9
+ * - Models: `{apiBase}/v1/content/{org}/{project}/models`
10
+ * - Entries: `{apiBase}/v1/content/{org}/{project}/models/{model}/entries`
11
+ * - Entry: `{apiBase}/v1/content/{org}/{project}/models/{model}/entries/{slug}`
12
+ */
13
+ export declare function createContentAPIClient(apiBase: string, org: string, project: string, apiKey: string, debug?: boolean): ContentClient;
14
+ //# sourceMappingURL=content-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-api.d.ts","sourceRoot":"","sources":["../src/content-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAOd,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,UAAQ,GACZ,aAAa,CA2Ef"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Creates a content client that fetches from the REST API.
3
+ *
4
+ * This mode supports filtering, pagination, and real-time data (no caching lag).
5
+ * Requires an API key for authentication via the `x-api-key` header.
6
+ *
7
+ * URL patterns:
8
+ * - Models: `{apiBase}/v1/content/{org}/{project}/models`
9
+ * - Entries: `{apiBase}/v1/content/{org}/{project}/models/{model}/entries`
10
+ * - Entry: `{apiBase}/v1/content/{org}/{project}/models/{model}/entries/{slug}`
11
+ */
12
+ export function createContentAPIClient(apiBase, org, project, apiKey, debug = false) {
13
+ const base = `${apiBase}/v1/content/${org}/${project}`;
14
+ const headers = {
15
+ "x-api-key": apiKey,
16
+ "content-type": "application/json",
17
+ };
18
+ const log = debug
19
+ ? (...args) => console.log("[better-i18n]", ...args)
20
+ : () => { };
21
+ if (debug) {
22
+ log("Client initialized", { apiBase, org, project, base });
23
+ }
24
+ async function request(url, label) {
25
+ log(`→ ${label}`, url);
26
+ const res = await fetch(url, { headers });
27
+ log(`← ${res.status} ${res.statusText}`);
28
+ if (!res.ok) {
29
+ const body = await res.text().catch(() => "");
30
+ log(" Error body:", body);
31
+ throw new Error(`API error ${label}: ${res.status} ${body}`);
32
+ }
33
+ const data = await res.json();
34
+ log(" Response:", JSON.stringify(data).slice(0, 500));
35
+ return { res, data };
36
+ }
37
+ return {
38
+ async getModels() {
39
+ const { data } = await request(`${base}/models`, "getModels");
40
+ return data;
41
+ },
42
+ async getEntries(modelSlug, options) {
43
+ const params = new URLSearchParams();
44
+ if (options?.language)
45
+ params.set("language", options.language);
46
+ if (options?.status)
47
+ params.set("status", options.status);
48
+ if (options?.sort)
49
+ params.set("sort", options.sort);
50
+ if (options?.order)
51
+ params.set("order", options.order);
52
+ if (options?.page)
53
+ params.set("page", String(options.page));
54
+ if (options?.limit)
55
+ params.set("limit", String(options.limit));
56
+ const qs = params.toString() ? `?${params}` : "";
57
+ const { data } = await request(`${base}/models/${modelSlug}/entries${qs}`, `getEntries(${modelSlug})`);
58
+ return {
59
+ items: data.items,
60
+ total: data.total,
61
+ hasMore: data.hasMore,
62
+ };
63
+ },
64
+ async getEntry(modelSlug, entrySlug, options) {
65
+ const params = new URLSearchParams();
66
+ if (options?.language)
67
+ params.set("language", options.language);
68
+ const qs = params.toString() ? `?${params}` : "";
69
+ const { data } = await request(`${base}/models/${modelSlug}/entries/${entrySlug}${qs}`, `getEntry(${modelSlug}/${entrySlug})`);
70
+ return data;
71
+ },
72
+ };
73
+ }
74
+ //# sourceMappingURL=content-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-api.js","sourceRoot":"","sources":["../src/content-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { createClient } from "./client.js";
2
+ export { createContentAPIClient } from "./content-api.js";
3
+ export type { ClientConfig, ContentClient, ContentEntry, ContentEntryListItem, ContentEntryStatus, ContentEntrySortField, ContentModel, ListEntriesOptions, GetEntryOptions, PaginatedResponse, } from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,YAAY,EACV,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createClient } from "./client.js";
2
+ export { createContentAPIClient } from "./content-api.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,107 @@
1
+ /** Configuration for creating a Better i18n content client. */
2
+ export interface ClientConfig {
3
+ /** Project identifier in `org/project` format (e.g., "acme-corp/web-app"). Same as the dashboard URL path. */
4
+ project: string;
5
+ /** API key for authenticating content requests. Required. */
6
+ apiKey: string;
7
+ /** REST API base URL. Defaults to `https://dash.better-i18n.com`. */
8
+ apiBase?: string;
9
+ /** Enable debug logging to see request URLs, headers, and responses. */
10
+ debug?: boolean;
11
+ }
12
+ /**
13
+ * A full content entry with all localized fields.
14
+ *
15
+ * @typeParam CF - Custom fields shape. Defaults to `Record<string, string | null>`.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // Typed custom fields
20
+ * interface BlogFields { readingTime: string | null; category: string | null }
21
+ * const post = await client.getEntry<BlogFields>("blog", "hello-world");
22
+ * post.customFields.readingTime; // string | null (typed!)
23
+ * ```
24
+ */
25
+ export interface ContentEntry<CF extends Record<string, string | null> = Record<string, string | null>> {
26
+ id: string;
27
+ slug: string;
28
+ status: "draft" | "published" | "archived";
29
+ publishedAt: string | null;
30
+ sourceLanguage: string;
31
+ availableLanguages: string[];
32
+ featuredImage: string | null;
33
+ tags: string[];
34
+ author: {
35
+ name: string;
36
+ image: string | null;
37
+ } | null;
38
+ customFields: CF;
39
+ title: string;
40
+ excerpt: string | null;
41
+ body: Record<string, any> | null;
42
+ bodyHtml: string | null;
43
+ bodyMarkdown: string | null;
44
+ metaTitle: string | null;
45
+ metaDescription: string | null;
46
+ }
47
+ /** Entry status filter values. */
48
+ export type ContentEntryStatus = "draft" | "published" | "archived";
49
+ /** A summary item for content entry lists. */
50
+ export interface ContentEntryListItem {
51
+ slug: string;
52
+ title: string;
53
+ excerpt: string | null;
54
+ publishedAt: string | null;
55
+ featuredImage: string | null;
56
+ tags: string[];
57
+ author: {
58
+ name: string;
59
+ image: string | null;
60
+ } | null;
61
+ }
62
+ /** Paginated response wrapper. */
63
+ export interface PaginatedResponse<T> {
64
+ items: T[];
65
+ total: number;
66
+ hasMore: boolean;
67
+ }
68
+ /** A content model definition. */
69
+ export interface ContentModel {
70
+ slug: string;
71
+ displayName: string;
72
+ description: string | null;
73
+ kind: string;
74
+ entryCount: number;
75
+ }
76
+ /** Sortable fields for content entries. */
77
+ export type ContentEntrySortField = "publishedAt" | "createdAt" | "updatedAt" | "title";
78
+ /** Options for listing content entries. */
79
+ export interface ListEntriesOptions {
80
+ /** Language code for localized content. Defaults to source language. */
81
+ language?: string;
82
+ /** Filter by entry status. */
83
+ status?: ContentEntryStatus;
84
+ /** Field to sort by. Defaults to `"updatedAt"`. */
85
+ sort?: ContentEntrySortField;
86
+ /** Sort direction. Defaults to `"desc"`. */
87
+ order?: "asc" | "desc";
88
+ /** Page number (1-based). */
89
+ page?: number;
90
+ /** Max entries per page (1-100). Defaults to 50. */
91
+ limit?: number;
92
+ }
93
+ /** Options for fetching a single content entry. */
94
+ export interface GetEntryOptions {
95
+ /** Language code for localized content. Defaults to source language. */
96
+ language?: string;
97
+ }
98
+ /** Client for fetching content entries and models. */
99
+ export interface ContentClient {
100
+ /** List all content models in the project. */
101
+ getModels(): Promise<ContentModel[]>;
102
+ /** List entries for a content model with pagination. */
103
+ getEntries(modelSlug: string, options?: ListEntriesOptions): Promise<PaginatedResponse<ContentEntryListItem>>;
104
+ /** Fetch a single content entry by slug. */
105
+ getEntry<CF extends Record<string, string | null> = Record<string, string | null>>(modelSlug: string, entrySlug: string, options?: GetEntryOptions): Promise<ContentEntry<CF>>;
106
+ }
107
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // ─── Client Configuration ────────────────────────────────────────────
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE"}
package/package.json CHANGED
@@ -1,19 +1,26 @@
1
1
  {
2
2
  "name": "@better-i18n/sdk",
3
- "version": "0.4.0",
3
+ "version": "1.0.1",
4
4
  "description": "Content SDK for Better i18n - headless CMS client for fetching content models and entries",
5
5
  "type": "module",
6
- "main": "src/index.ts",
7
- "types": "src/index.ts",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
8
  "exports": {
9
- ".": "./src/index.ts"
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "bun": "./src/index.ts",
12
+ "default": "./dist/index.js"
13
+ }
10
14
  },
11
15
  "files": [
12
- "src",
16
+ "dist",
13
17
  "README.md"
14
18
  ],
15
19
  "scripts": {
16
- "typecheck": "tsc --noEmit"
20
+ "build": "tsc",
21
+ "typecheck": "tsc --noEmit",
22
+ "clean": "rm -rf dist",
23
+ "prepublishOnly": "bun run build"
17
24
  },
18
25
  "keywords": [
19
26
  "content",
package/src/client.ts DELETED
@@ -1,44 +0,0 @@
1
- import type { ClientConfig, ContentClient } from "./types";
2
- import { createContentAPIClient } from "./content-api";
3
-
4
- const DEFAULT_API_BASE = "https://dash.better-i18n.com";
5
-
6
- /**
7
- * Creates a Better i18n content client.
8
- *
9
- * Fetches content models and entries from the REST API.
10
- * Requires an API key for authentication.
11
- *
12
- * @example
13
- * ```typescript
14
- * const client = createClient({
15
- * project: "acme/web-app",
16
- * apiKey: "bi18n_...",
17
- * });
18
- *
19
- * const models = await client.getModels();
20
- * const posts = await client.getEntries("blog-posts", { language: "fr" });
21
- * const post = await client.getEntry("blog-posts", "hello-world");
22
- * ```
23
- */
24
- export function createClient(config: ClientConfig): ContentClient {
25
- const apiBase = (config.apiBase || DEFAULT_API_BASE).replace(/\/$/, "");
26
-
27
- const parts = config.project.split("/");
28
- if (parts.length !== 2 || !parts[0] || !parts[1]) {
29
- throw new Error(
30
- `Invalid project format "${config.project}". Expected "org/project" (e.g., "acme/web-app").`,
31
- );
32
- }
33
- const [org, project] = parts;
34
-
35
- if (!config.apiKey) {
36
- throw new Error(
37
- "API key is required for content API access.\n" +
38
- "Set apiKey in your client config:\n\n" +
39
- ' createClient({ project: "acme/web-app", apiKey: "bi18n_..." })',
40
- );
41
- }
42
-
43
- return createContentAPIClient(apiBase, org, project, config.apiKey, config.debug);
44
- }
@@ -1,103 +0,0 @@
1
- import type {
2
- ContentClient,
3
- ContentEntry,
4
- ContentEntryListItem,
5
- ContentModel,
6
- ListEntriesOptions,
7
- GetEntryOptions,
8
- PaginatedResponse,
9
- } from "./types";
10
-
11
- /**
12
- * Creates a content client that fetches from the REST API.
13
- *
14
- * This mode supports filtering, pagination, and real-time data (no caching lag).
15
- * Requires an API key for authentication via the `x-api-key` header.
16
- *
17
- * URL patterns:
18
- * - Models: `{apiBase}/api/v1/content/{org}/{project}/models`
19
- * - Entries: `{apiBase}/api/v1/content/{org}/{project}/models/{model}/entries`
20
- * - Entry: `{apiBase}/api/v1/content/{org}/{project}/models/{model}/entries/{slug}`
21
- */
22
- export function createContentAPIClient(
23
- apiBase: string,
24
- org: string,
25
- project: string,
26
- apiKey: string,
27
- debug = false,
28
- ): ContentClient {
29
- const base = `${apiBase}/api/v1/content/${org}/${project}`;
30
- const headers: Record<string, string> = {
31
- "x-api-key": apiKey,
32
- "content-type": "application/json",
33
- };
34
-
35
- const log = debug
36
- ? (...args: unknown[]) => console.log("[better-i18n]", ...args)
37
- : () => {};
38
-
39
- if (debug) {
40
- log("Client initialized", { apiBase, org, project, base });
41
- }
42
-
43
- async function request<T>(url: string, label: string): Promise<{ res: Response; data: T }> {
44
- log(`→ ${label}`, url);
45
- const res = await fetch(url, { headers });
46
- log(`← ${res.status} ${res.statusText}`);
47
- if (!res.ok) {
48
- const body = await res.text().catch(() => "");
49
- log(" Error body:", body);
50
- throw new Error(`API error ${label}: ${res.status} ${body}`);
51
- }
52
- const data = await res.json() as T;
53
- log(" Response:", JSON.stringify(data).slice(0, 500));
54
- return { res, data };
55
- }
56
-
57
- return {
58
- async getModels(): Promise<ContentModel[]> {
59
- const { data } = await request<ContentModel[]>(`${base}/models`, "getModels");
60
- return data;
61
- },
62
-
63
- async getEntries(
64
- modelSlug: string,
65
- options?: ListEntriesOptions,
66
- ): Promise<PaginatedResponse<ContentEntryListItem>> {
67
- const params = new URLSearchParams();
68
- if (options?.language) params.set("language", options.language);
69
- if (options?.status) params.set("status", options.status);
70
- if (options?.sort) params.set("sort", options.sort);
71
- if (options?.order) params.set("order", options.order);
72
- if (options?.page) params.set("page", String(options.page));
73
- if (options?.limit) params.set("limit", String(options.limit));
74
- const qs = params.toString() ? `?${params}` : "";
75
-
76
- const { data } = await request<{ items: ContentEntryListItem[]; total: number; hasMore: boolean }>(
77
- `${base}/models/${modelSlug}/entries${qs}`,
78
- `getEntries(${modelSlug})`,
79
- );
80
- return {
81
- items: data.items,
82
- total: data.total,
83
- hasMore: data.hasMore,
84
- };
85
- },
86
-
87
- async getEntry<CF extends Record<string, string | null> = Record<string, string | null>>(
88
- modelSlug: string,
89
- entrySlug: string,
90
- options?: GetEntryOptions,
91
- ): Promise<ContentEntry<CF>> {
92
- const params = new URLSearchParams();
93
- if (options?.language) params.set("language", options.language);
94
- const qs = params.toString() ? `?${params}` : "";
95
-
96
- const { data } = await request<ContentEntry<CF>>(
97
- `${base}/models/${modelSlug}/entries/${entrySlug}${qs}`,
98
- `getEntry(${modelSlug}/${entrySlug})`,
99
- );
100
- return data;
101
- },
102
- };
103
- }
package/src/index.ts DELETED
@@ -1,14 +0,0 @@
1
- export { createClient } from "./client";
2
- export { createContentAPIClient } from "./content-api";
3
- export type {
4
- ClientConfig,
5
- ContentClient,
6
- ContentEntry,
7
- ContentEntryListItem,
8
- ContentEntryStatus,
9
- ContentEntrySortField,
10
- ContentModel,
11
- ListEntriesOptions,
12
- GetEntryOptions,
13
- PaginatedResponse,
14
- } from "./types";
package/src/types.ts DELETED
@@ -1,124 +0,0 @@
1
- // ─── Client Configuration ────────────────────────────────────────────
2
-
3
- /** Configuration for creating a Better i18n content client. */
4
- export interface ClientConfig {
5
- /** Project identifier in `org/project` format (e.g., "acme-corp/web-app"). Same as the dashboard URL path. */
6
- project: string;
7
- /** API key for authenticating content requests. Required. */
8
- apiKey: string;
9
- /** REST API base URL. Defaults to `https://dash.better-i18n.com`. */
10
- apiBase?: string;
11
- /** Enable debug logging to see request URLs, headers, and responses. */
12
- debug?: boolean;
13
- }
14
-
15
- // ─── Content Types ───────────────────────────────────────────────────
16
-
17
- /**
18
- * A full content entry with all localized fields.
19
- *
20
- * @typeParam CF - Custom fields shape. Defaults to `Record<string, string | null>`.
21
- *
22
- * @example
23
- * ```typescript
24
- * // Typed custom fields
25
- * interface BlogFields { readingTime: string | null; category: string | null }
26
- * const post = await client.getEntry<BlogFields>("blog", "hello-world");
27
- * post.customFields.readingTime; // string | null (typed!)
28
- * ```
29
- */
30
- export interface ContentEntry<CF extends Record<string, string | null> = Record<string, string | null>> {
31
- id: string;
32
- slug: string;
33
- status: "draft" | "published" | "archived";
34
- publishedAt: string | null;
35
- sourceLanguage: string;
36
- availableLanguages: string[];
37
- featuredImage: string | null;
38
- tags: string[];
39
- author: { name: string; image: string | null } | null;
40
- customFields: CF;
41
- // Localized content
42
- title: string;
43
- excerpt: string | null;
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
- body: Record<string, any> | null;
46
- bodyHtml: string | null;
47
- bodyMarkdown: string | null;
48
- metaTitle: string | null;
49
- metaDescription: string | null;
50
- }
51
-
52
- /** Entry status filter values. */
53
- export type ContentEntryStatus = "draft" | "published" | "archived";
54
-
55
- /** A summary item for content entry lists. */
56
- export interface ContentEntryListItem {
57
- slug: string;
58
- title: string;
59
- excerpt: string | null;
60
- publishedAt: string | null;
61
- featuredImage: string | null;
62
- tags: string[];
63
- author: { name: string; image: string | null } | null;
64
- }
65
-
66
- /** Paginated response wrapper. */
67
- export interface PaginatedResponse<T> {
68
- items: T[];
69
- total: number;
70
- hasMore: boolean;
71
- }
72
-
73
- /** A content model definition. */
74
- export interface ContentModel {
75
- slug: string;
76
- displayName: string;
77
- description: string | null;
78
- kind: string;
79
- entryCount: number;
80
- }
81
-
82
- // ─── Client Interface ───────────────────────────────────────────────
83
-
84
- /** Sortable fields for content entries. */
85
- export type ContentEntrySortField = "publishedAt" | "createdAt" | "updatedAt" | "title";
86
-
87
- /** Options for listing content entries. */
88
- export interface ListEntriesOptions {
89
- /** Language code for localized content. Defaults to source language. */
90
- language?: string;
91
- /** Filter by entry status. */
92
- status?: ContentEntryStatus;
93
- /** Field to sort by. Defaults to `"updatedAt"`. */
94
- sort?: ContentEntrySortField;
95
- /** Sort direction. Defaults to `"desc"`. */
96
- order?: "asc" | "desc";
97
- /** Page number (1-based). */
98
- page?: number;
99
- /** Max entries per page (1-100). Defaults to 50. */
100
- limit?: number;
101
- }
102
-
103
- /** Options for fetching a single content entry. */
104
- export interface GetEntryOptions {
105
- /** Language code for localized content. Defaults to source language. */
106
- language?: string;
107
- }
108
-
109
- /** Client for fetching content entries and models. */
110
- export interface ContentClient {
111
- /** List all content models in the project. */
112
- getModels(): Promise<ContentModel[]>;
113
- /** List entries for a content model with pagination. */
114
- getEntries(
115
- modelSlug: string,
116
- options?: ListEntriesOptions,
117
- ): Promise<PaginatedResponse<ContentEntryListItem>>;
118
- /** Fetch a single content entry by slug. */
119
- getEntry<CF extends Record<string, string | null> = Record<string, string | null>>(
120
- modelSlug: string,
121
- entrySlug: string,
122
- options?: GetEntryOptions,
123
- ): Promise<ContentEntry<CF>>;
124
- }