@bernierllc/contentful-cma-client 1.0.2

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,538 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.ContentfulCMAClient = void 0;
14
+ const contentful_management_1 = __importDefault(require("contentful-management"));
15
+ const logger_1 = require("@bernierllc/logger");
16
+ class ContentfulCMAClient {
17
+ constructor(config) {
18
+ // Set defaults
19
+ this.config = {
20
+ environmentId: 'master',
21
+ host: 'api.contentful.com',
22
+ retryOnError: true,
23
+ timeout: 30000,
24
+ ...config
25
+ };
26
+ // Initialize logger
27
+ this.logger = new logger_1.Logger({
28
+ level: logger_1.LogLevel.INFO,
29
+ transports: [new logger_1.ConsoleTransport()],
30
+ context: {
31
+ service: 'contentful-cma-client',
32
+ version: '1.0.0',
33
+ spaceId: config.spaceId,
34
+ environmentId: this.config.environmentId
35
+ }
36
+ });
37
+ // Create contentful-management client with plain API
38
+ this.client = contentful_management_1.default.createClient({
39
+ accessToken: config.accessToken,
40
+ host: this.config.host
41
+ }, {
42
+ type: 'plain',
43
+ defaults: {
44
+ spaceId: config.spaceId,
45
+ environmentId: this.config.environmentId
46
+ }
47
+ });
48
+ this.logger.info('ContentfulCMAClient initialized', {
49
+ spaceId: config.spaceId,
50
+ environmentId: this.config.environmentId,
51
+ host: this.config.host
52
+ });
53
+ }
54
+ /**
55
+ * Get entry by ID
56
+ */
57
+ async getEntry(entryId) {
58
+ try {
59
+ this.logger.debug('Getting entry', { entryId });
60
+ const entry = await this.client.entry.get({ entryId });
61
+ this.logger.debug('Entry retrieved', { entryId });
62
+ return entry;
63
+ }
64
+ catch (error) {
65
+ const errorObj = error instanceof Error ? error : new Error(String(error));
66
+ this.logger.error('Failed to get entry', errorObj, { entryId });
67
+ throw error;
68
+ }
69
+ }
70
+ /**
71
+ * Get multiple entries with optional query parameters
72
+ */
73
+ async getEntries(query) {
74
+ try {
75
+ this.logger.debug('Getting entries', { query: query || {} });
76
+ const response = await this.client.entry.getMany({
77
+ query: query
78
+ });
79
+ this.logger.debug('Entries retrieved', {
80
+ count: response.items.length,
81
+ total: response.total
82
+ });
83
+ return response.items;
84
+ }
85
+ catch (error) {
86
+ const errorObj = error instanceof Error ? error : new Error(String(error));
87
+ this.logger.error('Failed to get entries', errorObj, { query: query || {} });
88
+ throw error;
89
+ }
90
+ }
91
+ /**
92
+ * Get all entries with automatic pagination
93
+ */
94
+ async getAllEntries(query) {
95
+ const all = [];
96
+ let skip = 0;
97
+ const limit = 100;
98
+ this.logger.debug('Getting all entries with pagination', { query: query || {} });
99
+ let hasMore = true;
100
+ while (hasMore) {
101
+ const response = await this.client.entry.getMany({
102
+ query: { ...query, skip, limit }
103
+ });
104
+ all.push(...response.items);
105
+ this.logger.debug('Retrieved entry batch', {
106
+ batch: response.items.length,
107
+ total: all.length,
108
+ remaining: response.total - (skip + response.items.length)
109
+ });
110
+ if (skip + response.items.length >= response.total) {
111
+ hasMore = false;
112
+ }
113
+ else {
114
+ skip += response.items.length;
115
+ }
116
+ }
117
+ this.logger.info('All entries retrieved', { totalCount: all.length });
118
+ return all;
119
+ }
120
+ /**
121
+ * Create entry
122
+ */
123
+ async createEntry(contentTypeId, fields, options) {
124
+ try {
125
+ this.logger.debug('Creating entry', { contentTypeId, options: options || {} });
126
+ const entry = await this.client.entry.create({ contentTypeId, ...(options?.entryId ? { entryId: options.entryId } : {}) }, { fields: fields });
127
+ this.logger.info('Entry created', {
128
+ entryId: entry.sys.id,
129
+ contentTypeId
130
+ });
131
+ return entry;
132
+ }
133
+ catch (error) {
134
+ const errorObj = error instanceof Error ? error : new Error(String(error));
135
+ this.logger.error('Failed to create entry', errorObj, { contentTypeId });
136
+ throw error;
137
+ }
138
+ }
139
+ /**
140
+ * Update entry
141
+ */
142
+ async updateEntry(entryId, fields, version, _options) {
143
+ try {
144
+ this.logger.debug('Updating entry', { entryId, version });
145
+ // Type assertion needed due to contentful-management strict typing
146
+ const entry = await this.client.entry.update({ entryId }, {
147
+ sys: { version },
148
+ fields: fields
149
+ });
150
+ this.logger.info('Entry updated', { entryId, newVersion: entry.sys.version });
151
+ return entry;
152
+ }
153
+ catch (error) {
154
+ const errorObj = error instanceof Error ? error : new Error(String(error));
155
+ this.logger.error('Failed to update entry', errorObj, { entryId, version });
156
+ throw error;
157
+ }
158
+ }
159
+ /**
160
+ * Publish entry
161
+ */
162
+ async publishEntry(entryId, version) {
163
+ try {
164
+ this.logger.debug('Publishing entry', { entryId, version });
165
+ // Type assertion needed due to contentful-management strict typing
166
+ const entry = await this.client.entry.publish({ entryId }, { sys: { version } });
167
+ this.logger.info('Entry published', {
168
+ entryId,
169
+ publishedVersion: entry.sys.publishedVersion
170
+ });
171
+ return entry;
172
+ }
173
+ catch (error) {
174
+ const errorObj = error instanceof Error ? error : new Error(String(error));
175
+ this.logger.error('Failed to publish entry', errorObj, { entryId, version });
176
+ throw error;
177
+ }
178
+ }
179
+ /**
180
+ * Unpublish entry
181
+ */
182
+ async unpublishEntry(entryId) {
183
+ try {
184
+ this.logger.debug('Unpublishing entry', { entryId });
185
+ const entry = await this.client.entry.unpublish({ entryId });
186
+ this.logger.info('Entry unpublished', { entryId });
187
+ return entry;
188
+ }
189
+ catch (error) {
190
+ const errorObj = error instanceof Error ? error : new Error(String(error));
191
+ this.logger.error('Failed to unpublish entry', errorObj, { entryId });
192
+ throw error;
193
+ }
194
+ }
195
+ /**
196
+ * Archive entry
197
+ */
198
+ async archiveEntry(entryId, version) {
199
+ try {
200
+ this.logger.debug('Archiving entry', { entryId, version });
201
+ // Type assertion needed due to contentful-management strict typing
202
+ const entry = await this.client.entry.archive({ entryId, version });
203
+ this.logger.info('Entry archived', { entryId });
204
+ return entry;
205
+ }
206
+ catch (error) {
207
+ const errorObj = error instanceof Error ? error : new Error(String(error));
208
+ this.logger.error('Failed to archive entry', errorObj, { entryId, version });
209
+ throw error;
210
+ }
211
+ }
212
+ /**
213
+ * Unarchive entry
214
+ */
215
+ async unarchiveEntry(entryId) {
216
+ try {
217
+ this.logger.debug('Unarchiving entry', { entryId });
218
+ const entry = await this.client.entry.unarchive({ entryId });
219
+ this.logger.info('Entry unarchived', { entryId });
220
+ return entry;
221
+ }
222
+ catch (error) {
223
+ const errorObj = error instanceof Error ? error : new Error(String(error));
224
+ this.logger.error('Failed to unarchive entry', errorObj, { entryId });
225
+ throw error;
226
+ }
227
+ }
228
+ /**
229
+ * Delete entry
230
+ */
231
+ async deleteEntry(entryId) {
232
+ try {
233
+ this.logger.debug('Deleting entry', { entryId });
234
+ await this.client.entry.delete({ entryId });
235
+ this.logger.info('Entry deleted', { entryId });
236
+ }
237
+ catch (error) {
238
+ const errorObj = error instanceof Error ? error : new Error(String(error));
239
+ this.logger.error('Failed to delete entry', errorObj, { entryId });
240
+ throw error;
241
+ }
242
+ }
243
+ /**
244
+ * Get asset by ID
245
+ */
246
+ async getAsset(assetId) {
247
+ try {
248
+ this.logger.debug('Getting asset', { assetId });
249
+ const asset = await this.client.asset.get({ assetId });
250
+ this.logger.debug('Asset retrieved', { assetId });
251
+ return asset;
252
+ }
253
+ catch (error) {
254
+ const errorObj = error instanceof Error ? error : new Error(String(error));
255
+ this.logger.error('Failed to get asset', errorObj, { assetId });
256
+ throw error;
257
+ }
258
+ }
259
+ /**
260
+ * Get multiple assets with optional query parameters
261
+ */
262
+ async getAssets(query) {
263
+ try {
264
+ this.logger.debug('Getting assets', { query: query || {} });
265
+ const response = await this.client.asset.getMany({
266
+ query: query
267
+ });
268
+ this.logger.debug('Assets retrieved', {
269
+ count: response.items.length,
270
+ total: response.total
271
+ });
272
+ return response.items;
273
+ }
274
+ catch (error) {
275
+ const errorObj = error instanceof Error ? error : new Error(String(error));
276
+ this.logger.error('Failed to get assets', errorObj, { query: query || {} });
277
+ throw error;
278
+ }
279
+ }
280
+ /**
281
+ * Get all assets with automatic pagination
282
+ */
283
+ async getAllAssets(query) {
284
+ const all = [];
285
+ let skip = 0;
286
+ const limit = 100;
287
+ this.logger.debug('Getting all assets with pagination', { query: query || {} });
288
+ let hasMore = true;
289
+ while (hasMore) {
290
+ const response = await this.client.asset.getMany({
291
+ query: { ...query, skip, limit }
292
+ });
293
+ all.push(...response.items);
294
+ this.logger.debug('Retrieved asset batch', {
295
+ batch: response.items.length,
296
+ total: all.length,
297
+ remaining: response.total - (skip + response.items.length)
298
+ });
299
+ if (skip + response.items.length >= response.total) {
300
+ hasMore = false;
301
+ }
302
+ else {
303
+ skip += response.items.length;
304
+ }
305
+ }
306
+ this.logger.info('All assets retrieved', { totalCount: all.length });
307
+ return all;
308
+ }
309
+ /**
310
+ * Create asset
311
+ */
312
+ async createAsset(fields, options) {
313
+ try {
314
+ this.logger.debug('Creating asset', { options: options || {} });
315
+ // Type assertion needed due to contentful-management strict typing
316
+ const asset = await this.client.asset.create({ ...(options?.assetId ? { assetId: options.assetId } : {}) }, { fields });
317
+ this.logger.info('Asset created', { assetId: asset.sys.id });
318
+ return asset;
319
+ }
320
+ catch (error) {
321
+ const errorObj = error instanceof Error ? error : new Error(String(error));
322
+ this.logger.error('Failed to create asset', errorObj);
323
+ throw error;
324
+ }
325
+ }
326
+ /**
327
+ * Process asset (for uploaded files)
328
+ */
329
+ async processAsset(assetId, version, locale = 'en-US') {
330
+ try {
331
+ this.logger.debug('Processing asset', { assetId, version, locale });
332
+ // Type assertion needed due to contentful-management strict typing
333
+ const asset = await this.client.asset.processForLocale({ assetId, version }, { sys: { version } }, locale);
334
+ this.logger.info('Asset processing started', { assetId });
335
+ return asset;
336
+ }
337
+ catch (error) {
338
+ const errorObj = error instanceof Error ? error : new Error(String(error));
339
+ this.logger.error('Failed to process asset', errorObj, {
340
+ assetId,
341
+ version,
342
+ locale
343
+ });
344
+ throw error;
345
+ }
346
+ }
347
+ /**
348
+ * Publish asset
349
+ */
350
+ async publishAsset(assetId, version) {
351
+ try {
352
+ this.logger.debug('Publishing asset', { assetId, version });
353
+ // Type assertion needed due to contentful-management strict typing
354
+ const asset = await this.client.asset.publish({ assetId }, { sys: { version } });
355
+ this.logger.info('Asset published', {
356
+ assetId,
357
+ publishedVersion: asset.sys.publishedVersion
358
+ });
359
+ return asset;
360
+ }
361
+ catch (error) {
362
+ const errorObj = error instanceof Error ? error : new Error(String(error));
363
+ this.logger.error('Failed to publish asset', errorObj, { assetId, version });
364
+ throw error;
365
+ }
366
+ }
367
+ /**
368
+ * Unpublish asset
369
+ */
370
+ async unpublishAsset(assetId) {
371
+ try {
372
+ this.logger.debug('Unpublishing asset', { assetId });
373
+ const asset = await this.client.asset.unpublish({ assetId });
374
+ this.logger.info('Asset unpublished', { assetId });
375
+ return asset;
376
+ }
377
+ catch (error) {
378
+ const errorObj = error instanceof Error ? error : new Error(String(error));
379
+ this.logger.error('Failed to unpublish asset', errorObj, { assetId });
380
+ throw error;
381
+ }
382
+ }
383
+ /**
384
+ * Delete asset
385
+ */
386
+ async deleteAsset(assetId) {
387
+ try {
388
+ this.logger.debug('Deleting asset', { assetId });
389
+ await this.client.asset.delete({ assetId });
390
+ this.logger.info('Asset deleted', { assetId });
391
+ }
392
+ catch (error) {
393
+ const errorObj = error instanceof Error ? error : new Error(String(error));
394
+ this.logger.error('Failed to delete asset', errorObj, { assetId });
395
+ throw error;
396
+ }
397
+ }
398
+ /**
399
+ * Get content type by ID
400
+ */
401
+ async getContentType(contentTypeId) {
402
+ try {
403
+ this.logger.debug('Getting content type', { contentTypeId });
404
+ const contentType = await this.client.contentType.get({ contentTypeId });
405
+ this.logger.debug('Content type retrieved', { contentTypeId });
406
+ return contentType;
407
+ }
408
+ catch (error) {
409
+ const errorObj = error instanceof Error ? error : new Error(String(error));
410
+ this.logger.error('Failed to get content type', errorObj, { contentTypeId });
411
+ throw error;
412
+ }
413
+ }
414
+ /**
415
+ * Get all content types
416
+ */
417
+ async getContentTypes(query) {
418
+ try {
419
+ this.logger.debug('Getting content types', { query: query || {} });
420
+ const response = await this.client.contentType.getMany({
421
+ query: query
422
+ });
423
+ this.logger.info('Content types retrieved', { count: response.items.length });
424
+ return response.items;
425
+ }
426
+ catch (error) {
427
+ const errorObj = error instanceof Error ? error : new Error(String(error));
428
+ this.logger.error('Failed to get content types', errorObj, { query: query || {} });
429
+ throw error;
430
+ }
431
+ }
432
+ /**
433
+ * Bulk create entries
434
+ */
435
+ async bulkCreateEntries(entries) {
436
+ const results = {
437
+ successful: [],
438
+ failed: []
439
+ };
440
+ this.logger.info('Starting bulk entry creation', { count: entries.length });
441
+ for (const entry of entries) {
442
+ try {
443
+ const created = await this.createEntry(entry.contentTypeId, entry.fields, { entryId: entry.entryId });
444
+ results.successful.push(created);
445
+ }
446
+ catch (error) {
447
+ results.failed.push({
448
+ item: { contentTypeId: entry.contentTypeId, fields: entry.fields },
449
+ error: this.getErrorMessage(error)
450
+ });
451
+ }
452
+ }
453
+ this.logger.info('Bulk entry creation completed', {
454
+ successful: results.successful.length,
455
+ failed: results.failed.length
456
+ });
457
+ return results;
458
+ }
459
+ /**
460
+ * Bulk delete entries
461
+ */
462
+ async bulkDeleteEntries(entryIds) {
463
+ const results = {
464
+ successful: [],
465
+ failed: []
466
+ };
467
+ this.logger.info('Starting bulk entry deletion', { count: entryIds.length });
468
+ for (const entryId of entryIds) {
469
+ try {
470
+ await this.deleteEntry(entryId);
471
+ results.successful.push(entryId);
472
+ }
473
+ catch (error) {
474
+ results.failed.push({
475
+ item: entryId,
476
+ error: this.getErrorMessage(error)
477
+ });
478
+ }
479
+ }
480
+ this.logger.info('Bulk entry deletion completed', {
481
+ successful: results.successful.length,
482
+ failed: results.failed.length
483
+ });
484
+ return results;
485
+ }
486
+ /**
487
+ * Bulk publish entries
488
+ */
489
+ async bulkPublishEntries(entries) {
490
+ const results = {
491
+ successful: [],
492
+ failed: []
493
+ };
494
+ this.logger.info('Starting bulk entry publishing', { count: entries.length });
495
+ for (const entry of entries) {
496
+ try {
497
+ const published = await this.publishEntry(entry.entryId, entry.version);
498
+ results.successful.push(published);
499
+ }
500
+ catch (error) {
501
+ results.failed.push({
502
+ item: entry,
503
+ error: this.getErrorMessage(error)
504
+ });
505
+ }
506
+ }
507
+ this.logger.info('Bulk entry publishing completed', {
508
+ successful: results.successful.length,
509
+ failed: results.failed.length
510
+ });
511
+ return results;
512
+ }
513
+ /**
514
+ * Helper method to extract error message
515
+ */
516
+ getErrorMessage(error) {
517
+ if (error instanceof Error) {
518
+ return error.message;
519
+ }
520
+ if (typeof error === 'string') {
521
+ return error;
522
+ }
523
+ return 'Unknown error';
524
+ }
525
+ /**
526
+ * Get current space ID
527
+ */
528
+ getSpaceId() {
529
+ return this.config.spaceId;
530
+ }
531
+ /**
532
+ * Get current environment ID
533
+ */
534
+ getEnvironmentId() {
535
+ return this.config.environmentId;
536
+ }
537
+ }
538
+ exports.ContentfulCMAClient = ContentfulCMAClient;
@@ -0,0 +1,2 @@
1
+ export { ContentfulCMAClient } from './ContentfulCMAClient';
2
+ export { CMAQueryOptions, CMAEntryCreateOptions, CMAEntryUpdateOptions, CMAAssetCreateOptions, CMABulkOperationResult } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.ContentfulCMAClient = void 0;
11
+ var ContentfulCMAClient_1 = require("./ContentfulCMAClient");
12
+ Object.defineProperty(exports, "ContentfulCMAClient", { enumerable: true, get: function () { return ContentfulCMAClient_1.ContentfulCMAClient; } });
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Query options for CMA operations
3
+ */
4
+ export interface CMAQueryOptions {
5
+ skip?: number;
6
+ limit?: number;
7
+ order?: string;
8
+ locale?: string;
9
+ content_type?: string;
10
+ [key: string]: string | number | undefined;
11
+ }
12
+ /**
13
+ * Options for creating entries
14
+ */
15
+ export interface CMAEntryCreateOptions {
16
+ entryId?: string;
17
+ }
18
+ /**
19
+ * Options for updating entries
20
+ */
21
+ export interface CMAEntryUpdateOptions {
22
+ locale?: string;
23
+ }
24
+ /**
25
+ * Options for creating assets
26
+ */
27
+ export interface CMAAssetCreateOptions {
28
+ assetId?: string;
29
+ }
30
+ /**
31
+ * Result of bulk operations
32
+ */
33
+ export interface CMABulkOperationResult<T> {
34
+ successful: T[];
35
+ failed: Array<{
36
+ item: unknown;
37
+ error: string;
38
+ }>;
39
+ }
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,31 @@
1
+ /*
2
+ Copyright (c) 2025 Bernier LLC
3
+
4
+ This file is licensed to the client under a limited-use license.
5
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
6
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
+ */
8
+
9
+ module.exports = {
10
+ preset: 'ts-jest',
11
+ testEnvironment: '<rootDir>/../../../jest-environment-node-fixed.cjs',
12
+ roots: ['<rootDir>/src'],
13
+ testMatch: ['**/__tests__/**/*.test.ts'],
14
+ collectCoverageFrom: [
15
+ 'src/**/*.ts',
16
+ '!src/**/*.test.ts',
17
+ '!src/**/__tests__/**'
18
+ ],
19
+ coverageThreshold: {
20
+ global: {
21
+ branches: 70,
22
+ functions: 90,
23
+ lines: 90,
24
+ statements: 90
25
+ }
26
+ },
27
+ moduleNameMapper: {
28
+ '^@bernierllc/contentful-types$': '<rootDir>/../contentful-types/src',
29
+ '^@bernierllc/logger$': '<rootDir>/../logger/src'
30
+ }
31
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@bernierllc/contentful-cma-client",
3
+ "version": "1.0.2",
4
+ "description": "Thin wrapper around contentful-management SDK with consistent error handling, logging, and typed interfaces",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest",
10
+ "test:run": "jest",
11
+ "test:watch": "jest --watch",
12
+ "test:coverage": "jest --coverage",
13
+ "lint": "eslint src --ext .ts",
14
+ "lint:fix": "eslint src --ext .ts --fix",
15
+ "clean": "rm -rf dist"
16
+ },
17
+ "keywords": [
18
+ "contentful",
19
+ "cma",
20
+ "content-management",
21
+ "api-wrapper",
22
+ "bernierllc"
23
+ ],
24
+ "author": "Bernier LLC",
25
+ "license": "SEE LICENSE IN LICENSE.md",
26
+ "dependencies": {
27
+ "@bernierllc/contentful-types": "workspace:*",
28
+ "@bernierllc/logger": "^1.0.1",
29
+ "contentful-management": "^11.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/jest": "^29.5.0",
33
+ "@types/node": "^20.0.0",
34
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
35
+ "@typescript-eslint/parser": "^6.0.0",
36
+ "eslint": "^8.0.0",
37
+ "jest": "^29.0.0",
38
+ "ts-jest": "^29.0.0",
39
+ "typescript": "^5.0.0"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }