@mybe/sdk 1.0.2 → 1.0.5

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,366 +1,546 @@
1
- # Mybe CMS SDK
2
-
3
- A TypeScript SDK for fetching content from Mybe CMS.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @mybe/sdk
9
- ```
10
-
11
- ## Features
12
-
13
- - ✅ **Zero Dependencies** - Uses native fetch API
14
- - ✅ **TypeScript Support** - Full type safety with TypeScript
15
- - ✅ **Auto Environment Detection** - Automatically detects development vs production
16
- - ✅ **Error Handling** - Custom error classes for better error handling
17
- - ✅ **Pagination Support** - Built-in pagination for large datasets
18
- - ✅ **Status Filtering** - Filter content by status (draft, published, archived)
19
- - ✅ **Locale Filtering** - Filter content by locale for multi-language support
20
-
21
- ## Quick Start
22
-
23
- ```typescript
24
- import { MybeSDK } from '@mybe/sdk';
25
-
26
- // Initialize the SDK
27
- const sdk = new MybeSDK({
28
- apiKey: 'your-api-key'
29
- });
30
-
31
- // Fetch a single content entry
32
- const content = await sdk.getContent('content-id');
33
-
34
- // Fetch content by type with filters
35
- const contentList = await sdk.getContentByType('content-type-id', {
36
- status: 'published',
37
- limit: 10
38
- });
39
- ```
40
-
41
- ## Configuration
42
-
43
- ### Basic Configuration
44
-
45
- ```typescript
46
- const sdk = new MybeSDK({
47
- apiKey: 'your-api-key'
48
- });
49
- ```
50
-
51
- The SDK automatically detects the environment:
52
- - **Development** (`NODE_ENV=development`): Uses `http://localhost:3001/api/v1`
53
- - **Production**: Uses `https://fizsdck7l0.execute-api.us-east-1.amazonaws.com/dev/api/v1`
54
-
55
- > **Note:** The production endpoint uses AWS API Gateway REST API with API key validation and automatic usage tracking.
56
-
57
- ### Custom Base URL (Optional)
58
-
59
- ```typescript
60
- const sdk = new MybeSDK({
61
- apiKey: 'your-api-key',
62
- baseUrl: 'https://custom-api.example.com/api/v1'
63
- });
64
- ```
65
-
66
- ## API Reference
67
-
68
- ### Content Models (Content Types)
69
-
70
- #### `getContentModels(projectId: string)`
71
-
72
- Get all content models for a project.
73
-
74
- ```typescript
75
- const contentModels = await sdk.getContentModels('project-id');
76
- ```
77
-
78
- **Returns:** `Promise<ContentType[]>`
79
-
80
- #### `getContentModel(contentTypeId: string)`
81
-
82
- Get a specific content model by ID.
83
-
84
- ```typescript
85
- const contentModel = await sdk.getContentModel('content-type-id');
86
- ```
87
-
88
- **Returns:** `Promise<ContentType>`
89
-
90
- ### Content Entries
91
-
92
- #### `getContent(contentId: string)`
93
-
94
- Get a single content entry by ID.
95
-
96
- ```typescript
97
- const content = await sdk.getContent('content-id');
98
- ```
99
-
100
- **Returns:** `Promise<ContentEntry>`
101
-
102
- #### `getContentByType(contentTypeId: string, options?: ContentFilterOptions)`
103
-
104
- Get all content entries for a specific content type.
105
-
106
- ```typescript
107
- const result = await sdk.getContentByType('content-type-id', {
108
- status: 'published',
109
- limit: 20,
110
- lastKey: 'pagination-key' // For pagination
111
- });
112
-
113
- console.log(result.data); // Array of content entries
114
- console.log(result.pagination.hasMore); // Boolean
115
- console.log(result.pagination.lastEvaluatedKey); // For next page
116
- ```
117
-
118
- **Options:**
119
- - `status?: 'draft' | 'published' | 'archived'` - Filter by status
120
- - `locale?: string` - Filter by locale (e.g., 'en-US', 'bn-BD', 'fr-FR', 'es-ES')
121
- - `limit?: number` - Number of items per page
122
- - `lastKey?: string` - Pagination key from previous response
123
-
124
- **Returns:** `Promise<ContentListResponse>`
125
-
126
- #### `getContentByProject(projectId: string, options?: ContentFilterOptions)`
127
-
128
- Get all content entries for a project.
129
-
130
- ```typescript
131
- const result = await sdk.getContentByProject('project-id', {
132
- status: 'published',
133
- limit: 20
134
- });
135
- ```
136
-
137
- **Options:**
138
- - `status?: 'draft' | 'published' | 'archived'` - Filter by status
139
- - `locale?: string` - Filter by locale (e.g., 'en-US', 'bn-BD', 'fr-FR', 'es-ES')
140
- - `limit?: number` - Number of items per page
141
- - `lastKey?: string` - Pagination key from previous response
142
-
143
- **Returns:** `Promise<ContentListResponse>`
144
-
145
- ## Pagination Example
146
-
147
- ```typescript
148
- let lastKey: string | undefined;
149
- let allContent: ContentEntry[] = [];
150
-
151
- do {
152
- const result = await sdk.getContentByType('content-type-id', {
153
- status: 'published',
154
- limit: 50,
155
- lastKey: lastKey ? JSON.stringify(result.pagination.lastEvaluatedKey) : undefined
156
- });
157
-
158
- allContent = [...allContent, ...result.data];
159
- lastKey = result.pagination.hasMore
160
- ? JSON.stringify(result.pagination.lastEvaluatedKey)
161
- : undefined;
162
-
163
- } while (lastKey);
164
-
165
- console.log(`Fetched ${allContent.length} total items`);
166
- ```
167
-
168
- ## Error Handling
169
-
170
- The SDK provides custom error classes for better error handling:
171
-
172
- ```typescript
173
- import {
174
- MybeSDK,
175
- NotFoundError,
176
- UnauthorizedError,
177
- ValidationError,
178
- ServerError
179
- } from '@mybe/sdk';
180
-
181
- try {
182
- const content = await sdk.getContent('content-id');
183
- } catch (error) {
184
- if (error instanceof NotFoundError) {
185
- console.error('Content not found');
186
- } else if (error instanceof UnauthorizedError) {
187
- console.error('Invalid API key');
188
- } else if (error instanceof ValidationError) {
189
- console.error('Validation error:', error.message);
190
- } else if (error instanceof ServerError) {
191
- console.error('Server error:', error.message);
192
- } else {
193
- console.error('Unknown error:', error);
194
- }
195
- }
196
- ```
197
-
198
- ### Error Types
199
-
200
- - `MybeSDKError` - Base error class
201
- - `NotFoundError` - Resource not found (404)
202
- - `UnauthorizedError` - Invalid API key (401)
203
- - `ForbiddenError` - Insufficient permissions (403)
204
- - `ValidationError` - Validation failed (400)
205
- - `ServerError` - Internal server error (500)
206
-
207
- ## TypeScript Types
208
-
209
- The SDK exports all TypeScript types for your convenience:
210
-
211
- ```typescript
212
- import type {
213
- ContentType,
214
- ContentEntry,
215
- ContentFilterOptions,
216
- PaginationResponse,
217
- APIResponse,
218
- ContentListResponse
219
- } from '@mybe/sdk';
220
- ```
221
-
222
- ### ContentType
223
-
224
- ```typescript
225
- interface ContentType {
226
- id: string;
227
- project_id: string;
228
- name: string;
229
- slug: string;
230
- description?: string;
231
- created_at: string;
232
- updated_at: string;
233
- }
234
- ```
235
-
236
- ### ContentEntry
237
-
238
- ```typescript
239
- interface ContentEntry {
240
- id: string;
241
- content_type_id: string;
242
- project_id: string;
243
- slug?: string;
244
- status: 'draft' | 'published' | 'archived';
245
- data: Record<string, any>;
246
- locale?: string;
247
- created_by: string;
248
- updated_by?: string;
249
- published_at?: string;
250
- created_at: string;
251
- updated_at: string;
252
- }
253
- ```
254
-
255
- ## Examples
256
-
257
- ### Fetch Published Blog Posts
258
-
259
- ```typescript
260
- const sdk = new MybeSDK({ apiKey: 'your-api-key' });
261
-
262
- const posts = await sdk.getContentByType('blog-post-type-id', {
263
- status: 'published',
264
- limit: 10
265
- });
266
-
267
- posts.data.forEach(post => {
268
- console.log(post.data.title);
269
- console.log(post.data.content);
270
- });
271
- ```
272
-
273
- ### Fetch All Content Models
274
-
275
- ```typescript
276
- const contentModels = await sdk.getContentModels('project-id');
277
-
278
- contentModels.forEach(model => {
279
- console.log(`${model.name} (${model.slug})`);
280
- });
281
- ```
282
-
283
- ### Fetch Single Content Entry
284
-
285
- ```typescript
286
- const content = await sdk.getContent('content-entry-id');
287
-
288
- console.log(content.data); // Your content data
289
- console.log(content.status); // draft | published | archived
290
- ```
291
-
292
- ### Multi-locale Content
293
-
294
- Fetch content in different languages using the locale filter:
295
-
296
- ```typescript
297
- const sdk = new MybeSDK({ apiKey: 'your-api-key' });
298
-
299
- // Fetch English content
300
- const englishPosts = await sdk.getContentByType('blog-post-type-id', {
301
- status: 'published',
302
- locale: 'en-US',
303
- limit: 10
304
- });
305
-
306
- // Fetch Bangla content
307
- const banglaPosts = await sdk.getContentByType('blog-post-type-id', {
308
- status: 'published',
309
- locale: 'bn-BD',
310
- limit: 10
311
- });
312
-
313
- // Fetch French content
314
- const frenchPosts = await sdk.getContentByType('blog-post-type-id', {
315
- status: 'published',
316
- locale: 'fr-FR',
317
- limit: 10
318
- });
319
-
320
- // Fetch Spanish content
321
- const spanishPosts = await sdk.getContentByType('blog-post-type-id', {
322
- status: 'published',
323
- locale: 'es-ES',
324
- limit: 10
325
- });
326
- ```
327
-
328
- **Building a Multi-language Website:**
329
-
330
- ```typescript
331
- // Get user's preferred language
332
- const userLocale = getUserPreferredLocale(); // e.g., 'bn-BD'
333
-
334
- // Fetch content in user's language
335
- const localizedContent = await sdk.getContentByType('content-type-id', {
336
- status: 'published',
337
- locale: userLocale,
338
- limit: 20
339
- });
340
-
341
- // Display localized content
342
- localizedContent.data.forEach(item => {
343
- console.log(item.data.title); // Title in user's language
344
- console.log(item.metadata.locale); // e.g., 'bn-BD'
345
- });
346
- ```
347
-
348
- **Supported Locales:**
349
- - `en-US` - English (United States)
350
- - `bn-BD` - Bangla (Bangladesh)
351
- - `fr-FR` - French (France)
352
- - `es-ES` - Spanish (Spain)
353
- - Custom locales as configured in your CMS
354
-
355
- ## Requirements
356
-
357
- - Node.js 18+ (for native fetch support)
358
- - TypeScript 5.0+ (for development)
359
-
360
- ## License
361
-
362
- MIT
363
-
364
- ## Support
365
-
366
- For issues and questions, please visit the [GitHub repository](https://github.com/MyBeeInovationLabs/mybe-cms).
1
+ # Mybe CMS SDK
2
+
3
+ A TypeScript SDK for fetching content from Mybe CMS.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @mybe/sdk
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - ✅ **Zero Dependencies** - Uses native fetch API
14
+ - ✅ **TypeScript Support** - Full type safety with TypeScript
15
+ - ✅ **Auto Environment Detection** - Automatically detects development vs production
16
+ - ✅ **Error Handling** - Custom error classes for better error handling
17
+ - ✅ **Pagination Support** - Built-in pagination for large datasets
18
+ - ✅ **Status Filtering** - Filter content by status (draft, published, archived)
19
+ - ✅ **Locale Filtering** - Filter content by locale for multi-language support
20
+ - ✅ **GraphQL Support** - Powerful GraphQL API for flexible data querying
21
+
22
+ ## Quick Start
23
+
24
+ ```typescript
25
+ import { MybeSDK } from '@mybe/sdk';
26
+
27
+ // Initialize the SDK
28
+ const sdk = new MybeSDK({
29
+ apiKey: 'your-api-key'
30
+ });
31
+
32
+ // Fetch a single content entry
33
+ const content = await sdk.getContent('content-id');
34
+
35
+ // Fetch content by type with filters
36
+ const contentList = await sdk.getContentByType('content-type-id', {
37
+ status: 'published',
38
+ limit: 10
39
+ });
40
+ ```
41
+
42
+ ## Configuration
43
+
44
+ ### Basic Configuration
45
+
46
+ ```typescript
47
+ const sdk = new MybeSDK({
48
+ apiKey: 'your-api-key'
49
+ });
50
+ ```
51
+
52
+ The SDK automatically detects the environment:
53
+ - **Development** (`NODE_ENV=development`): Uses `https://sdk-dev.contensa.ai/dev/api/v1`
54
+ - **Production**: Uses `https://sdk-dev.contensa.ai/dev/api/v1`
55
+
56
+ > **Note:** The SDK uses a custom domain for all environments, providing a professional branded URL while hiding the underlying AWS infrastructure. Both development and production currently point to the same endpoint temporarily.
57
+
58
+ ### Custom Base URL (Optional)
59
+
60
+ ```typescript
61
+ const sdk = new MybeSDK({
62
+ apiKey: 'your-api-key',
63
+ baseUrl: 'https://custom-api.example.com/api/v1'
64
+ });
65
+ ```
66
+
67
+ ## API Reference
68
+
69
+ ### Content Models (Content Types)
70
+
71
+ #### `getContentModels(projectId: string)`
72
+
73
+ Get all content models for a project.
74
+
75
+ ```typescript
76
+ const contentModels = await sdk.getContentModels('project-id');
77
+ ```
78
+
79
+ **Returns:** `Promise<ContentType[]>`
80
+
81
+ #### `getContentModel(contentTypeId: string)`
82
+
83
+ Get a specific content model by ID.
84
+
85
+ ```typescript
86
+ const contentModel = await sdk.getContentModel('content-type-id');
87
+ ```
88
+
89
+ **Returns:** `Promise<ContentType>`
90
+
91
+ ### Content Entries
92
+
93
+ #### `getContent(contentId: string)`
94
+
95
+ Get a single content entry by ID.
96
+
97
+ ```typescript
98
+ const content = await sdk.getContent('content-id');
99
+ ```
100
+
101
+ **Returns:** `Promise<ContentEntry>`
102
+
103
+ #### `getContentByType(contentTypeId: string, options?: ContentFilterOptions)`
104
+
105
+ Get all content entries for a specific content type.
106
+
107
+ ```typescript
108
+ const result = await sdk.getContentByType('content-type-id', {
109
+ status: 'published',
110
+ limit: 20,
111
+ lastKey: 'pagination-key' // For pagination
112
+ });
113
+
114
+ console.log(result.data); // Array of content entries
115
+ console.log(result.pagination.hasMore); // Boolean
116
+ console.log(result.pagination.lastEvaluatedKey); // For next page
117
+ ```
118
+
119
+ **Options:**
120
+ - `status?: 'draft' | 'published' | 'archived'` - Filter by status
121
+ - `locale?: string` - Filter by locale (e.g., 'en-US', 'bn-BD', 'fr-FR', 'es-ES')
122
+ - `limit?: number` - Number of items per page
123
+ - `lastKey?: string` - Pagination key from previous response
124
+
125
+ **Returns:** `Promise<ContentListResponse>`
126
+
127
+ #### `getContentByProject(projectId: string, options?: ContentFilterOptions)`
128
+
129
+ Get all content entries for a project.
130
+
131
+ ```typescript
132
+ const result = await sdk.getContentByProject('project-id', {
133
+ status: 'published',
134
+ limit: 20
135
+ });
136
+ ```
137
+
138
+ **Options:**
139
+ - `status?: 'draft' | 'published' | 'archived'` - Filter by status
140
+ - `locale?: string` - Filter by locale (e.g., 'en-US', 'bn-BD', 'fr-FR', 'es-ES')
141
+ - `limit?: number` - Number of items per page
142
+ - `lastKey?: string` - Pagination key from previous response
143
+
144
+ **Returns:** `Promise<ContentListResponse>`
145
+
146
+ ## Pagination Example
147
+
148
+ ```typescript
149
+ let lastKey: string | undefined;
150
+ let allContent: ContentEntry[] = [];
151
+
152
+ do {
153
+ const result = await sdk.getContentByType('content-type-id', {
154
+ status: 'published',
155
+ limit: 50,
156
+ lastKey: lastKey ? JSON.stringify(result.pagination.lastEvaluatedKey) : undefined
157
+ });
158
+
159
+ allContent = [...allContent, ...result.data];
160
+ lastKey = result.pagination.hasMore
161
+ ? JSON.stringify(result.pagination.lastEvaluatedKey)
162
+ : undefined;
163
+
164
+ } while (lastKey);
165
+
166
+ console.log(`Fetched ${allContent.length} total items`);
167
+ ```
168
+
169
+ ## Error Handling
170
+
171
+ The SDK provides custom error classes for better error handling:
172
+
173
+ ```typescript
174
+ import {
175
+ MybeSDK,
176
+ NotFoundError,
177
+ UnauthorizedError,
178
+ ValidationError,
179
+ ServerError
180
+ } from '@mybe/sdk';
181
+
182
+ try {
183
+ const content = await sdk.getContent('content-id');
184
+ } catch (error) {
185
+ if (error instanceof NotFoundError) {
186
+ console.error('Content not found');
187
+ } else if (error instanceof UnauthorizedError) {
188
+ console.error('Invalid API key');
189
+ } else if (error instanceof ValidationError) {
190
+ console.error('Validation error:', error.message);
191
+ } else if (error instanceof ServerError) {
192
+ console.error('Server error:', error.message);
193
+ } else {
194
+ console.error('Unknown error:', error);
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### Error Types
200
+
201
+ - `MybeSDKError` - Base error class
202
+ - `NotFoundError` - Resource not found (404)
203
+ - `UnauthorizedError` - Invalid API key (401)
204
+ - `ForbiddenError` - Insufficient permissions (403)
205
+ - `ValidationError` - Validation failed (400)
206
+ - `ServerError` - Internal server error (500)
207
+
208
+ ## TypeScript Types
209
+
210
+ The SDK exports all TypeScript types for your convenience:
211
+
212
+ ```typescript
213
+ import type {
214
+ ContentType,
215
+ ContentEntry,
216
+ ContentFilterOptions,
217
+ PaginationResponse,
218
+ APIResponse,
219
+ ContentListResponse
220
+ } from '@mybe/sdk';
221
+ ```
222
+
223
+ ### ContentType
224
+
225
+ ```typescript
226
+ interface ContentType {
227
+ id: string;
228
+ project_id: string;
229
+ name: string;
230
+ slug: string;
231
+ description?: string;
232
+ created_at: string;
233
+ updated_at: string;
234
+ }
235
+ ```
236
+
237
+ ### ContentEntry
238
+
239
+ ```typescript
240
+ interface ContentEntry {
241
+ id: string;
242
+ content_type_id: string;
243
+ project_id: string;
244
+ slug?: string;
245
+ status: 'draft' | 'published' | 'archived';
246
+ data: Record<string, any>;
247
+ locale?: string;
248
+ created_by: string;
249
+ updated_by?: string;
250
+ published_at?: string;
251
+ created_at: string;
252
+ updated_at: string;
253
+ }
254
+ ```
255
+
256
+ ## Examples
257
+
258
+ ### Fetch Published Blog Posts
259
+
260
+ ```typescript
261
+ const sdk = new MybeSDK({ apiKey: 'your-api-key' });
262
+
263
+ const posts = await sdk.getContentByType('blog-post-type-id', {
264
+ status: 'published',
265
+ limit: 10
266
+ });
267
+
268
+ posts.data.forEach(post => {
269
+ console.log(post.data.title);
270
+ console.log(post.data.content);
271
+ });
272
+ ```
273
+
274
+ ### Fetch All Content Models
275
+
276
+ ```typescript
277
+ const contentModels = await sdk.getContentModels('project-id');
278
+
279
+ contentModels.forEach(model => {
280
+ console.log(`${model.name} (${model.slug})`);
281
+ });
282
+ ```
283
+
284
+ ### Fetch Single Content Entry
285
+
286
+ ```typescript
287
+ const content = await sdk.getContent('content-entry-id');
288
+
289
+ console.log(content.data); // Your content data
290
+ console.log(content.status); // draft | published | archived
291
+ ```
292
+
293
+ ### Multi-locale Content
294
+
295
+ Fetch content in different languages using the locale filter:
296
+
297
+ ```typescript
298
+ const sdk = new MybeSDK({ apiKey: 'your-api-key' });
299
+
300
+ // Fetch English content
301
+ const englishPosts = await sdk.getContentByType('blog-post-type-id', {
302
+ status: 'published',
303
+ locale: 'en-US',
304
+ limit: 10
305
+ });
306
+
307
+ // Fetch Bangla content
308
+ const banglaPosts = await sdk.getContentByType('blog-post-type-id', {
309
+ status: 'published',
310
+ locale: 'bn-BD',
311
+ limit: 10
312
+ });
313
+
314
+ // Fetch French content
315
+ const frenchPosts = await sdk.getContentByType('blog-post-type-id', {
316
+ status: 'published',
317
+ locale: 'fr-FR',
318
+ limit: 10
319
+ });
320
+
321
+ // Fetch Spanish content
322
+ const spanishPosts = await sdk.getContentByType('blog-post-type-id', {
323
+ status: 'published',
324
+ locale: 'es-ES',
325
+ limit: 10
326
+ });
327
+ ```
328
+
329
+ **Building a Multi-language Website:**
330
+
331
+ ```typescript
332
+ // Get user's preferred language
333
+ const userLocale = getUserPreferredLocale(); // e.g., 'bn-BD'
334
+
335
+ // Fetch content in user's language
336
+ const localizedContent = await sdk.getContentByType('content-type-id', {
337
+ status: 'published',
338
+ locale: userLocale,
339
+ limit: 20
340
+ });
341
+
342
+ // Display localized content
343
+ localizedContent.data.forEach(item => {
344
+ console.log(item.data.title); // Title in user's language
345
+ console.log(item.metadata.locale); // e.g., 'bn-BD'
346
+ });
347
+ ```
348
+
349
+ **Supported Locales:**
350
+ - `en-US` - English (United States)
351
+ - `bn-BD` - Bangla (Bangladesh)
352
+ - `fr-FR` - French (France)
353
+ - `es-ES` - Spanish (Spain)
354
+ - Custom locales as configured in your CMS
355
+
356
+ ## GraphQL API
357
+
358
+ Mybe CMS provides a powerful GraphQL API for flexible data querying. Use GraphQL when you need advanced filtering, want to request specific fields only, or prefer the GraphQL query language.
359
+
360
+ ### Quick Start with GraphQL
361
+
362
+ ```typescript
363
+ import { MybeSDK } from '@mybe/sdk';
364
+
365
+ const sdk = new MybeSDK({ apiKey: 'your-api-key' });
366
+ const PROJECT_ID = 'your-project-id';
367
+
368
+ // Execute a GraphQL query
369
+ const result = await sdk.graphql(PROJECT_ID, `
370
+ query {
371
+ blogPostCollection(limit: 10, where: { status: "published" }) {
372
+ items {
373
+ id
374
+ slug
375
+ data
376
+ }
377
+ total
378
+ }
379
+ }
380
+ `);
381
+
382
+ console.log(result.blogPostCollection.items);
383
+ ```
384
+
385
+ ### GraphQL Method
386
+
387
+ #### `graphql<T>(projectId: string, query: string, variables?: Record<string, any>)`
388
+
389
+ Execute a GraphQL query against your project.
390
+
391
+ **Parameters:**
392
+ - `projectId` - Your project ID
393
+ - `query` - GraphQL query string
394
+ - `variables` - Optional query variables (recommended for dynamic queries)
395
+
396
+ **Returns:** `Promise<T>` - The data from your GraphQL query
397
+
398
+ ### GraphQL Examples
399
+
400
+ #### Basic Collection Query
401
+
402
+ ```typescript
403
+ const { blogPostCollection } = await sdk.graphql(PROJECT_ID, `
404
+ query {
405
+ blogPostCollection(limit: 5) {
406
+ items {
407
+ id
408
+ slug
409
+ status
410
+ data
411
+ published_at
412
+ }
413
+ total
414
+ }
415
+ }
416
+ `);
417
+
418
+ console.log(`Total posts: ${blogPostCollection.total}`);
419
+ blogPostCollection.items.forEach(post => {
420
+ console.log(post.data.title);
421
+ });
422
+ ```
423
+
424
+ #### Filter by Status
425
+
426
+ ```typescript
427
+ const { blogPostCollection } = await sdk.graphql(PROJECT_ID, `
428
+ query {
429
+ blogPostCollection(where: { status: "published" }) {
430
+ items {
431
+ id
432
+ slug
433
+ data
434
+ }
435
+ }
436
+ }
437
+ `);
438
+ ```
439
+
440
+ #### Get Single Entry by ID
441
+
442
+ ```typescript
443
+ const { blogPost } = await sdk.graphql(PROJECT_ID, `
444
+ query {
445
+ blogPost(id: "entry-123") {
446
+ id
447
+ slug
448
+ status
449
+ data
450
+ created_at
451
+ published_at
452
+ }
453
+ }
454
+ `);
455
+ ```
456
+
457
+ #### Using Variables (Recommended)
458
+
459
+ ```typescript
460
+ const result = await sdk.graphql(
461
+ PROJECT_ID,
462
+ `
463
+ query GetPostBySlug($slug: String!) {
464
+ blogPostCollection(where: { slug: $slug }) {
465
+ items {
466
+ id
467
+ slug
468
+ data
469
+ }
470
+ }
471
+ }
472
+ `,
473
+ { slug: 'my-blog-post' }
474
+ );
475
+
476
+ const post = result.blogPostCollection.items[0];
477
+ ```
478
+
479
+ #### Pagination with GraphQL
480
+
481
+ ```typescript
482
+ // Page 1
483
+ const page1 = await sdk.graphql(PROJECT_ID, `
484
+ query {
485
+ blogPostCollection(limit: 10, skip: 0) {
486
+ items { id slug }
487
+ total
488
+ }
489
+ }
490
+ `);
491
+
492
+ // Page 2
493
+ const page2 = await sdk.graphql(PROJECT_ID, `
494
+ query {
495
+ blogPostCollection(limit: 10, skip: 10) {
496
+ items { id slug }
497
+ total
498
+ }
499
+ }
500
+ `);
501
+ ```
502
+
503
+ ### REST vs GraphQL - When to Use
504
+
505
+ **Use REST When:**
506
+ - ✅ Simple queries (get by ID, get all)
507
+ - ✅ You want straightforward API calls
508
+ - ✅ You prefer SDK helper methods
509
+ - ✅ You need full object responses
510
+
511
+ **Use GraphQL When:**
512
+ - ✅ Complex filtering requirements
513
+ - ✅ You need specific fields only (reduce payload size)
514
+ - ✅ Multiple queries in one request
515
+ - ✅ You want flexible, ad-hoc queries
516
+ - ✅ Your frontend uses Apollo Client or similar
517
+
518
+ ### Complete GraphQL Guide
519
+
520
+ For comprehensive GraphQL documentation, examples, and best practices, see:
521
+ - **[GraphQL API Guide](./GRAPHQL_GUIDE.md)** - Complete GraphQL documentation
522
+ - **[Test Examples](./test-graphql.ts)** - Working code examples
523
+
524
+ ### GraphQL Schema
525
+
526
+ Your GraphQL schema is automatically generated from your content types. For example:
527
+
528
+ **Content Type:** "Blog Post"
529
+ **Generates:**
530
+ - `blogPost(id: ID!)` - Get single entry
531
+ - `blogPostCollection(limit, skip, where)` - Get collection
532
+
533
+ **Naming:** Content type names are converted to camelCase (e.g., "Blog Post" → `blogPost`)
534
+
535
+ ## Requirements
536
+
537
+ - Node.js 18+ (for native fetch support)
538
+ - TypeScript 5.0+ (for development)
539
+
540
+ ## License
541
+
542
+ MIT
543
+
544
+ ## Support
545
+
546
+ For issues and questions, please visit the [GitHub repository](https://github.com/MyBeeInovationLabs/mybe-cms).
package/dist/client.d.ts CHANGED
@@ -7,6 +7,32 @@ export declare class MybeSDK {
7
7
  * Make HTTP request to API
8
8
  */
9
9
  private request;
10
+ /**
11
+ * Convert kebab-case to camelCase for GraphQL field names
12
+ * Examples:
13
+ * "blog-post" → "blogPost"
14
+ * "case-study" → "caseStudy"
15
+ * "product" → "product" (unchanged)
16
+ */
17
+ private toCamelCase;
18
+ /**
19
+ * Automatically convert kebab-case content type slugs to camelCase in GraphQL queries
20
+ * This allows users to use their actual content type slugs (e.g., "blog-post")
21
+ * without needing to know about GraphQL's camelCase requirement
22
+ *
23
+ * Converts patterns like:
24
+ * "blog-post" → "blogPost"
25
+ * "blog-postCollection" → "blogPostCollection"
26
+ * "case-study(" → "caseStudy("
27
+ *
28
+ * @returns Object with converted query and mapping of original to converted field names
29
+ */
30
+ private convertQuerySlugs;
31
+ /**
32
+ * Convert response keys back to the original format used in the query
33
+ * This allows users to access response fields using the same kebab-case format they used in the query
34
+ */
35
+ private convertResponseKeys;
10
36
  /**
11
37
  * Build query string from options
12
38
  */
@@ -31,4 +57,65 @@ export declare class MybeSDK {
31
57
  * Get all content entries for a project
32
58
  */
33
59
  getContentByProject(projectId: string, options?: ContentFilterOptions): Promise<ContentListResponse>;
60
+ /**
61
+ * Get a single content entry by field value
62
+ * Similar to Contentful's getEntries with field filters
63
+ * @example
64
+ * // Get service by href field
65
+ * const service = await sdk.getContentByField('service-type-id', 'href', '/services/web-dev');
66
+ *
67
+ * // Get blog post by slug field
68
+ * const post = await sdk.getContentByField('blog-type-id', 'slug', 'my-first-post');
69
+ */
70
+ getContentByField(contentTypeId: string, fieldName: string, fieldValue: string): Promise<ContentEntry | null>;
71
+ /**
72
+ * Execute a GraphQL query against your project
73
+ *
74
+ * Provides flexible, powerful querying capabilities for your content.
75
+ * GraphQL allows you to request exactly the data you need in a single request.
76
+ *
77
+ * @param projectId - Your project ID
78
+ * @param query - GraphQL query string
79
+ * @param variables - Optional query variables for parameterized queries
80
+ *
81
+ * @example
82
+ * // Get published blog posts
83
+ * const result = await sdk.graphql('project-id', `
84
+ * query {
85
+ * blogPostCollection(limit: 10, where: { status: "published" }) {
86
+ * items {
87
+ * id
88
+ * slug
89
+ * data
90
+ * }
91
+ * total
92
+ * }
93
+ * }
94
+ * `);
95
+ *
96
+ * @example
97
+ * // Get a single entry by ID
98
+ * const result = await sdk.graphql('project-id', `
99
+ * query {
100
+ * blogPost(id: "entry-123") {
101
+ * id
102
+ * slug
103
+ * status
104
+ * data
105
+ * published_at
106
+ * }
107
+ * }
108
+ * `);
109
+ *
110
+ * @example
111
+ * // Using variables for safer queries
112
+ * const result = await sdk.graphql('project-id', `
113
+ * query GetPost($slug: String!) {
114
+ * blogPostCollection(where: { slug: $slug }) {
115
+ * items { id data }
116
+ * }
117
+ * }
118
+ * `, { slug: 'my-post' });
119
+ */
120
+ graphql<T = any>(projectId: string, query: string, variables?: Record<string, any>): Promise<T>;
34
121
  }
package/dist/client.js CHANGED
@@ -13,10 +13,12 @@ export class MybeSDK {
13
13
  else {
14
14
  const isDevelopment = typeof process !== 'undefined' &&
15
15
  process.env.NODE_ENV === 'development';
16
- // Use REST API endpoint for production (with API key validation and usage tracking)
16
+ // Use custom domain for SDK endpoints
17
+ // TODO: Update production URL when production custom domain is ready
18
+ // Production: https://fizsdck7l0.execute-api.us-east-1.amazonaws.com/dev/api/v1
17
19
  this.baseUrl = isDevelopment
18
- ? 'http://localhost:3001/api/v1'
19
- : 'https://fizsdck7l0.execute-api.us-east-1.amazonaws.com/dev/api/v1';
20
+ ? 'https://sdk-dev.contensa.ai/dev/api/v1'
21
+ : 'https://sdk-dev.contensa.ai/dev/api/v1'; // Using dev URL for both temporarily
20
22
  }
21
23
  }
22
24
  /**
@@ -69,6 +71,65 @@ export class MybeSDK {
69
71
  throw new MybeSDKError('An unknown error occurred');
70
72
  }
71
73
  }
74
+ /**
75
+ * Convert kebab-case to camelCase for GraphQL field names
76
+ * Examples:
77
+ * "blog-post" → "blogPost"
78
+ * "case-study" → "caseStudy"
79
+ * "product" → "product" (unchanged)
80
+ */
81
+ toCamelCase(str) {
82
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
83
+ }
84
+ /**
85
+ * Automatically convert kebab-case content type slugs to camelCase in GraphQL queries
86
+ * This allows users to use their actual content type slugs (e.g., "blog-post")
87
+ * without needing to know about GraphQL's camelCase requirement
88
+ *
89
+ * Converts patterns like:
90
+ * "blog-post" → "blogPost"
91
+ * "blog-postCollection" → "blogPostCollection"
92
+ * "case-study(" → "caseStudy("
93
+ *
94
+ * @returns Object with converted query and mapping of original to converted field names
95
+ */
96
+ convertQuerySlugs(query) {
97
+ const fieldMap = new Map();
98
+ // Match kebab-case slugs followed by Collection, (, {, or whitespace
99
+ const convertedQuery = query.replace(/([a-z]+(?:-[a-z]+)+)(Collection|\(|{|\s)/gi, (match, slug, suffix) => {
100
+ const camelCaseSlug = this.toCamelCase(slug);
101
+ const originalField = slug + suffix.replace(/[\(\{\s]/, ''); // Extract just 'Collection' if present
102
+ const convertedField = camelCaseSlug + suffix.replace(/[\(\{\s]/, '');
103
+ // Store mapping if it's a field name (with Collection or standalone)
104
+ if (suffix === 'Collection' || suffix === '(' || suffix === ' ' || suffix === '{') {
105
+ // Map both with and without Collection
106
+ fieldMap.set(camelCaseSlug, slug);
107
+ fieldMap.set(camelCaseSlug + 'Collection', slug + 'Collection');
108
+ fieldMap.set(camelCaseSlug + 'Collection', slug + 'Collection');
109
+ }
110
+ return camelCaseSlug + suffix;
111
+ });
112
+ return { query: convertedQuery, fieldMap };
113
+ }
114
+ /**
115
+ * Convert response keys back to the original format used in the query
116
+ * This allows users to access response fields using the same kebab-case format they used in the query
117
+ */
118
+ convertResponseKeys(data, fieldMap) {
119
+ if (!data || typeof data !== 'object') {
120
+ return data;
121
+ }
122
+ if (Array.isArray(data)) {
123
+ return data.map(item => this.convertResponseKeys(item, fieldMap));
124
+ }
125
+ const converted = {};
126
+ for (const [key, value] of Object.entries(data)) {
127
+ // Check if this key should be converted back
128
+ const originalKey = fieldMap.get(key) || key;
129
+ converted[originalKey] = this.convertResponseKeys(value, fieldMap);
130
+ }
131
+ return converted;
132
+ }
72
133
  /**
73
134
  * Build query string from options
74
135
  */
@@ -126,4 +187,109 @@ export class MybeSDK {
126
187
  const queryString = this.buildQueryString(options);
127
188
  return await this.request(`/content-entry/project/${projectId}${queryString}`);
128
189
  }
190
+ /**
191
+ * Get a single content entry by field value
192
+ * Similar to Contentful's getEntries with field filters
193
+ * @example
194
+ * // Get service by href field
195
+ * const service = await sdk.getContentByField('service-type-id', 'href', '/services/web-dev');
196
+ *
197
+ * // Get blog post by slug field
198
+ * const post = await sdk.getContentByField('blog-type-id', 'slug', 'my-first-post');
199
+ */
200
+ async getContentByField(contentTypeId, fieldName, fieldValue) {
201
+ try {
202
+ const encodedValue = encodeURIComponent(fieldValue);
203
+ const response = await this.request(`/type/${contentTypeId}/field/${fieldName}/${encodedValue}`);
204
+ return response.data;
205
+ }
206
+ catch (error) {
207
+ // If it's a 404, return null instead of throwing
208
+ if (error instanceof NotFoundError) {
209
+ return null;
210
+ }
211
+ throw error;
212
+ }
213
+ }
214
+ /**
215
+ * Execute a GraphQL query against your project
216
+ *
217
+ * Provides flexible, powerful querying capabilities for your content.
218
+ * GraphQL allows you to request exactly the data you need in a single request.
219
+ *
220
+ * @param projectId - Your project ID
221
+ * @param query - GraphQL query string
222
+ * @param variables - Optional query variables for parameterized queries
223
+ *
224
+ * @example
225
+ * // Get published blog posts
226
+ * const result = await sdk.graphql('project-id', `
227
+ * query {
228
+ * blogPostCollection(limit: 10, where: { status: "published" }) {
229
+ * items {
230
+ * id
231
+ * slug
232
+ * data
233
+ * }
234
+ * total
235
+ * }
236
+ * }
237
+ * `);
238
+ *
239
+ * @example
240
+ * // Get a single entry by ID
241
+ * const result = await sdk.graphql('project-id', `
242
+ * query {
243
+ * blogPost(id: "entry-123") {
244
+ * id
245
+ * slug
246
+ * status
247
+ * data
248
+ * published_at
249
+ * }
250
+ * }
251
+ * `);
252
+ *
253
+ * @example
254
+ * // Using variables for safer queries
255
+ * const result = await sdk.graphql('project-id', `
256
+ * query GetPost($slug: String!) {
257
+ * blogPostCollection(where: { slug: $slug }) {
258
+ * items { id data }
259
+ * }
260
+ * }
261
+ * `, { slug: 'my-post' });
262
+ */
263
+ async graphql(projectId, query, variables) {
264
+ try {
265
+ // Automatically convert kebab-case slugs to camelCase for GraphQL
266
+ const { query: processedQuery, fieldMap } = this.convertQuerySlugs(query);
267
+ const response = await this.request('/graphql', {
268
+ method: 'POST',
269
+ headers: {
270
+ 'x-project-id': projectId,
271
+ },
272
+ body: JSON.stringify({ query: processedQuery, variables }),
273
+ });
274
+ // Fixed: Safe check for errors array
275
+ if (response.errors && Array.isArray(response.errors) && response.errors.length > 0) {
276
+ const errorMessages = response.errors.map((e) => e.message).join(', ');
277
+ throw new MybeSDKError(`GraphQL query failed: ${errorMessages}`, 400, response.errors);
278
+ }
279
+ // Convert response keys back to original format (kebab-case if that's what user used)
280
+ const convertedData = this.convertResponseKeys(response.data, fieldMap);
281
+ return convertedData;
282
+ }
283
+ catch (error) {
284
+ // Re-throw SDK errors
285
+ if (error instanceof MybeSDKError) {
286
+ throw error;
287
+ }
288
+ // Handle other errors
289
+ if (error instanceof Error) {
290
+ throw new MybeSDKError(`GraphQL request failed: ${error.message}`, undefined, error);
291
+ }
292
+ throw new MybeSDKError('GraphQL request failed with unknown error');
293
+ }
294
+ }
129
295
  }
package/dist/types.d.ts CHANGED
@@ -17,6 +17,15 @@ export interface ContentType {
17
17
  created_at: string;
18
18
  updated_at: string;
19
19
  }
20
+ /**
21
+ * Content Type Metadata (for GraphQL responses)
22
+ */
23
+ export interface ContentTypeMetadata {
24
+ id: string;
25
+ name: string;
26
+ slug: string;
27
+ description?: string;
28
+ }
20
29
  /**
21
30
  * Content Entry
22
31
  */
@@ -26,6 +35,7 @@ export interface ContentEntry {
26
35
  project_id: string;
27
36
  slug?: string;
28
37
  status: 'draft' | 'published' | 'archived';
38
+ contentType?: ContentTypeMetadata;
29
39
  data: Record<string, any>;
30
40
  locale?: string;
31
41
  created_by: string;
package/package.json CHANGED
@@ -1,24 +1,24 @@
1
- {
2
- "name": "@mybe/sdk",
3
- "version": "1.0.2",
4
- "type": "module",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
7
- "files": [
8
- "dist/",
9
- "README.md"
10
- ],
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "default": "./dist/index.js"
15
- }
16
- },
17
- "scripts": {
18
- "build": "tsc",
19
- "dev": "tsc --watch",
20
- "typecheck": "tsc --noEmit",
21
- "test": "npm run build && npx tsx test.ts",
22
- "prepublishOnly": "npm run build"
23
- }
24
- }
1
+ {
2
+ "name": "@mybe/sdk",
3
+ "version": "1.0.5",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "files": [
8
+ "dist/",
9
+ "README.md"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "dev": "tsc --watch",
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "npm run build && npx tsx test.ts",
22
+ "prepublishOnly": "npm run build"
23
+ }
24
+ }