@contentstorage/core 2.1.1 → 2.2.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.
@@ -1,257 +0,0 @@
1
- import { populateTextWithVariables } from '../helpers/populateTextWithVariables.js';
2
- export let activeContent = null;
3
- export let appConfig = null;
4
- /**
5
- * NB! Only used when live editor mode is on
6
- */
7
- window.memoryMap = new Map();
8
- window.currentLanguageCode = null;
9
- /**
10
- * Loads and sets the content for a specific language.
11
- * It will internally ensure the application configuration (for contentDir) is loaded.
12
- * @param contentJson
13
- * Language code which is used for live editor to manage pending changes
14
- * @param languageCode
15
- */
16
- export function setContentLanguage({ languageCode, contentJson, }) {
17
- if (!contentJson || typeof contentJson !== 'object') {
18
- throw new Error('[Contentstorage] Invalid contentJson might be provided which caused setContentLanguage to fail.');
19
- }
20
- try {
21
- activeContent = contentJson; // Relies on augmentation
22
- if (typeof window !== 'undefined') {
23
- window.currentLanguageCode = languageCode;
24
- }
25
- console.log(`[Contentstorage] Content loaded.`);
26
- }
27
- catch (error) {
28
- activeContent = null; // Reset on failure
29
- window.currentLanguageCode = null;
30
- console.error(`[Contentstorage] Failed to load content. Error: ${error.message}`);
31
- }
32
- }
33
- export function initContentStorage(config) {
34
- if (!config ||
35
- typeof config !== 'object' ||
36
- !config.contentKey ||
37
- !Array.isArray(config.languageCodes)) {
38
- if (!config.contentKey) {
39
- throw new Error('[Contentstorage] No contentKey provided in initContentStorage function.');
40
- }
41
- if (!Array.isArray(config.languageCodes)) {
42
- throw new Error('[Contentstorage] No languageCodes provided in initContentStorage function.');
43
- }
44
- throw new Error('[Contentstorage] Invalid config.');
45
- }
46
- appConfig = config;
47
- }
48
- /**
49
- * Retrieves the text string from the loaded JSON content for the given path.
50
- * Autocompletion for pathString is enabled via module augmentation of CustomContentStructure.
51
- * `setContentLanguage()` must be called successfully before using this.
52
- *
53
- * @param contentId A dot-notation path string (e.g., 'HomePage.Login'). Autocompletion is provided.
54
- * @param variables Variables help to render dynamic content inside text strings
55
- * If not provided, and path is not found/value not string, undefined is returned.
56
- * @returns The text string from the JSON, or the fallbackValue, or undefined.
57
- */
58
- export function getText(contentId, variables) {
59
- const defaultVal = {
60
- contentId,
61
- text: '',
62
- };
63
- if (!activeContent) {
64
- const msg = `[Contentstorage] getText: Content not loaded (Key: "${String(contentId)}"). Ensure setContentLanguage() was called and completed successfully.`;
65
- console.warn(msg);
66
- return defaultVal;
67
- }
68
- const keys = contentId.split('.');
69
- let current = activeContent;
70
- for (const key of keys) {
71
- if (current && typeof current === 'object' && key in current) {
72
- current = current[key];
73
- }
74
- else {
75
- const msg = `[Contentstorage] getText: Path "${String(contentId)}" not found in loaded content.`;
76
- console.warn(msg);
77
- return defaultVal;
78
- }
79
- }
80
- if (typeof current === 'string') {
81
- if (window.parent && window.parent !== window) {
82
- const key = current;
83
- const existingEntry = window.memoryMap.get(key);
84
- const idSet = existingEntry ? existingEntry.ids : new Set();
85
- idSet.add(contentId); // Add the current ID to the set.
86
- window.memoryMap.set(key, {
87
- ids: idSet,
88
- type: 'text',
89
- });
90
- }
91
- if (!variables || Object.keys(variables).length === 0) {
92
- return {
93
- contentId,
94
- text: current,
95
- };
96
- }
97
- return {
98
- contentId,
99
- text: populateTextWithVariables(current, variables, contentId),
100
- };
101
- }
102
- else {
103
- const msg = `[Contentstorage] getText: Value at path "${String(contentId)}" is not a string (actual type: ${typeof current}).`;
104
- console.warn(msg);
105
- return defaultVal;
106
- }
107
- }
108
- export function getImage(contentId) {
109
- const defaultVal = {
110
- contentId,
111
- data: { url: '', altText: '', contentstorage_type: 'image' },
112
- };
113
- if (!activeContent) {
114
- const msg = `[Contentstorage] getImage: Content not loaded (Content Id: "${contentId}"). Ensure setContentLanguage() was called and completed successfully.`;
115
- console.warn(msg);
116
- return {
117
- contentId,
118
- data: { url: '', altText: '', contentstorage_type: 'image' },
119
- };
120
- }
121
- const keys = contentId.split('.');
122
- let current = activeContent;
123
- for (const key of keys) {
124
- if (current && typeof current === 'object' && key in current) {
125
- current = current[key];
126
- }
127
- else {
128
- const msg = `[Contentstorage] getImage: Path "${contentId}" not found in loaded content.`;
129
- console.warn(msg);
130
- return defaultVal;
131
- }
132
- }
133
- if (current &&
134
- typeof current === 'object' &&
135
- current.contentstorage_type === 'image' &&
136
- typeof current.url === 'string') {
137
- const currentData = current;
138
- const key = `https://cdn.contentstorage.app/${currentData.url}`;
139
- if (window.parent && window.parent !== window) {
140
- const existingEntry = window.memoryMap.get(key);
141
- const idSet = existingEntry ? existingEntry.ids : new Set();
142
- idSet.add(contentId); // Add the current ID to the set.
143
- window.memoryMap.set(key, {
144
- ids: idSet,
145
- type: 'image',
146
- });
147
- }
148
- console.log('currentData.url', currentData.url);
149
- return {
150
- contentId,
151
- data: {
152
- ...currentData,
153
- url: key,
154
- },
155
- };
156
- }
157
- else {
158
- const msg = `[Contentstorage] getImage: Value at path "${contentId}" is not a valid image object (actual value: ${JSON.stringify(current)}).`;
159
- console.warn(msg);
160
- return defaultVal;
161
- }
162
- }
163
- export function getVariation(contentId, variationKey, variables) {
164
- const defaultVal = {
165
- contentId,
166
- text: '',
167
- };
168
- if (!activeContent) {
169
- const msg = `[Contentstorage] getVariation: Content not loaded (Content Id: "${contentId}", Variation: "${variationKey?.toString()}"). Ensure setContentLanguage() was called and completed successfully.`;
170
- console.warn(msg);
171
- return defaultVal;
172
- }
173
- const keys = contentId.split('.');
174
- let current = activeContent;
175
- for (const key of keys) {
176
- if (current && typeof current === 'object' && key in current) {
177
- current = current[key];
178
- }
179
- else {
180
- const msg = `[Contentstorage] getVariation: Path "${contentId}" for variation object not found in loaded content.`;
181
- console.warn(msg);
182
- return defaultVal;
183
- }
184
- }
185
- if (current &&
186
- typeof current === 'object' &&
187
- current.contentstorage_type === 'variation' &&
188
- typeof current.data === 'object' &&
189
- current.data !== null) {
190
- const variationObject = current;
191
- if (variationKey &&
192
- typeof variationKey === 'string' &&
193
- variationKey in variationObject.data) {
194
- if (typeof variationObject.data[variationKey] === 'string') {
195
- const current = variationObject.data[variationKey];
196
- if (window.parent && window.parent !== window) {
197
- const key = current;
198
- const existingEntry = window.memoryMap.get(key);
199
- const idSet = existingEntry ? existingEntry.ids : new Set();
200
- idSet.add(contentId); // Add the current ID to the set.
201
- window.memoryMap.set(key, {
202
- ids: idSet,
203
- type: 'variation',
204
- variation: variationKey,
205
- });
206
- }
207
- if (!variables || Object.keys(variables).length === 0) {
208
- return {
209
- contentId,
210
- text: current,
211
- };
212
- }
213
- return {
214
- contentId,
215
- text: populateTextWithVariables(current, variables, contentId),
216
- };
217
- }
218
- else {
219
- const msg = `[Contentstorage] getVariation: Variation value for key "${variationKey}" at path "${contentId}" is not a string (actual type: ${typeof variationObject.data[variationKey]}).`;
220
- console.warn(msg);
221
- }
222
- }
223
- // If specific variationKey is not found or not provided, try to return the 'default' variation
224
- if ('default' in variationObject.data && typeof variationKey === 'string') {
225
- if (typeof variationObject.data.default === 'string') {
226
- if (variationKey && variationKey !== 'default') {
227
- console.warn(`[Contentstorage] getVariation: Variation key "${variationKey}" not found at path "${contentId}". Returning 'default' variation.`);
228
- }
229
- if (window.parent && window.parent !== window) {
230
- const key = current;
231
- const existingEntry = window.memoryMap.get(key);
232
- const idSet = existingEntry ? existingEntry.ids : new Set();
233
- idSet.add(contentId); // Add the current ID to the set.
234
- window.memoryMap.set(key, {
235
- ids: idSet,
236
- type: 'variation',
237
- variation: 'default',
238
- });
239
- }
240
- return {
241
- contentId,
242
- text: variationObject.data.default,
243
- };
244
- }
245
- else {
246
- console.warn(`[Contentstorage] getVariation: 'default' variation value at path "${contentId}" is not a string (actual type: ${typeof variationObject.data.default}).`);
247
- }
248
- }
249
- // If neither specific key nor 'default' is found or valid
250
- console.warn(`[Contentstorage] getVariation: Neither variation key "${variationKey?.toString()}" nor 'default' variation found or valid at path "${contentId}".`);
251
- return defaultVal;
252
- }
253
- else {
254
- console.warn(`[Contentstorage] getVariation: Value at path "${contentId}" is not a valid variation object (actual value: ${JSON.stringify(current)}).`);
255
- return defaultVal;
256
- }
257
- }
@@ -1,7 +0,0 @@
1
- import { LanguageCode } from '../../types.js';
2
- interface FetchContentOptions {
3
- withPendingChanges?: boolean;
4
- contentKey?: string;
5
- }
6
- export declare function fetchContent(language?: LanguageCode, options?: FetchContentOptions): Promise<void>;
7
- export {};
@@ -1,141 +0,0 @@
1
- import axios from 'axios';
2
- import { CONTENTSTORAGE_CONFIG } from '../../contentstorage-config.js';
3
- import { appConfig, setContentLanguage } from '../contentManagement.js';
4
- // Map of request keys to their debounce timers
5
- const debounceTimers = new Map();
6
- // Map of request keys to their cancel token sources
7
- const cancelTokenSources = new Map();
8
- // Fixed debounce delay in milliseconds
9
- const DEBOUNCE_MS = 200;
10
- export async function fetchContent(language, options = {}) {
11
- const { withPendingChanges, contentKey } = options;
12
- const languageToFetch = language || appConfig?.languageCodes?.[0];
13
- const usePendingChangesToFetch = withPendingChanges || appConfig?.pendingChanges;
14
- console.log(`Starting content fetch for language ${language}...`);
15
- if (!languageToFetch) {
16
- throw Error(`No language found`);
17
- }
18
- if (!appConfig) {
19
- throw Error(`No app config found`);
20
- }
21
- const effectiveContentKey = contentKey || appConfig.contentKey;
22
- let fileUrl;
23
- let requestConfig = {};
24
- if (usePendingChangesToFetch) {
25
- if (!effectiveContentKey) {
26
- throw Error(`No contentKey found`);
27
- }
28
- fileUrl = `${CONTENTSTORAGE_CONFIG.API_URL}/pending-changes/get-json?languageCode=${languageToFetch}`;
29
- requestConfig.headers = {
30
- 'X-Content-Key': effectiveContentKey,
31
- };
32
- // Apply debouncing and cancellation only for pending-changes API endpoint
33
- const requestKey = `${languageToFetch}:${effectiveContentKey}`;
34
- // Cancel any existing in-flight request for the same parameters
35
- const existingCancelSource = cancelTokenSources.get(requestKey);
36
- if (existingCancelSource) {
37
- existingCancelSource.cancel('Request cancelled due to new request');
38
- cancelTokenSources.delete(requestKey);
39
- }
40
- // Clear any existing debounce timer for the same parameters
41
- const existingTimer = debounceTimers.get(requestKey);
42
- if (existingTimer) {
43
- clearTimeout(existingTimer);
44
- debounceTimers.delete(requestKey);
45
- }
46
- // Return a promise that will resolve after debouncing
47
- return new Promise((resolve, reject) => {
48
- const timer = setTimeout(async () => {
49
- debounceTimers.delete(requestKey);
50
- // Create a cancel token for this request
51
- const cancelTokenSource = axios.CancelToken.source();
52
- requestConfig.cancelToken = cancelTokenSource.token;
53
- cancelTokenSources.set(requestKey, cancelTokenSource);
54
- try {
55
- // Fetch data for the current language
56
- const response = await axios.get(fileUrl, requestConfig);
57
- let jsonData = response.data;
58
- // Handle API response structure - only for pending changes API
59
- if (jsonData && typeof jsonData === 'object' && 'data' in jsonData) {
60
- jsonData = jsonData.data;
61
- }
62
- if (jsonData === undefined || jsonData === null) {
63
- throw new Error(`No data received from ${fileUrl} for language ${languageToFetch}.`);
64
- }
65
- // Validate that jsonData is a single, non-null JSON object (not an array)
66
- if (typeof jsonData !== 'object') {
67
- throw new Error(`Expected a single JSON object from ${fileUrl} for language ${languageToFetch}, but received type ${typeof jsonData}. Cannot proceed.`);
68
- }
69
- console.log(`Received JSON for ${languageToFetch}`);
70
- setContentLanguage({
71
- languageCode: languageToFetch,
72
- contentJson: jsonData,
73
- });
74
- // Clean up cancel token source
75
- cancelTokenSources.delete(requestKey);
76
- resolve();
77
- }
78
- catch (error) {
79
- // Clean up cancel token source
80
- cancelTokenSources.delete(requestKey);
81
- // Don't log cancelled requests as errors
82
- if (axios.isCancel(error)) {
83
- console.log(`Request cancelled for language ${languageToFetch}`);
84
- resolve();
85
- return;
86
- }
87
- // Catch errors related to fetching or saving a single language file
88
- console.error(`\nError processing language ${languageToFetch} from ${fileUrl}:`);
89
- if (axios.isAxiosError(error)) {
90
- console.error(` Status: ${error.response?.status}`);
91
- console.error(`Response Data: ${error.response?.data ? JSON.stringify(error.response.data) : 'N/A'}`);
92
- console.error(` Message: ${error.message}`);
93
- }
94
- else {
95
- console.error(` Error: ${error.message}`);
96
- }
97
- reject(error);
98
- }
99
- }, DEBOUNCE_MS);
100
- debounceTimers.set(requestKey, timer);
101
- });
102
- }
103
- else {
104
- fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${effectiveContentKey}/content/${languageToFetch}.json`;
105
- }
106
- try {
107
- // Fetch data for the current language (CDN endpoint - no debouncing)
108
- const response = await axios.get(fileUrl, requestConfig);
109
- let jsonData = response.data;
110
- // Handle API response structure - only for pending changes API
111
- if (usePendingChangesToFetch && jsonData && typeof jsonData === 'object' && 'data' in jsonData) {
112
- jsonData = jsonData.data;
113
- }
114
- if (jsonData === undefined || jsonData === null) {
115
- throw new Error(`No data received from ${fileUrl} for language ${languageToFetch}.`);
116
- }
117
- // Validate that jsonData is a single, non-null JSON object (not an array)
118
- // This check mirrors the original code's expectation for the content of a JSON file.
119
- if (typeof jsonData !== 'object') {
120
- throw new Error(`Expected a single JSON object from ${fileUrl} for language ${languageToFetch}, but received type ${typeof jsonData}. Cannot proceed.`);
121
- }
122
- console.log(`Received JSON for ${languageToFetch}`);
123
- setContentLanguage({
124
- languageCode: languageToFetch,
125
- contentJson: jsonData,
126
- });
127
- }
128
- catch (error) {
129
- // Catch errors related to fetching or saving a single language file
130
- console.error(`\nError processing language ${languageToFetch} from ${fileUrl}:`);
131
- if (axios.isAxiosError(error)) {
132
- console.error(` Status: ${error.response?.status}`);
133
- console.error(`Response Data: ${error.response?.data ? JSON.stringify(error.response.data) : 'N/A'}`);
134
- console.error(` Message: ${error.message}`); // Axios error message
135
- }
136
- else {
137
- // For non-Axios errors (e.g., manually thrown errors, fs errors)
138
- console.error(` Error: ${error.message}`);
139
- }
140
- }
141
- }
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
@@ -1,96 +0,0 @@
1
- #!/usr/bin/env node
2
- import chalk from 'chalk';
3
- import { pullContent } from './pull-content.js';
4
- import { generateTypes } from './generate-types.js';
5
- const COMMANDS = {
6
- pull: {
7
- name: 'pull',
8
- description: 'Pull content from Contentstorage CDN',
9
- usage: 'contentstorage pull [options]',
10
- options: [
11
- ' --content-key <key> Content key for your project',
12
- ' --content-dir <dir> Directory to save content files',
13
- ' --lang <code> Language code (e.g., EN, FR)',
14
- ' --pending-changes Fetch pending/draft content',
15
- ],
16
- },
17
- 'generate-types': {
18
- name: 'generate-types',
19
- description: 'Generate TypeScript type definitions from content',
20
- usage: 'contentstorage generate-types [options]',
21
- options: [
22
- ' --output <file> Output file for generated types',
23
- ' --content-key <key> Content key for your project',
24
- ' --lang <code> Language code (e.g., EN, FR)',
25
- ' --pending-changes Use pending/draft content',
26
- ],
27
- },
28
- };
29
- function showHelp() {
30
- console.log(chalk.bold('\nContentStorage CLI'));
31
- console.log(chalk.dim('Manage content and generate TypeScript types\n'));
32
- console.log(chalk.bold('Usage:'));
33
- console.log(' contentstorage <command> [options]\n');
34
- console.log(chalk.bold('Commands:'));
35
- Object.values(COMMANDS).forEach((cmd) => {
36
- console.log(` ${chalk.cyan(cmd.name.padEnd(20))} ${cmd.description}`);
37
- });
38
- console.log(chalk.bold('\nOptions:'));
39
- console.log(' --help Show help for a command\n');
40
- console.log(chalk.dim('Examples:'));
41
- console.log(chalk.dim(' contentstorage pull --content-key abc123'));
42
- console.log(chalk.dim(' contentstorage generate-types --output types.ts'));
43
- console.log(chalk.dim(' contentstorage pull --help # Show help for pull command\n'));
44
- }
45
- function showCommandHelp(commandName) {
46
- const cmd = COMMANDS[commandName];
47
- if (!cmd) {
48
- console.error(chalk.red(`Unknown command: ${commandName}`));
49
- process.exit(1);
50
- }
51
- console.log(chalk.bold(`\n${cmd.name}`));
52
- console.log(chalk.dim(cmd.description + '\n'));
53
- console.log(chalk.bold('Usage:'));
54
- console.log(` ${cmd.usage}\n`);
55
- if (cmd.options.length > 0) {
56
- console.log(chalk.bold('Options:'));
57
- cmd.options.forEach((opt) => console.log(opt));
58
- console.log('');
59
- }
60
- }
61
- async function main() {
62
- const args = process.argv.slice(2);
63
- // No arguments - show help
64
- if (args.length === 0) {
65
- showHelp();
66
- process.exit(0);
67
- }
68
- const command = args[0];
69
- // Global --help flag
70
- if (command === '--help' || command === '-h') {
71
- showHelp();
72
- process.exit(0);
73
- }
74
- // Command-specific --help
75
- if (args.includes('--help') || args.includes('-h')) {
76
- showCommandHelp(command);
77
- process.exit(0);
78
- }
79
- // Route to commands
80
- switch (command) {
81
- case 'pull':
82
- await pullContent();
83
- break;
84
- case 'generate-types':
85
- await generateTypes();
86
- break;
87
- default:
88
- console.error(chalk.red(`Unknown command: ${command}\n`));
89
- console.log(chalk.dim('Run "contentstorage --help" for usage'));
90
- process.exit(1);
91
- }
92
- }
93
- main().catch((error) => {
94
- console.error(chalk.red('Unexpected error:'), error);
95
- process.exit(1);
96
- });
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export declare function generateTypes(): Promise<void>;