@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.
Files changed (46) hide show
  1. package/dist/cjs/fieldTypes/rich_text.js +3 -3
  2. package/dist/cjs/fieldTypes/rich_text.js.map +1 -1
  3. package/dist/cjs/fieldTypes/single_block.js +3 -3
  4. package/dist/cjs/fieldTypes/single_block.js.map +1 -1
  5. package/dist/cjs/fieldTypes/structured_text.js +3 -3
  6. package/dist/cjs/fieldTypes/structured_text.js.map +1 -1
  7. package/dist/cjs/generated/Client.js +1 -1
  8. package/dist/cjs/utilities/fieldValueLocalization.js +90 -55
  9. package/dist/cjs/utilities/fieldValueLocalization.js.map +1 -1
  10. package/dist/cjs/utilities/structuredText.js +11 -11
  11. package/dist/cjs/utilities/structuredText.js.map +1 -1
  12. package/dist/esm/fieldTypes/rich_text.d.ts +2 -2
  13. package/dist/esm/fieldTypes/rich_text.js +1 -1
  14. package/dist/esm/fieldTypes/rich_text.js.map +1 -1
  15. package/dist/esm/fieldTypes/single_block.d.ts +2 -2
  16. package/dist/esm/fieldTypes/single_block.js +1 -1
  17. package/dist/esm/fieldTypes/single_block.js.map +1 -1
  18. package/dist/esm/fieldTypes/structured_text.d.ts +8 -8
  19. package/dist/esm/fieldTypes/structured_text.js +1 -1
  20. package/dist/esm/fieldTypes/structured_text.js.map +1 -1
  21. package/dist/esm/generated/Client.js +1 -1
  22. package/dist/esm/generated/SchemaTypes.d.ts +11 -6
  23. package/dist/esm/generated/SimpleSchemaTypes.d.ts +11 -6
  24. package/dist/esm/utilities/fieldValueLocalization.d.ts +51 -9
  25. package/dist/esm/utilities/fieldValueLocalization.js +87 -54
  26. package/dist/esm/utilities/fieldValueLocalization.js.map +1 -1
  27. package/dist/esm/utilities/structuredText.d.ts +2 -2
  28. package/dist/esm/utilities/structuredText.js +8 -8
  29. package/dist/esm/utilities/structuredText.js.map +1 -1
  30. package/dist/types/fieldTypes/rich_text.d.ts +2 -2
  31. package/dist/types/fieldTypes/single_block.d.ts +2 -2
  32. package/dist/types/fieldTypes/structured_text.d.ts +8 -8
  33. package/dist/types/generated/SchemaTypes.d.ts +11 -6
  34. package/dist/types/generated/SimpleSchemaTypes.d.ts +11 -6
  35. package/dist/types/utilities/fieldValueLocalization.d.ts +51 -9
  36. package/dist/types/utilities/structuredText.d.ts +2 -2
  37. package/package.json +4 -4
  38. package/src/fieldTypes/rich_text.ts +3 -3
  39. package/src/fieldTypes/single_block.ts +3 -3
  40. package/src/fieldTypes/structured_text.ts +13 -13
  41. package/src/generated/Client.ts +1 -1
  42. package/src/generated/SchemaTypes.ts +42 -7
  43. package/src/generated/SimpleSchemaTypes.ts +42 -7
  44. package/src/utilities/blocks.ts +6 -6
  45. package/src/utilities/fieldValueLocalization.ts +122 -93
  46. 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
- * In DatoCMS, fields can be localized. In this scenario, their value contains values
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
- if (isLocalized(field)) {
70
- const localizedValue = value as LocalizedFieldValue;
71
-
72
- return Object.fromEntries(
73
- Object.entries(localizedValue).map(([locale, value]) => [
74
- locale,
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
- if (isLocalized(field)) {
100
- const localizedValue = value as LocalizedFieldValue;
101
-
102
- return Object.fromEntries(
103
- await Promise.all(
104
- Object.entries(localizedValue).map<Promise<[string, unknown]>>(
105
- async ([locale, value]) => [locale, await mapFn(locale, value)],
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
- if (isLocalized(field)) {
130
- const localizedValue = value as LocalizedFieldValue;
191
+ const entries = fieldValueToPossiblyLocalizedEntries(field, value);
192
+ const filteredEntries = entries.filter(({ locale, value }) =>
193
+ filterFn(locale, value),
194
+ );
131
195
 
132
- return Object.fromEntries(
133
- Object.entries(localizedValue).filter(([locale, value]) =>
134
- filterFn(locale, value),
135
- ),
136
- );
196
+ if (isLocalized(field)) {
197
+ return possiblyLocalizedEntriesToFieldValue(field, filteredEntries);
137
198
  }
138
199
 
139
- return filterFn(undefined, value) ? value : undefined;
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
- if (isLocalized(field)) {
161
- const localizedValue = value as LocalizedFieldValue;
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
- const results = await Promise.all(
164
- Object.entries(localizedValue).map<Promise<[string, unknown, boolean]>>(
165
- async ([locale, value]) => [
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
- return Object.fromEntries(
174
- results
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 (await filterFn(undefined, value)) ? value : undefined;
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
- if (isLocalized(field)) {
199
- const localizedValue = value as LocalizedFieldValue;
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
- if (isLocalized(field)) {
228
- const localizedValue = value as LocalizedFieldValue;
229
-
230
- const results = await Promise.all(
231
- Object.entries(localizedValue).map(
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
- if (isLocalized(field)) {
304
- const localizedValue = value as LocalizedFieldValue;
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
- if (isLocalized(field)) {
329
- const localizedValue = value as LocalizedFieldValue;
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 visitNodes<T>(
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
- visitNodes(child as T, visitor, node as AllNodesInTree<T>, [
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 visitNodesAsync<T>(
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 visitNodesAsync(child as T, visitor, node as AllNodesInTree<T>, [
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
- visitNodes(
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 visitNodesAsync(
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
- visitNodes(
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 visitNodesAsync(
439
+ await forEachNodeAsync(
440
440
  node,
441
441
  async (currentNode, currentParent, currentPath) => {
442
442
  accumulator = await reducer(