@datocms/cma-client 5.1.5 → 5.1.7

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 (49) hide show
  1. package/dist/cjs/fieldTypes/gallery.js +0 -2
  2. package/dist/cjs/fieldTypes/gallery.js.map +1 -1
  3. package/dist/cjs/fieldTypes/links.js +0 -2
  4. package/dist/cjs/fieldTypes/links.js.map +1 -1
  5. package/dist/cjs/fieldTypes/rich_text.js +0 -4
  6. package/dist/cjs/fieldTypes/rich_text.js.map +1 -1
  7. package/dist/cjs/generated/Client.js +1 -1
  8. package/dist/cjs/generated/resources/Item.js +2 -2
  9. package/dist/cjs/generated/resources/Item.js.map +1 -1
  10. package/dist/cjs/utilities/fieldValue.js +14 -9
  11. package/dist/cjs/utilities/fieldValue.js.map +1 -1
  12. package/dist/cjs/utilities/schemaRepository.js +64 -0
  13. package/dist/cjs/utilities/schemaRepository.js.map +1 -1
  14. package/dist/esm/fieldTypes/gallery.js +0 -2
  15. package/dist/esm/fieldTypes/gallery.js.map +1 -1
  16. package/dist/esm/fieldTypes/links.d.ts +1 -1
  17. package/dist/esm/fieldTypes/links.js +0 -2
  18. package/dist/esm/fieldTypes/links.js.map +1 -1
  19. package/dist/esm/fieldTypes/rich_text.js +0 -4
  20. package/dist/esm/fieldTypes/rich_text.js.map +1 -1
  21. package/dist/esm/generated/ApiTypes.d.ts +743 -5
  22. package/dist/esm/generated/Client.js +1 -1
  23. package/dist/esm/generated/RawApiTypes.d.ts +743 -5
  24. package/dist/esm/generated/resources/Item.js +2 -2
  25. package/dist/esm/generated/resources/Item.js.map +1 -1
  26. package/dist/esm/utilities/fieldValue.d.ts +18 -19
  27. package/dist/esm/utilities/fieldValue.js +14 -9
  28. package/dist/esm/utilities/fieldValue.js.map +1 -1
  29. package/dist/esm/utilities/itemDefinition.d.ts +27 -14
  30. package/dist/esm/utilities/schemaRepository.d.ts +64 -0
  31. package/dist/esm/utilities/schemaRepository.js +64 -0
  32. package/dist/esm/utilities/schemaRepository.js.map +1 -1
  33. package/dist/types/fieldTypes/links.d.ts +1 -1
  34. package/dist/types/generated/ApiTypes.d.ts +743 -5
  35. package/dist/types/generated/RawApiTypes.d.ts +743 -5
  36. package/dist/types/utilities/fieldValue.d.ts +18 -19
  37. package/dist/types/utilities/itemDefinition.d.ts +27 -14
  38. package/dist/types/utilities/schemaRepository.d.ts +64 -0
  39. package/package.json +4 -4
  40. package/src/fieldTypes/gallery.ts +0 -1
  41. package/src/fieldTypes/links.ts +1 -2
  42. package/src/fieldTypes/rich_text.ts +0 -4
  43. package/src/generated/ApiTypes.ts +743 -5
  44. package/src/generated/Client.ts +1 -1
  45. package/src/generated/RawApiTypes.ts +743 -5
  46. package/src/generated/resources/Item.ts +2 -2
  47. package/src/utilities/fieldValue.ts +96 -79
  48. package/src/utilities/itemDefinition.ts +99 -72
  49. package/src/utilities/schemaRepository.ts +64 -0
