@datocms/cma-client 5.1.0 → 5.1.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.
- package/dist/cjs/fieldTypes/rich_text.js +3 -3
- package/dist/cjs/fieldTypes/rich_text.js.map +1 -1
- package/dist/cjs/fieldTypes/single_block.js +3 -3
- package/dist/cjs/fieldTypes/single_block.js.map +1 -1
- package/dist/cjs/fieldTypes/structured_text.js +3 -3
- package/dist/cjs/fieldTypes/structured_text.js.map +1 -1
- package/dist/cjs/generated/Client.js +1 -1
- package/dist/cjs/utilities/fieldValueLocalization.js +90 -55
- package/dist/cjs/utilities/fieldValueLocalization.js.map +1 -1
- package/dist/cjs/utilities/structuredText.js +11 -11
- package/dist/cjs/utilities/structuredText.js.map +1 -1
- package/dist/esm/fieldTypes/rich_text.d.ts +2 -2
- package/dist/esm/fieldTypes/rich_text.js +1 -1
- package/dist/esm/fieldTypes/rich_text.js.map +1 -1
- package/dist/esm/fieldTypes/single_block.d.ts +2 -2
- package/dist/esm/fieldTypes/single_block.js +1 -1
- package/dist/esm/fieldTypes/single_block.js.map +1 -1
- package/dist/esm/fieldTypes/structured_text.d.ts +8 -8
- package/dist/esm/fieldTypes/structured_text.js +1 -1
- package/dist/esm/fieldTypes/structured_text.js.map +1 -1
- package/dist/esm/generated/Client.js +1 -1
- package/dist/esm/generated/SchemaTypes.d.ts +11 -6
- package/dist/esm/generated/SimpleSchemaTypes.d.ts +11 -6
- package/dist/esm/utilities/fieldValueLocalization.d.ts +51 -9
- package/dist/esm/utilities/fieldValueLocalization.js +87 -54
- package/dist/esm/utilities/fieldValueLocalization.js.map +1 -1
- package/dist/esm/utilities/structuredText.d.ts +2 -2
- package/dist/esm/utilities/structuredText.js +8 -8
- package/dist/esm/utilities/structuredText.js.map +1 -1
- package/dist/types/fieldTypes/rich_text.d.ts +2 -2
- package/dist/types/fieldTypes/single_block.d.ts +2 -2
- package/dist/types/fieldTypes/structured_text.d.ts +8 -8
- package/dist/types/generated/SchemaTypes.d.ts +11 -6
- package/dist/types/generated/SimpleSchemaTypes.d.ts +11 -6
- package/dist/types/utilities/fieldValueLocalization.d.ts +51 -9
- package/dist/types/utilities/structuredText.d.ts +2 -2
- package/package.json +4 -4
- package/src/fieldTypes/rich_text.ts +3 -3
- package/src/fieldTypes/single_block.ts +3 -3
- package/src/fieldTypes/structured_text.ts +13 -13
- package/src/generated/Client.ts +1 -1
- package/src/generated/SchemaTypes.ts +42 -7
- package/src/generated/SimpleSchemaTypes.ts +42 -7
- package/src/utilities/blocks.ts +6 -6
- package/src/utilities/fieldValueLocalization.ts +122 -93
- package/src/utilities/structuredText.ts +8 -8
|
@@ -38,18 +38,91 @@ import type * as SimpleSchemaTypes from '../generated/SimpleSchemaTypes';
|
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* Represents a localized field value in DatoCMS.
|
|
42
|
+
*
|
|
43
|
+
* In DatoCMS, fields can be localized. In this scenario, their value contains values
|
|
42
44
|
* for various locales structured as an object, such as
|
|
43
|
-
`{ "en": "Hello", "it": "Ciao" }`
|
|
45
|
+
* `{ "en": "Hello", "it": "Ciao" }`
|
|
44
46
|
*/
|
|
45
47
|
export type LocalizedFieldValue = Record<string, unknown>;
|
|
46
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Determines whether a DatoCMS field is localized or not.
|
|
51
|
+
*
|
|
52
|
+
* This function handles both full Schema field objects and simplified Schema field objects
|
|
53
|
+
* by checking the appropriate property based on the object structure.
|
|
54
|
+
*
|
|
55
|
+
* @param field - The DatoCMS field definition (either full or simple schema)
|
|
56
|
+
* @returns true if the field is localized, false otherwise
|
|
57
|
+
*/
|
|
47
58
|
export function isLocalized(
|
|
48
59
|
field: SchemaTypes.Field | SimpleSchemaTypes.Field,
|
|
49
60
|
): boolean {
|
|
50
61
|
return 'attributes' in field ? field.attributes.localized : field.localized;
|
|
51
62
|
}
|
|
52
63
|
|
|
64
|
+
/**
|
|
65
|
+
* A normalized entry that represents a single value from either a localized or non-localized field.
|
|
66
|
+
*
|
|
67
|
+
* For localized fields, each locale produces one entry with `locale` set to the locale code (e.g., "en", "it").
|
|
68
|
+
* For non-localized fields, there's one entry with `locale` set to `undefined`.
|
|
69
|
+
*
|
|
70
|
+
* This uniform structure allows the same processing logic to work with both field types.
|
|
71
|
+
*/
|
|
72
|
+
export type PossiblyLocalizedEntry = {
|
|
73
|
+
locale: string | undefined;
|
|
74
|
+
value: unknown;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Converts a field value (localized or non-localized) into a uniform array of entries.
|
|
79
|
+
*
|
|
80
|
+
* This function normalizes the handling of field values by converting them into a consistent
|
|
81
|
+
* array format, regardless of whether the field is localized or not.
|
|
82
|
+
*
|
|
83
|
+
* @param field - The DatoCMS field definition that determines localization behavior
|
|
84
|
+
* @param value - The field value to convert (either a localized object or direct value)
|
|
85
|
+
* @returns Array of entries where each entry contains a locale (string for localized, undefined for non-localized) and the corresponding value
|
|
86
|
+
*/
|
|
87
|
+
export function fieldValueToPossiblyLocalizedEntries(
|
|
88
|
+
field: SchemaTypes.Field | SimpleSchemaTypes.Field,
|
|
89
|
+
value: unknown,
|
|
90
|
+
) {
|
|
91
|
+
if (isLocalized(field)) {
|
|
92
|
+
const localizedValue = value as LocalizedFieldValue;
|
|
93
|
+
|
|
94
|
+
return Object.entries(localizedValue).map<PossiblyLocalizedEntry>(
|
|
95
|
+
([locale, value]) => ({ locale, value }),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return [{ locale: undefined, value }];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Converts an array of possibly localized entries back into the appropriate field value format.
|
|
104
|
+
*
|
|
105
|
+
* This function is the inverse of `fieldValueToPossiblyLocalizedEntries`. It takes a uniform
|
|
106
|
+
* array of entries and converts them back to either a localized object or a direct value,
|
|
107
|
+
* depending on the field's localization setting.
|
|
108
|
+
*
|
|
109
|
+
* @param field - The DatoCMS field definition that determines the output format
|
|
110
|
+
* @param possiblyLocalizedEntries - Array of entries to convert back to field value format
|
|
111
|
+
* @returns Either a localized object (for localized fields) or the direct value (for non-localized fields)
|
|
112
|
+
*/
|
|
113
|
+
export function possiblyLocalizedEntriesToFieldValue(
|
|
114
|
+
field: SchemaTypes.Field | SimpleSchemaTypes.Field,
|
|
115
|
+
possiblyLocalizedEntries: PossiblyLocalizedEntry[],
|
|
116
|
+
) {
|
|
117
|
+
if (isLocalized(field)) {
|
|
118
|
+
return Object.fromEntries(
|
|
119
|
+
possiblyLocalizedEntries.map(({ locale, value }) => [locale, value]),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return possiblyLocalizedEntries[0]?.value;
|
|
124
|
+
}
|
|
125
|
+
|
|
53
126
|
/**
|
|
54
127
|
* Maps localized field values using a provided mapping function.
|
|
55
128
|
* For localized fields, applies the mapping function to each locale value.
|
|
@@ -66,18 +139,12 @@ export function mapLocalizedFieldValues<T>(
|
|
|
66
139
|
value: unknown,
|
|
67
140
|
mapFn: (locale: string | undefined, localeValue: unknown) => T,
|
|
68
141
|
) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
mapFn(locale, value),
|
|
76
|
-
]),
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return mapFn(undefined, value);
|
|
142
|
+
const entries = fieldValueToPossiblyLocalizedEntries(field, value);
|
|
143
|
+
const mappedEntries = entries.map(({ locale, value }) => ({
|
|
144
|
+
locale,
|
|
145
|
+
value: mapFn(locale, value),
|
|
146
|
+
}));
|
|
147
|
+
return possiblyLocalizedEntriesToFieldValue(field, mappedEntries);
|
|
81
148
|
}
|
|
82
149
|
|
|
83
150
|
/**
|
|
@@ -96,19 +163,14 @@ export async function mapLocalizedFieldValuesAsync<T>(
|
|
|
96
163
|
value: unknown,
|
|
97
164
|
mapFn: (locale: string | undefined, localeValue: unknown) => Promise<T>,
|
|
98
165
|
) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
await
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
),
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return await mapFn(undefined, value);
|
|
166
|
+
const entries = fieldValueToPossiblyLocalizedEntries(field, value);
|
|
167
|
+
const mappedEntries = await Promise.all(
|
|
168
|
+
entries.map(async ({ locale, value }) => ({
|
|
169
|
+
locale,
|
|
170
|
+
value: await mapFn(locale, value),
|
|
171
|
+
})),
|
|
172
|
+
);
|
|
173
|
+
return possiblyLocalizedEntriesToFieldValue(field, mappedEntries);
|
|
112
174
|
}
|
|
113
175
|
|
|
114
176
|
/**
|
|
@@ -126,17 +188,16 @@ export function filterLocalizedFieldValues(
|
|
|
126
188
|
value: unknown,
|
|
127
189
|
filterFn: (locale: string | undefined, localeValue: unknown) => boolean,
|
|
128
190
|
) {
|
|
129
|
-
|
|
130
|
-
|
|
191
|
+
const entries = fieldValueToPossiblyLocalizedEntries(field, value);
|
|
192
|
+
const filteredEntries = entries.filter(({ locale, value }) =>
|
|
193
|
+
filterFn(locale, value),
|
|
194
|
+
);
|
|
131
195
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
filterFn(locale, value),
|
|
135
|
-
),
|
|
136
|
-
);
|
|
196
|
+
if (isLocalized(field)) {
|
|
197
|
+
return possiblyLocalizedEntriesToFieldValue(field, filteredEntries);
|
|
137
198
|
}
|
|
138
199
|
|
|
139
|
-
return
|
|
200
|
+
return filteredEntries.length > 0 ? filteredEntries[0]?.value : undefined;
|
|
140
201
|
}
|
|
141
202
|
|
|
142
203
|
/**
|
|
@@ -157,27 +218,24 @@ export async function filterLocalizedFieldValuesAsync(
|
|
|
157
218
|
localeValue: unknown,
|
|
158
219
|
) => Promise<boolean>,
|
|
159
220
|
) {
|
|
160
|
-
|
|
161
|
-
|
|
221
|
+
const entries = fieldValueToPossiblyLocalizedEntries(field, value);
|
|
222
|
+
const results = await Promise.all(
|
|
223
|
+
entries.map(async ({ locale, value }) => ({
|
|
224
|
+
locale,
|
|
225
|
+
value,
|
|
226
|
+
passed: await filterFn(locale, value),
|
|
227
|
+
})),
|
|
228
|
+
);
|
|
162
229
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
locale,
|
|
167
|
-
value,
|
|
168
|
-
await filterFn(locale, value),
|
|
169
|
-
],
|
|
170
|
-
),
|
|
171
|
-
);
|
|
230
|
+
const filteredEntries = results
|
|
231
|
+
.filter(({ passed }) => passed)
|
|
232
|
+
.map(({ locale, value }) => ({ locale, value }));
|
|
172
233
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.filter(([, , passed]) => passed)
|
|
176
|
-
.map(([locale, value]) => [locale, value]),
|
|
177
|
-
);
|
|
234
|
+
if (isLocalized(field)) {
|
|
235
|
+
return possiblyLocalizedEntriesToFieldValue(field, filteredEntries);
|
|
178
236
|
}
|
|
179
237
|
|
|
180
|
-
return
|
|
238
|
+
return filteredEntries.length > 0 ? filteredEntries[0]?.value : undefined;
|
|
181
239
|
}
|
|
182
240
|
|
|
183
241
|
/**
|
|
@@ -195,15 +253,8 @@ export function someLocalizedFieldValues(
|
|
|
195
253
|
value: unknown,
|
|
196
254
|
testFn: (locale: string | undefined, localeValue: unknown) => boolean,
|
|
197
255
|
): boolean {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return Object.entries(localizedValue).some(([locale, value]) =>
|
|
202
|
-
testFn(locale, value),
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return testFn(undefined, value);
|
|
256
|
+
const entries = fieldValueToPossiblyLocalizedEntries(field, value);
|
|
257
|
+
return entries.some(({ locale, value }) => testFn(locale, value));
|
|
207
258
|
}
|
|
208
259
|
|
|
209
260
|
/**
|
|
@@ -224,19 +275,11 @@ export async function someLocalizedFieldValuesAsync(
|
|
|
224
275
|
localeValue: unknown,
|
|
225
276
|
) => Promise<boolean>,
|
|
226
277
|
): Promise<boolean> {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
async ([locale, value]) => await testFn(locale, value),
|
|
233
|
-
),
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
return results.some((result) => result);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return await testFn(undefined, value);
|
|
278
|
+
const entries = fieldValueToPossiblyLocalizedEntries(field, value);
|
|
279
|
+
const results = await Promise.all(
|
|
280
|
+
entries.map(({ locale, value }) => testFn(locale, value)),
|
|
281
|
+
);
|
|
282
|
+
return results.some((result) => result);
|
|
240
283
|
}
|
|
241
284
|
|
|
242
285
|
/**
|
|
@@ -300,14 +343,9 @@ export function visitLocalizedFieldValues(
|
|
|
300
343
|
value: unknown,
|
|
301
344
|
visitFn: (locale: string | undefined, localeValue: unknown) => void,
|
|
302
345
|
): void {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
for (const [locale, value] of Object.entries(localizedValue)) {
|
|
307
|
-
visitFn(locale, value);
|
|
308
|
-
}
|
|
309
|
-
} else {
|
|
310
|
-
visitFn(undefined, value);
|
|
346
|
+
const entries = fieldValueToPossiblyLocalizedEntries(field, value);
|
|
347
|
+
for (const { locale, value } of entries) {
|
|
348
|
+
visitFn(locale, value);
|
|
311
349
|
}
|
|
312
350
|
}
|
|
313
351
|
|
|
@@ -325,15 +363,6 @@ export async function visitLocalizedFieldValuesAsync(
|
|
|
325
363
|
value: unknown,
|
|
326
364
|
visitFn: (locale: string | undefined, localeValue: unknown) => Promise<void>,
|
|
327
365
|
): Promise<void> {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
await Promise.all(
|
|
332
|
-
Object.entries(localizedValue).map(
|
|
333
|
-
async ([locale, value]) => await visitFn(locale, value),
|
|
334
|
-
),
|
|
335
|
-
);
|
|
336
|
-
} else {
|
|
337
|
-
await visitFn(undefined, value);
|
|
338
|
-
}
|
|
366
|
+
const entries = fieldValueToPossiblyLocalizedEntries(field, value);
|
|
367
|
+
await Promise.all(entries.map(({ locale, value }) => visitFn(locale, value)));
|
|
339
368
|
}
|
|
@@ -76,7 +76,7 @@ function hasChildren(node: unknown): node is { children: readonly unknown[] } {
|
|
|
76
76
|
* @param node - The root node to start visiting from
|
|
77
77
|
* @param visitor - Synchronous function called for each node. Receives the node, its parent, and path through the Structured Text tree
|
|
78
78
|
*/
|
|
79
|
-
export function
|
|
79
|
+
export function forEachNode<T>(
|
|
80
80
|
node: T,
|
|
81
81
|
visitor: NodePredicate<T, void>,
|
|
82
82
|
parent: AllNodesInTree<T> | null = null,
|
|
@@ -89,7 +89,7 @@ export function visitNodes<T>(
|
|
|
89
89
|
if (hasChildren(node)) {
|
|
90
90
|
for (let index = 0; index < node.children.length; index++) {
|
|
91
91
|
const child = node.children[index];
|
|
92
|
-
|
|
92
|
+
forEachNode(child as T, visitor, node as AllNodesInTree<T>, [
|
|
93
93
|
...path,
|
|
94
94
|
'children',
|
|
95
95
|
index,
|
|
@@ -107,7 +107,7 @@ export function visitNodes<T>(
|
|
|
107
107
|
* @param visitor - Asynchronous function called for each node. Receives the node, its parent, and path through the Structured Text tree
|
|
108
108
|
* @returns Promise that resolves when all nodes have been visited
|
|
109
109
|
*/
|
|
110
|
-
export async function
|
|
110
|
+
export async function forEachNodeAsync<T>(
|
|
111
111
|
node: T,
|
|
112
112
|
visitor: NodePredicate<T, Promise<void>>,
|
|
113
113
|
parent: AllNodesInTree<T> | null = null,
|
|
@@ -120,7 +120,7 @@ export async function visitNodesAsync<T>(
|
|
|
120
120
|
if (hasChildren(node)) {
|
|
121
121
|
for (let index = 0; index < node.children.length; index++) {
|
|
122
122
|
const child = node.children[index];
|
|
123
|
-
await
|
|
123
|
+
await forEachNodeAsync(child as T, visitor, node as AllNodesInTree<T>, [
|
|
124
124
|
...path,
|
|
125
125
|
'children',
|
|
126
126
|
index,
|
|
@@ -232,7 +232,7 @@ export function findAllNodes<T>(
|
|
|
232
232
|
): Array<{ node: AllNodesInTree<T>; path: TreePath }> {
|
|
233
233
|
const results: Array<{ node: AllNodesInTree<T>; path: TreePath }> = [];
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
forEachNode(
|
|
236
236
|
node,
|
|
237
237
|
(currentNode, currentParent, currentPath) => {
|
|
238
238
|
if (predicate(currentNode, currentParent, currentPath)) {
|
|
@@ -263,7 +263,7 @@ export async function findAllNodesAsync<T>(
|
|
|
263
263
|
): Promise<Array<{ node: AllNodesInTree<T>; path: TreePath }>> {
|
|
264
264
|
const results: Array<{ node: AllNodesInTree<T>; path: TreePath }> = [];
|
|
265
265
|
|
|
266
|
-
await
|
|
266
|
+
await forEachNodeAsync(
|
|
267
267
|
node,
|
|
268
268
|
async (currentNode, currentParent, currentPath) => {
|
|
269
269
|
if (await predicate(currentNode, currentParent, currentPath)) {
|
|
@@ -393,7 +393,7 @@ export function reduceNodes<T, R>(
|
|
|
393
393
|
): R {
|
|
394
394
|
let accumulator = initialValue;
|
|
395
395
|
|
|
396
|
-
|
|
396
|
+
forEachNode(
|
|
397
397
|
node,
|
|
398
398
|
(currentNode, currentParent, currentPath) => {
|
|
399
399
|
accumulator = reducer(
|
|
@@ -436,7 +436,7 @@ export async function reduceNodesAsync<T, R>(
|
|
|
436
436
|
): Promise<R> {
|
|
437
437
|
let accumulator = initialValue;
|
|
438
438
|
|
|
439
|
-
await
|
|
439
|
+
await forEachNodeAsync(
|
|
440
440
|
node,
|
|
441
441
|
async (currentNode, currentParent, currentPath) => {
|
|
442
442
|
accumulator = await reducer(
|