@mixpeek/contentful 1.0.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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/api/mixpeekClient.js +111 -0
- package/dist/cache/cacheManager.js +90 -0
- package/dist/config/constants.js +48 -0
- package/dist/index.cjs +8 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.js +46 -0
- package/dist/modules/contentEnricher.js +92 -0
- package/dist/modules/contentfulClient.js +92 -0
- package/dist/modules/webhookHandler.js +92 -0
- package/dist/utils/helpers.js +49 -0
- package/dist/utils/logger.js +63 -0
- package/package.json +67 -0
- package/src/api/mixpeekClient.js +111 -0
- package/src/cache/cacheManager.js +90 -0
- package/src/config/constants.js +48 -0
- package/src/index.js +46 -0
- package/src/modules/contentEnricher.js +92 -0
- package/src/modules/contentfulClient.js +92 -0
- package/src/modules/webhookHandler.js +92 -0
- package/src/utils/helpers.js +49 -0
- package/src/utils/logger.js +63 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mixpeek/contentful — ContentfulClient
|
|
3
|
+
*
|
|
4
|
+
* Contentful Management API client for reading/writing enrichment data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createClient } from '../api/mixpeekClient.js';
|
|
8
|
+
import { createCacheManager } from '../cache/cacheManager.js';
|
|
9
|
+
import { getLogger } from '../utils/logger.js';
|
|
10
|
+
import { DEFAULT_CONFIG } from '../config/constants.js';
|
|
11
|
+
|
|
12
|
+
class ContentfulClient {
|
|
13
|
+
/**
|
|
14
|
+
* @param {Object} config
|
|
15
|
+
* @param {string} config.apiKey - Mixpeek API key
|
|
16
|
+
* @param {string} [config.endpoint] - API endpoint
|
|
17
|
+
* @param {number} [config.timeout] - Request timeout in ms
|
|
18
|
+
* @param {number} [config.cacheTTL] - Cache TTL in seconds
|
|
19
|
+
* @param {boolean} [config.enableCache] - Enable caching
|
|
20
|
+
* @param {boolean} [config.debug] - Enable debug logging
|
|
21
|
+
*/
|
|
22
|
+
constructor(config = {}) {
|
|
23
|
+
if (!config.apiKey) throw new Error('apiKey is required for ContentfulClient');
|
|
24
|
+
|
|
25
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
26
|
+
this.client = createClient({
|
|
27
|
+
apiKey: config.apiKey,
|
|
28
|
+
endpoint: this.config.endpoint,
|
|
29
|
+
timeout: this.config.timeout,
|
|
30
|
+
debug: this.config.debug
|
|
31
|
+
});
|
|
32
|
+
this.cache = this.config.enableCache ? createCacheManager({ ttl: this.config.cacheTTL, debug: this.config.debug }) : null;
|
|
33
|
+
this.logger = getLogger({ debug: this.config.debug });
|
|
34
|
+
this.metrics = { requests: 0, errors: 0, totalLatencyMs: 0 };
|
|
35
|
+
this.logger.info('ContentfulClient initialized');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getEntry(...args) {
|
|
39
|
+
this.logger.debug('ContentfulClient.getEntry called');
|
|
40
|
+
// TODO: Implement getEntry
|
|
41
|
+
throw new Error('ContentfulClient.getEntry not yet implemented');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getEntries(...args) {
|
|
45
|
+
this.logger.debug('ContentfulClient.getEntries called');
|
|
46
|
+
// TODO: Implement getEntries
|
|
47
|
+
throw new Error('ContentfulClient.getEntries not yet implemented');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async updateEntry(...args) {
|
|
51
|
+
this.logger.debug('ContentfulClient.updateEntry called');
|
|
52
|
+
// TODO: Implement updateEntry
|
|
53
|
+
throw new Error('ContentfulClient.updateEntry not yet implemented');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getAsset(...args) {
|
|
57
|
+
this.logger.debug('ContentfulClient.getAsset called');
|
|
58
|
+
// TODO: Implement getAsset
|
|
59
|
+
throw new Error('ContentfulClient.getAsset not yet implemented');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async publishEntry(...args) {
|
|
63
|
+
this.logger.debug('ContentfulClient.publishEntry called');
|
|
64
|
+
// TODO: Implement publishEntry
|
|
65
|
+
throw new Error('ContentfulClient.publishEntry not yet implemented');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getMetrics() {
|
|
69
|
+
return {
|
|
70
|
+
...this.metrics,
|
|
71
|
+
avgLatencyMs: this.metrics.requests > 0 ? this.metrics.totalLatencyMs / this.metrics.requests : 0,
|
|
72
|
+
cache: this.cache ? this.cache.getStats() : null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
resetMetrics() {
|
|
77
|
+
this.metrics = { requests: 0, errors: 0, totalLatencyMs: 0 };
|
|
78
|
+
if (this.cache) this.cache.resetStats();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
destroy() {
|
|
82
|
+
if (this.cache) this.cache.destroy();
|
|
83
|
+
this.logger.info('ContentfulClient destroyed');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createContentfulClient(config) {
|
|
88
|
+
return new ContentfulClient(config);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { ContentfulClient };
|
|
92
|
+
export default { createContentfulClient, ContentfulClient };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mixpeek/contentful — WebhookHandler
|
|
3
|
+
*
|
|
4
|
+
* Handles Contentful webhooks (entry publish/unpublish/archive) and triggers enrichment
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createClient } from '../api/mixpeekClient.js';
|
|
8
|
+
import { createCacheManager } from '../cache/cacheManager.js';
|
|
9
|
+
import { getLogger } from '../utils/logger.js';
|
|
10
|
+
import { DEFAULT_CONFIG } from '../config/constants.js';
|
|
11
|
+
|
|
12
|
+
class WebhookHandler {
|
|
13
|
+
/**
|
|
14
|
+
* @param {Object} config
|
|
15
|
+
* @param {string} config.apiKey - Mixpeek API key
|
|
16
|
+
* @param {string} [config.endpoint] - API endpoint
|
|
17
|
+
* @param {number} [config.timeout] - Request timeout in ms
|
|
18
|
+
* @param {number} [config.cacheTTL] - Cache TTL in seconds
|
|
19
|
+
* @param {boolean} [config.enableCache] - Enable caching
|
|
20
|
+
* @param {boolean} [config.debug] - Enable debug logging
|
|
21
|
+
*/
|
|
22
|
+
constructor(config = {}) {
|
|
23
|
+
if (!config.apiKey) throw new Error('apiKey is required for WebhookHandler');
|
|
24
|
+
|
|
25
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
26
|
+
this.client = createClient({
|
|
27
|
+
apiKey: config.apiKey,
|
|
28
|
+
endpoint: this.config.endpoint,
|
|
29
|
+
timeout: this.config.timeout,
|
|
30
|
+
debug: this.config.debug
|
|
31
|
+
});
|
|
32
|
+
this.cache = this.config.enableCache ? createCacheManager({ ttl: this.config.cacheTTL, debug: this.config.debug }) : null;
|
|
33
|
+
this.logger = getLogger({ debug: this.config.debug });
|
|
34
|
+
this.metrics = { requests: 0, errors: 0, totalLatencyMs: 0 };
|
|
35
|
+
this.logger.info('WebhookHandler initialized');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async handleWebhook(...args) {
|
|
39
|
+
this.logger.debug('WebhookHandler.handleWebhook called');
|
|
40
|
+
// TODO: Implement handleWebhook
|
|
41
|
+
throw new Error('WebhookHandler.handleWebhook not yet implemented');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
verifySignature(...args) {
|
|
45
|
+
this.logger.debug('WebhookHandler.verifySignature called');
|
|
46
|
+
// TODO: Implement verifySignature
|
|
47
|
+
throw new Error('WebhookHandler.verifySignature not yet implemented');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async registerWebhook(...args) {
|
|
51
|
+
this.logger.debug('WebhookHandler.registerWebhook called');
|
|
52
|
+
// TODO: Implement registerWebhook
|
|
53
|
+
throw new Error('WebhookHandler.registerWebhook not yet implemented');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async listWebhooks(...args) {
|
|
57
|
+
this.logger.debug('WebhookHandler.listWebhooks called');
|
|
58
|
+
// TODO: Implement listWebhooks
|
|
59
|
+
throw new Error('WebhookHandler.listWebhooks not yet implemented');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async deleteWebhook(...args) {
|
|
63
|
+
this.logger.debug('WebhookHandler.deleteWebhook called');
|
|
64
|
+
// TODO: Implement deleteWebhook
|
|
65
|
+
throw new Error('WebhookHandler.deleteWebhook not yet implemented');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getMetrics() {
|
|
69
|
+
return {
|
|
70
|
+
...this.metrics,
|
|
71
|
+
avgLatencyMs: this.metrics.requests > 0 ? this.metrics.totalLatencyMs / this.metrics.requests : 0,
|
|
72
|
+
cache: this.cache ? this.cache.getStats() : null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
resetMetrics() {
|
|
77
|
+
this.metrics = { requests: 0, errors: 0, totalLatencyMs: 0 };
|
|
78
|
+
if (this.cache) this.cache.resetStats();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
destroy() {
|
|
82
|
+
if (this.cache) this.cache.destroy();
|
|
83
|
+
this.logger.info('WebhookHandler destroyed');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createWebhookHandler(config) {
|
|
88
|
+
return new WebhookHandler(config);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { WebhookHandler };
|
|
92
|
+
export default { createWebhookHandler, WebhookHandler };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mixpeek/contentful — Helper Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function generateId() {
|
|
6
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createCacheKey(content) {
|
|
10
|
+
const str = JSON.stringify(content);
|
|
11
|
+
let hash = 0;
|
|
12
|
+
for (let i = 0; i < str.length; i++) {
|
|
13
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
14
|
+
hash = hash & hash;
|
|
15
|
+
}
|
|
16
|
+
return `mixpeek_${Math.abs(hash).toString(36)}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function sanitizeText(text, maxLength = 50000) {
|
|
20
|
+
if (!text || typeof text !== 'string') return '';
|
|
21
|
+
return text.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().substring(0, maxLength);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function deepMerge(target, source) {
|
|
25
|
+
const result = { ...target };
|
|
26
|
+
for (const key of Object.keys(source)) {
|
|
27
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
28
|
+
result[key] = target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])
|
|
29
|
+
? deepMerge(target[key], source[key]) : { ...source[key] };
|
|
30
|
+
} else if (Array.isArray(source[key])) {
|
|
31
|
+
result[key] = [...source[key]];
|
|
32
|
+
} else {
|
|
33
|
+
result[key] = source[key];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isValidUrl(url) {
|
|
40
|
+
if (!url || typeof url !== 'string') return false;
|
|
41
|
+
try { new URL(url); return true; } catch { return false; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function extractDomain(url) {
|
|
45
|
+
if (!isValidUrl(url)) return null;
|
|
46
|
+
try { return new URL(url).hostname; } catch { return null; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default { generateId, createCacheKey, sanitizeText, deepMerge, isValidUrl, extractDomain };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mixpeek/contentful — Logger Utility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const LOG_LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, NONE: 4 };
|
|
6
|
+
|
|
7
|
+
class Logger {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.prefix = options.prefix || '[Mixpeek-Contentful]';
|
|
10
|
+
this.level = options.debug ? LOG_LEVELS.DEBUG : LOG_LEVELS.INFO;
|
|
11
|
+
this.enabled = options.enabled !== false;
|
|
12
|
+
this.timers = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setLevel(level) {
|
|
16
|
+
this.level = typeof level === 'string' ? (LOG_LEVELS[level.toUpperCase()] ?? LOG_LEVELS.INFO) : level;
|
|
17
|
+
}
|
|
18
|
+
setEnabled(enabled) { this.enabled = enabled; }
|
|
19
|
+
|
|
20
|
+
_log(level, levelName, ...args) {
|
|
21
|
+
if (!this.enabled || level < this.level) return;
|
|
22
|
+
const ts = new Date().toISOString();
|
|
23
|
+
const formatted = args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : a);
|
|
24
|
+
const msg = `${ts} ${this.prefix} [${levelName}]`;
|
|
25
|
+
if (level === LOG_LEVELS.ERROR) console.error(msg, ...formatted);
|
|
26
|
+
else if (level === LOG_LEVELS.WARN) console.warn(msg, ...formatted);
|
|
27
|
+
else if (level === LOG_LEVELS.DEBUG) console.debug(msg, ...formatted);
|
|
28
|
+
else console.log(msg, ...formatted);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
debug(...args) { this._log(LOG_LEVELS.DEBUG, 'DEBUG', ...args); }
|
|
32
|
+
info(...args) { this._log(LOG_LEVELS.INFO, 'INFO', ...args); }
|
|
33
|
+
warn(...args) { this._log(LOG_LEVELS.WARN, 'WARN', ...args); }
|
|
34
|
+
error(...args) { this._log(LOG_LEVELS.ERROR, 'ERROR', ...args); }
|
|
35
|
+
|
|
36
|
+
time(label) { this.timers.set(label, performance.now ? performance.now() : Date.now()); }
|
|
37
|
+
timeEnd(label) {
|
|
38
|
+
const start = this.timers.get(label);
|
|
39
|
+
if (!start) return 0;
|
|
40
|
+
const elapsed = (performance.now ? performance.now() : Date.now()) - start;
|
|
41
|
+
this.timers.delete(label);
|
|
42
|
+
this.debug(`${label}: ${elapsed.toFixed(2)}ms`);
|
|
43
|
+
return elapsed;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
child(subPrefix) {
|
|
47
|
+
return new Logger({ prefix: `${this.prefix}[${subPrefix}]`, debug: this.level === LOG_LEVELS.DEBUG, enabled: this.enabled });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let defaultLogger = null;
|
|
52
|
+
export function getLogger(options = {}) {
|
|
53
|
+
if (!defaultLogger) { defaultLogger = new Logger(options); }
|
|
54
|
+
else if (Object.keys(options).length > 0) {
|
|
55
|
+
if (options.debug !== undefined) defaultLogger.setLevel(options.debug ? 'DEBUG' : 'INFO');
|
|
56
|
+
if (options.enabled !== undefined) defaultLogger.setEnabled(options.enabled);
|
|
57
|
+
}
|
|
58
|
+
return defaultLogger;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createLogger(options = {}) { return new Logger(options); }
|
|
62
|
+
export { Logger, LOG_LEVELS };
|
|
63
|
+
export default { getLogger, createLogger, Logger, LOG_LEVELS };
|