@@ -702,7 +702,7 @@ export default class Item extends BaseResource {
702
702
  .request({
703
703
  method: 'PUT',
704
704
  url: `/items/${itemId}/publish`,
705
- body: Utils.serializeRawRequestBodyWithItems(body),
705
+ body: body ? Utils.serializeRawRequestBodyWithItems(body) : undefined,
706
706
  queryParams,
707
707
  })
708
708
  .then<
@@ -765,7 +765,7 @@ export default class Item extends BaseResource {
765
765
  .request({
766
766
  method: 'PUT',
767
767
  url: `/items/${itemId}/unpublish`,
768
- body: Utils.serializeRawRequestBodyWithItems(body),
768
+ body: body ? Utils.serializeRawRequestBodyWithItems(body) : undefined,
769
769
  queryParams,
770
770
  })
771
771
  .then<
@@ -27,7 +27,10 @@ import type * as RawApiTypes from '../generated/RawApiTypes';
27
27
  * for various locales structured as an object, such as
28
28
  * `{ "en": "Hello", "it": "Ciao" }`
29
29
  */
30
- export type LocalizedFieldValue<T = unknown> = Record<string, T>;
30
+ export type LocalizedFieldValue<
31
+ T = unknown,
32
+ L extends string = string,
33
+ > = Partial<Record<L, T>>;
31
34
 
32
35
  /**
33
36
  * Determines whether a DatoCMS field is localized or not.
@@ -52,13 +55,11 @@ export function isLocalized(
52
55
  *
53
56
  * This uniform structure allows the same processing logic to work with both field types.
54
57
  */
55
- export type FieldValueEntry = {
56
- locale: string | undefined;
57
- value: unknown;
58
+ export type FieldValueEntry<T = unknown, L extends string = string> = {
59
+ locale: L | undefined;
60
+ value: T;
58
61
  };
59
62
 
60
- export type PossiblyLocalizedEntry = FieldValueEntry;
61
-
62
63
  /**
63
64
  * Converts a field value (localized or non-localized) into a uniform array of entries.
64
65
  *
@@ -69,43 +70,48 @@ export type PossiblyLocalizedEntry = FieldValueEntry;
69
70
  * @param value - The field value to convert (either a localized object or direct value)
70
71
  * @returns Array of entries where each entry contains a locale (string for localized, undefined for non-localized) and the corresponding value
71
72
  */
72
- export function fieldValueToEntries(
73
+ export function fieldValueToEntries<T = unknown, L extends string = string>(
73
74
  field: RawApiTypes.Field | ApiTypes.Field,
74
- value: unknown,
75
- ) {
75
+ value: T | LocalizedFieldValue<T, L>,
76
+ ): FieldValueEntry<T, L>[] {
76
77
  if (isLocalized(field)) {
77
- const localizedValue = value as LocalizedFieldValue;
78
+ const localizedValue = value as LocalizedFieldValue<T, L>;
78
79
 
79
- return Object.entries(localizedValue).map<FieldValueEntry>(
80
- ([locale, value]) => ({ locale, value }),
81
- );
80
+ return Object.entries(localizedValue).map(([locale, value]) => ({
81
+ locale: locale as L,
82
+ value: value as T,
83
+ }));
82
84
  }
83
85
 
84
- return [{ locale: undefined, value }];
86
+ return [{ locale: undefined, value: value as T }];
85
87
  }
86
88
 
87
89
  /**
88
90
  * Converts an array of possibly localized entries back into the appropriate field value format.
89
91
  *
90
- * This function is the inverse of `fieldValueToPossiblyLocalizedEntries`. It takes a uniform
92
+ * This function is the inverse of `fieldValueToEntries`. It takes a uniform
91
93
  * array of entries and converts them back to either a localized object or a direct value,
92
94
  * depending on the field's localization setting.
93
95
  *
94
96
  * @param field - The DatoCMS field definition that determines the output format
95
- * @param possiblyLocalizedEntries - Array of entries to convert back to field value format
97
+ * @param entries - Array of entries to convert back to field value format
96
98
  * @returns Either a localized object (for localized fields) or the direct value (for non-localized fields)
97
99
  */
98
- export function entriesToFieldValue(
100
+ export function entriesToFieldValue<T = unknown, L extends string = string>(
99
101
  field: RawApiTypes.Field | ApiTypes.Field,
100
- possiblyLocalizedEntries: FieldValueEntry[],
101
- ) {
102
+ entries: FieldValueEntry<T, L>[],
103
+ ): T | LocalizedFieldValue<T, L> {
102
104
  if (isLocalized(field)) {
103
105
  return Object.fromEntries(
104
- possiblyLocalizedEntries.map(({ locale, value }) => [locale, value]),
105
- );
106
+ entries.map(({ locale, value }) => [locale, value]),
107
+ ) as LocalizedFieldValue<T, L>;
108
+ }
109
+
110
+ if (entries.length === 0) {
111
+ throw new Error('There must be at least one entry!');
106
112
  }
107
113
 
108
- return possiblyLocalizedEntries[0]?.value;
114
+ return entries[0]!.value;
109
115
  }
110
116
 
111
117
  /**
@@ -119,17 +125,21 @@ export function entriesToFieldValue(
119
125
  * @param mapFn - The function to apply to each locale value or the direct value
120
126
  * @returns The mapped value with the same structure as the input
121
127
  */
122
- export function mapFieldValue<T>(
128
+ export function mapFieldValue<
129
+ TInput = unknown,
130
+ TOutput = unknown,
131
+ L extends string = string,
132
+ >(
123
133
  field: RawApiTypes.Field | ApiTypes.Field,
124
- value: unknown,
125
- mapFn: (locale: string | undefined, localeValue: unknown) => T,
126
- ) {
127
- const entries = fieldValueToEntries(field, value);
134
+ value: TInput | LocalizedFieldValue<TInput, L>,
135
+ mapFn: (locale: L | undefined, localeValue: TInput) => TOutput,
136
+ ): TOutput | LocalizedFieldValue<TOutput, L> {
137
+ const entries = fieldValueToEntries<TInput, L>(field, value);
128
138
  const mappedEntries = entries.map(({ locale, value }) => ({
129
139
  locale,
130
140
  value: mapFn(locale, value),
131
141
  }));
132
- return entriesToFieldValue(field, mappedEntries);
142
+ return entriesToFieldValue<TOutput, L>(field, mappedEntries);
133
143
  }
134
144
 
135
145
  /**
@@ -143,19 +153,23 @@ export function mapFieldValue<T>(
143
153
  * @param mapFn - The function to apply to each locale value or the direct value
144
154
  * @returns The mapped value with the same structure as the input
145
155
  */
146
- export async function mapFieldValueAsync<T>(
156
+ export async function mapFieldValueAsync<
157
+ TInput = unknown,
158
+ TOutput = unknown,
159
+ L extends string = string,
160
+ >(
147
161
  field: RawApiTypes.Field | ApiTypes.Field,
148
- value: unknown,
149
- mapFn: (locale: string | undefined, localeValue: unknown) => Promise<T>,
150
- ) {
151
- const entries = fieldValueToEntries(field, value);
162
+ value: TInput | LocalizedFieldValue<TInput, L>,
163
+ mapFn: (locale: L | undefined, localeValue: TInput) => Promise<TOutput>,
164
+ ): Promise<TOutput | LocalizedFieldValue<TOutput, L>> {
165
+ const entries = fieldValueToEntries<TInput, L>(field, value);
152
166
  const mappedEntries = await Promise.all(
153
167
  entries.map(async ({ locale, value }) => ({
154
168
  locale,
155
169
  value: await mapFn(locale, value),
156
170
  })),
157
171
  );
158
- return entriesToFieldValue(field, mappedEntries);
172
+ return entriesToFieldValue<TOutput, L>(field, mappedEntries);
159
173
  }
160
174
 
161
175
  /**
@@ -168,18 +182,18 @@ export async function mapFieldValueAsync<T>(
168
182
  * @param filterFn - The function to test each locale value or the direct value
169
183
  * @returns The filtered value with the same structure as the input
170
184
  */
171
- export function filterFieldValue(
185
+ export function filterFieldValue<T = unknown, L extends string = string>(
172
186
  field: RawApiTypes.Field | ApiTypes.Field,
173
- value: unknown,
174
- filterFn: (locale: string | undefined, localeValue: unknown) => boolean,
175
- ) {
176
- const entries = fieldValueToEntries(field, value);
177
- const filteredEntries = entries.filter(({ locale, value }) =>
178
- filterFn(locale, value),
187
+ value: T | LocalizedFieldValue<T, L>,
188
+ filterFn: (locale: L | undefined, localeValue: T) => boolean,
189
+ ): T | LocalizedFieldValue<T, L> | undefined {
190
+ const entries = fieldValueToEntries<T, L>(field, value);
191
+ const filteredEntries = entries.filter((entry) =>
192
+ filterFn(entry.locale, entry.value),
179
193
  );
180
194
 
181
195
  if (isLocalized(field)) {
182
- return entriesToFieldValue(field, filteredEntries);
196
+ return entriesToFieldValue<T, L>(field, filteredEntries);
183
197
  }
184
198
 
185
199
  return filteredEntries.length > 0 ? filteredEntries[0]?.value : undefined;
@@ -195,15 +209,15 @@ export function filterFieldValue(
195
209
  * @param filterFn - The function to test each locale value or the direct value
196
210
  * @returns The filtered value with the same structure as the input
197
211
  */
198
- export async function filterFieldValueAsync(
212
+ export async function filterFieldValueAsync<
213
+ T = unknown,
214
+ L extends string = string,
215
+ >(
199
216
  field: RawApiTypes.Field | ApiTypes.Field,
200
- value: unknown,
201
- filterFn: (
202
- locale: string | undefined,
203
- localeValue: unknown,
204
- ) => Promise<boolean>,
205
- ) {
206
- const entries = fieldValueToEntries(field, value);
217
+ value: T | LocalizedFieldValue<T, L>,
218
+ filterFn: (locale: L | undefined, localeValue: T) => Promise<boolean>,
219
+ ): Promise<T | LocalizedFieldValue<T, L> | undefined> {
220
+ const entries = fieldValueToEntries<T, L>(field, value);
207
221
  const results = await Promise.all(
208
222
  entries.map(async ({ locale, value }) => ({
209
223
  locale,
@@ -217,7 +231,7 @@ export async function filterFieldValueAsync(
217
231
  .map(({ locale, value }) => ({ locale, value }));
218
232
 
219
233
  if (isLocalized(field)) {
220
- return entriesToFieldValue(field, filteredEntries);
234
+ return entriesToFieldValue<T, L>(field, filteredEntries);
221
235
  }
222
236
 
223
237
  return filteredEntries.length > 0 ? filteredEntries[0]?.value : undefined;
@@ -233,12 +247,12 @@ export async function filterFieldValueAsync(
233
247
  * @param testFn - The function to test each locale value or the direct value
234
248
  * @returns true if at least one value passes the test, false otherwise
235
249
  */
236
- export function someFieldValue(
250
+ export function someFieldValue<T = unknown, L extends string = string>(
237
251
  field: RawApiTypes.Field | ApiTypes.Field,
238
- value: unknown,
239
- testFn: (locale: string | undefined, localeValue: unknown) => boolean,
252
+ value: T | LocalizedFieldValue<T, L>,
253
+ testFn: (locale: L | undefined, localeValue: T) => boolean,
240
254
  ): boolean {
241
- const entries = fieldValueToEntries(field, value);
255
+ const entries = fieldValueToEntries<T, L>(field, value);
242
256
  return entries.some(({ locale, value }) => testFn(locale, value));
243
257
  }
244
258
 
@@ -252,15 +266,15 @@ export function someFieldValue(
252
266
  * @param testFn - The function to test each locale value or the direct value
253
267
  * @returns true if at least one value passes the test, false otherwise
254
268
  */
255
- export async function someFieldValueAsync(
269
+ export async function someFieldValueAsync<
270
+ T = unknown,
271
+ L extends string = string,
272
+ >(
256
273
  field: RawApiTypes.Field | ApiTypes.Field,
257
- value: unknown,
258
- testFn: (
259
- locale: string | undefined,
260
- localeValue: unknown,
261
- ) => Promise<boolean>,
274
+ value: T | LocalizedFieldValue<T, L>,
275
+ testFn: (locale: L | undefined, localeValue: T) => Promise<boolean>,
262
276
  ): Promise<boolean> {
263
- const entries = fieldValueToEntries(field, value);
277
+ const entries = fieldValueToEntries<T, L>(field, value);
264
278
  const results = await Promise.all(
265
279
  entries.map(({ locale, value }) => testFn(locale, value)),
266
280
  );
@@ -277,10 +291,10 @@ export async function someFieldValueAsync(
277
291
  * @param testFn - The function to test each locale value or the direct value
278
292
  * @returns true if all values pass the test, false otherwise
279
293
  */
280
- export function everyFieldValue(
294
+ export function everyFieldValue<T = unknown, L extends string = string>(
281
295
  field: RawApiTypes.Field | ApiTypes.Field,
282
- value: unknown,
283
- testFn: (locale: string | undefined, localeValue: unknown) => boolean,
296
+ value: T | LocalizedFieldValue<T, L>,
297
+ testFn: (locale: L | undefined, localeValue: T) => boolean,
284
298
  ): boolean {
285
299
  return !someFieldValue(
286
300
  field,
@@ -299,13 +313,13 @@ export function everyFieldValue(
299
313
  * @param testFn - The function to test each locale value or the direct value
300
314
  * @returns true if all values pass the test, false otherwise
301
315
  */
302
- export async function everyFieldValueAsync(
316
+ export async function everyFieldValueAsync<
317
+ T = unknown,
318
+ L extends string = string,
319
+ >(
303
320
  field: RawApiTypes.Field | ApiTypes.Field,
304
- value: unknown,
305
- testFn: (
306
- locale: string | undefined,
307
- localeValue: unknown,
308
- ) => Promise<boolean>,
321
+ value: T | LocalizedFieldValue<T, L>,
322
+ testFn: (locale: L | undefined, localeValue: T) => Promise<boolean>,
309
323
  ): Promise<boolean> {
310
324
  return !(await someFieldValueAsync(
311
325
  field,
@@ -323,12 +337,12 @@ export async function everyFieldValueAsync(
323
337
  * @param value - The field value (either localized object or direct value)
324
338
  * @param visitFn - The function to call for each locale value or the direct value
325
339
  */
326
- export function visitFieldValue(
340
+ export function visitFieldValue<T = unknown, L extends string = string>(
327
341
  field: RawApiTypes.Field | ApiTypes.Field,
328
- value: unknown,
329
- visitFn: (locale: string | undefined, localeValue: unknown) => void,
342
+ value: T | LocalizedFieldValue<T, L>,
343
+ visitFn: (locale: L | undefined, localeValue: T) => void,
330
344
  ): void {
331
- const entries = fieldValueToEntries(field, value);
345
+ const entries = fieldValueToEntries<T, L>(field, value);
332
346
  for (const { locale, value } of entries) {
333
347
  visitFn(locale, value);
334
348
  }
@@ -343,11 +357,14 @@ export function visitFieldValue(
343
357
  * @param value - The field value (either localized object or direct value)
344
358
  * @param visitFn - The function to call for each locale value or the direct value
345
359
  */
346
- export async function visitFieldValueAsync(
360
+ export async function visitFieldValueAsync<
361
+ T = unknown,
362
+ L extends string = string,
363
+ >(
347
364
  field: RawApiTypes.Field | ApiTypes.Field,
348
- value: unknown,
349
- visitFn: (locale: string | undefined, localeValue: unknown) => Promise<void>,
365
+ value: T | LocalizedFieldValue<T, L>,
366
+ visitFn: (locale: L | undefined, localeValue: T) => Promise<void>,
350
367
  ): Promise<void> {
351
- const entries = fieldValueToEntries(field, value);
368
+ const entries = fieldValueToEntries<T, L>(field, value);
352
369
  await Promise.all(entries.map(({ locale, value }) => visitFn(locale, value)));
353
370
  }
@@ -44,8 +44,8 @@ type StructuredTextFieldDefinition<
44
44
  AllowedBlocks extends ItemTypeDefinition = ItemTypeDefinition,
45
45
  AllowedInlineBlocks extends ItemTypeDefinition = ItemTypeDefinition,
46
46
  > = BaseFieldDefinition<'structured_text'> & {
47
- blocks: AllowedBlocks;
48
- inline_blocks: AllowedInlineBlocks;
47
+ blocks?: AllowedBlocks;
48
+ inline_blocks?: AllowedInlineBlocks;
49
49
  };
50
50
 
51
51
  /** Field definition union */
@@ -76,9 +76,11 @@ export type FieldDefinition =
76
76
 
77
77
  /** Item type definition */
78
78
  export type ItemTypeDefinition<
79
+ Settings extends { locales: string } = { locales: string },
79
80
  ItemTypeId extends string = string,
80
81
  FieldDefinitions extends Record<string, FieldDefinition> = {},
81
82
  > = {
83
+ settings: Settings;
82
84
  itemTypeId: ItemTypeId;
83
85
  fields: FieldDefinitions;
84
86
  };
@@ -118,109 +120,134 @@ type FieldTypeToValue = {
118
120
  };
119
121
 
120
122
  /** Localized wrapper */
121
- type LocalizeIfNeeded<T extends FieldDefinition, Value> = T extends {
122
- localized: true;
123
- }
124
- ? LocalizedFieldValue<Value>
125
- : Value;
123
+ type LocalizeIfNeeded<
124
+ T extends FieldDefinition,
125
+ Value,
126
+ Locales extends string,
127
+ > = T extends { localized: true } ? LocalizedFieldValue<Value, Locales> : Value;
126
128
 
127
129
  /** Standard mapping */
128
- type FieldDefinitionToFieldValue<T extends FieldDefinition> = LocalizeIfNeeded<
129
- T,
130
- FieldTypeToValue[T['type']]
131
- >;
130
+ type FieldDefinitionToFieldValue<
131
+ T extends FieldDefinition,
132
+ Locales extends string,
133
+ > = LocalizeIfNeeded<T, FieldTypeToValue[T['type']], Locales>;
132
134
 
133
135
  /** AsRequest mapping (block fields become generic over allowed blocks) */
134
- type FieldDefinitionToFieldValueAsRequest<T extends FieldDefinition> =
135
- T extends RichTextFieldDefinition<infer B>
136
+ type FieldDefinitionToFieldValueAsRequest<
137
+ T extends FieldDefinition,
138
+ Locales extends string,
139
+ > = T extends RichTextFieldDefinition<infer B>
140
+ ? LocalizeIfNeeded<
141
+ T,
142
+ RichTextFieldValueAsRequest<
143
+ ItemTypeDefinitionToItemDefinitionAsRequest<B>
144
+ >,
145
+ Locales
146
+ >
147
+ : T extends SingleBlockFieldDefinition<infer B>
136
148
  ? LocalizeIfNeeded<
137
149
  T,
138
- RichTextFieldValueAsRequest<
150
+ SingleBlockFieldValueAsRequest<
139
151
  ItemTypeDefinitionToItemDefinitionAsRequest<B>
140
- >
152
+ >,
153
+ Locales
141
154
  >
142
- : T extends SingleBlockFieldDefinition<infer B>
155
+ : T extends StructuredTextFieldDefinition<infer B, infer I>
143
156
  ? LocalizeIfNeeded<
144
157
  T,
145
- SingleBlockFieldValueAsRequest<
146
- ItemTypeDefinitionToItemDefinitionAsRequest<B>
147
- >
158
+ StructuredTextFieldValueAsRequest<
159
+ T extends { blocks: any }
160
+ ? ItemTypeDefinitionToItemDefinitionAsRequest<B>
161
+ : never,
162
+ T extends { inline_blocks: any }
163
+ ? ItemTypeDefinitionToItemDefinitionAsRequest<I>
164
+ : never
165
+ >,
166
+ Locales
148
167
  >
149
- : T extends StructuredTextFieldDefinition<infer B, infer I>
150
- ? LocalizeIfNeeded<
151
- T,
152
- StructuredTextFieldValueAsRequest<
153
- ItemTypeDefinitionToItemDefinitionAsRequest<B>,
154
- ItemTypeDefinitionToItemDefinitionAsRequest<I>
155
- >
156
- >
157
- : LocalizeIfNeeded<T, FieldTypeToValue[T['type']]>;
168
+ : LocalizeIfNeeded<T, FieldTypeToValue[T['type']], Locales>;
158
169
 
159
- type FieldDefinitionToFieldValueWithNestedBlocks<T extends FieldDefinition> =
160
- T extends RichTextFieldDefinition<infer B>
170
+ type FieldDefinitionToFieldValueWithNestedBlocks<
171
+ T extends FieldDefinition,
172
+ Locales extends string,
173
+ > = T extends RichTextFieldDefinition<infer B>
174
+ ? LocalizeIfNeeded<
175
+ T,
176
+ RichTextFieldValueWithNestedBlocks<
177
+ ItemTypeDefinitionToItemDefinitionWithNestedBlocks<B>
178
+ >,
179
+ Locales
180
+ >
181
+ : T extends SingleBlockFieldDefinition<infer B>
161
182
  ? LocalizeIfNeeded<
162
183
  T,
163
- RichTextFieldValueWithNestedBlocks<
184
+ SingleBlockFieldValueWithNestedBlocks<
164
185
  ItemTypeDefinitionToItemDefinitionWithNestedBlocks<B>
165
- >
186
+ >,
187
+ Locales
166
188
  >
167
- : T extends SingleBlockFieldDefinition<infer B>
189
+ : T extends StructuredTextFieldDefinition<infer B, infer I>
168
190
  ? LocalizeIfNeeded<
169
191
  T,
170
- SingleBlockFieldValueWithNestedBlocks<
171
- ItemTypeDefinitionToItemDefinitionWithNestedBlocks<B>
172
- >
192
+ StructuredTextFieldValueWithNestedBlocks<
193
+ T extends { blocks: any }
194
+ ? ItemTypeDefinitionToItemDefinitionWithNestedBlocks<B>
195
+ : never,
196
+ T extends { inline_blocks: any }
197
+ ? ItemTypeDefinitionToItemDefinitionWithNestedBlocks<I>
198
+ : never
199
+ >,
200
+ Locales
173
201
  >
174
- : T extends StructuredTextFieldDefinition<infer B, infer I>
175
- ? LocalizeIfNeeded<
176
- T,
177
- StructuredTextFieldValueWithNestedBlocks<
178
- ItemTypeDefinitionToItemDefinitionWithNestedBlocks<B>,
179
- ItemTypeDefinitionToItemDefinitionWithNestedBlocks<I>
180
- >
181
- >
182
- : LocalizeIfNeeded<T, FieldTypeToValue[T['type']]>;
202
+ : LocalizeIfNeeded<T, FieldTypeToValue[T['type']], Locales>;
183
203
 
184
204
  /** Transformers */
185
- export type ItemTypeDefinitionToItemDefinition<T extends ItemTypeDefinition> =
186
- T extends ItemTypeDefinition
187
- ? keyof T['fields'] extends never
188
- ? ItemDefinition<T['itemTypeId']>
189
- : ItemDefinition<
190
- T['itemTypeId'],
191
- {
192
- [K in keyof T['fields']]: T['fields'][K] extends FieldDefinition
193
- ? FieldDefinitionToFieldValue<T['fields'][K]>
194
- : never;
195
- }
196
- >
197
- : never;
205
+ export type ItemTypeDefinitionToItemDefinition<
206
+ T extends ItemTypeDefinition<any, any, any>,
207
+ > = T extends ItemTypeDefinition<infer Settings, infer ItemTypeId, infer Fields>
208
+ ? keyof Fields extends never
209
+ ? ItemDefinition<ItemTypeId>
210
+ : ItemDefinition<
211
+ ItemTypeId,
212
+ {
213
+ [K in keyof Fields]: Fields[K] extends FieldDefinition
214
+ ? FieldDefinitionToFieldValue<Fields[K], Settings['locales']>
215
+ : never;
216
+ }
217
+ >
218
+ : never;
198
219
 
199
220
  export type ItemTypeDefinitionToItemDefinitionAsRequest<
200
- T extends ItemTypeDefinition,
201
- > = T extends ItemTypeDefinition
202
- ? keyof T['fields'] extends never
203
- ? ItemDefinition<T['itemTypeId']>
221
+ T extends ItemTypeDefinition<any, any, any>,
222
+ > = T extends ItemTypeDefinition<infer Settings, infer ItemTypeId, infer Fields>
223
+ ? keyof Fields extends never
224
+ ? ItemDefinition<ItemTypeId>
204
225
  : ItemDefinition<
205
- T['itemTypeId'],
226
+ ItemTypeId,
206
227
  Partial<{
207
- [K in keyof T['fields']]: T['fields'][K] extends FieldDefinition
208
- ? FieldDefinitionToFieldValueAsRequest<T['fields'][K]>
228
+ [K in keyof Fields]: Fields[K] extends FieldDefinition
229
+ ? FieldDefinitionToFieldValueAsRequest<
230
+ Fields[K],
231
+ Settings['locales']
232
+ >
209
233
  : never;
210
234
  }>
211
235
  >
212
236
  : never;
213
237
 
214
238
  export type ItemTypeDefinitionToItemDefinitionWithNestedBlocks<
215
- T extends ItemTypeDefinition,
216
- > = T extends ItemTypeDefinition
217
- ? keyof T['fields'] extends never
218
- ? ItemDefinition<T['itemTypeId']>
239
+ T extends ItemTypeDefinition<any, any, any>,
240
+ > = T extends ItemTypeDefinition<infer Settings, infer ItemTypeId, infer Fields>
241
+ ? keyof Fields extends never
242
+ ? ItemDefinition<ItemTypeId>
219
243
  : ItemDefinition<
220
- T['itemTypeId'],
244
+ ItemTypeId,
221
245
  {
222
- [K in keyof T['fields']]: T['fields'][K] extends FieldDefinition
223
- ? FieldDefinitionToFieldValueWithNestedBlocks<T['fields'][K]>
246
+ [K in keyof Fields]: Fields[K] extends FieldDefinition
247
+ ? FieldDefinitionToFieldValueWithNestedBlocks<
248
+ Fields[K],
249
+ Settings['locales']
250
+ >
224
251
  : never;
225
252
  }
226
253
  >
@@ -21,6 +21,70 @@ interface GenericClient {
21
21
  /**
22
22
  * Repository for DatoCMS schema entities including item types, fields, and plugins.
23
23
  * Provides caching and efficient lookup functionality for schema-related operations.
24
+ *
25
+ * ## Purpose
26
+ *
27
+ * SchemaRepository is designed to solve the performance problem of repeatedly fetching
28
+ * the same schema information during complex operations that traverse nested blocks,
29
+ * structured text, or modular content. It acts as an in-memory cache/memoization layer
30
+ * for schema entities to avoid redundant API calls.
31
+ *
32
+ * ## What it's for:
33
+ *
34
+ * - **Caching schema entities**: Automatically caches item types, fields, fieldsets,
35
+ * and plugins after the first API request, returning cached results on subsequent calls
36
+ * - **Complex traversal operations**: Essential when using utilities like
37
+ * `mapBlocksInFieldValues()` that need to repeatedly lookup block models and fields
38
+ * while traversing nested content structures
39
+ * - **Bulk operations**: Ideal for scripts that process multiple records of different
40
+ * types and need efficient access to schema information
41
+ * - **Read-heavy workflows**: Perfect for scenarios where you need to repeatedly access
42
+ * the same schema information without making redundant API calls
43
+ *
44
+ * ## What it's NOT for:
45
+ *
46
+ * - **Schema modification**: Do NOT use SchemaRepository if your script modifies models,
47
+ * fields, fieldsets, or plugins, as the cache will become stale
48
+ * - **Record/content operations**: This is only for schema entities, not for records,
49
+ * uploads, or other content
50
+ * - **Long-running applications**: The cache has no expiration or invalidation mechanism,
51
+ * so it's not suitable for applications that need fresh schema data over time
52
+ * - **Concurrent schema changes**: No protection against cache inconsistency if other
53
+ * processes modify the schema while your script runs
54
+ *
55
+ * ## Usage Pattern
56
+ *
57
+ * ```typescript
58
+ * const schemaRepository = new SchemaRepository(client);
59
+ *
60
+ * // These calls will hit the API and cache the results
61
+ * const models = await schemaRepository.getAllModels();
62
+ * const blogPost = await schemaRepository.getItemTypeByApiKey('blog_post');
63
+ *
64
+ * // These subsequent calls will return cached results (no API calls)
65
+ * const sameModels = await schemaRepository.getAllModels();
66
+ * const sameBlogPost = await schemaRepository.getItemTypeByApiKey('blog_post');
67
+ *
68
+ * // Pass the repository to utilities that need schema information
69
+ * await mapBlocksInFieldValues(schemaRepository, record, (block) => {
70
+ * // The utility will use the cached schema data internally
71
+ * });
72
+ * ```
73
+ *
74
+ * ## Performance Benefits
75
+ *
76
+ * Without SchemaRepository, a script processing structured text with nested blocks
77
+ * might make the same `client.itemTypes.list()` or `client.fields.list()` calls
78
+ * dozens of times. SchemaRepository ensures each unique schema request is made only once.
79
+ *
80
+ * ## Best Practices
81
+ *
82
+ * - Use SchemaRepository consistently throughout your script — if you need it for one
83
+ * utility call, use it for all schema operations to maximize cache efficiency
84
+ * - Create one instance per script execution, not per operation
85
+ * - Only use when you have read-only access to schema entities
86
+ * - Consider using optimistic locking for any record updates to handle potential
87
+ * version conflicts when working with cached schema information
24
88
  */
25
89
  export class SchemaRepository {
26
90
  private client: GenericClient;