@datocms/cma-client 5.1.12 → 5.1.14

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 (36) hide show
  1. package/README.md +362 -167
  2. package/dist/cjs/generated/Client.js +1 -1
  3. package/dist/cjs/utilities/buildBlockRecord.js.map +1 -1
  4. package/dist/cjs/utilities/duplicateBlockRecord.js +2 -1
  5. package/dist/cjs/utilities/duplicateBlockRecord.js.map +1 -1
  6. package/dist/cjs/utilities/schemaRepository.js +107 -0
  7. package/dist/cjs/utilities/schemaRepository.js.map +1 -1
  8. package/dist/esm/fieldTypes/schema.d.ts +1 -1
  9. package/dist/esm/fieldTypes/single_block.d.ts +1 -1
  10. package/dist/esm/generated/ApiTypes.d.ts +4 -0
  11. package/dist/esm/generated/Client.js +1 -1
  12. package/dist/esm/generated/RawApiTypes.d.ts +4 -0
  13. package/dist/esm/generated/resources/Field.d.ts +100 -100
  14. package/dist/esm/utilities/buildBlockRecord.d.ts +10 -2
  15. package/dist/esm/utilities/buildBlockRecord.js.map +1 -1
  16. package/dist/esm/utilities/duplicateBlockRecord.js +2 -1
  17. package/dist/esm/utilities/duplicateBlockRecord.js.map +1 -1
  18. package/dist/esm/utilities/schemaRepository.d.ts +40 -0
  19. package/dist/esm/utilities/schemaRepository.js +107 -0
  20. package/dist/esm/utilities/schemaRepository.js.map +1 -1
  21. package/dist/types/fieldTypes/schema.d.ts +1 -1
  22. package/dist/types/fieldTypes/single_block.d.ts +1 -1
  23. package/dist/types/generated/ApiTypes.d.ts +4 -0
  24. package/dist/types/generated/RawApiTypes.d.ts +4 -0
  25. package/dist/types/generated/resources/Field.d.ts +100 -100
  26. package/dist/types/utilities/buildBlockRecord.d.ts +10 -2
  27. package/dist/types/utilities/schemaRepository.d.ts +40 -0
  28. package/package.json +4 -4
  29. package/src/fieldTypes/schema.ts +1 -1
  30. package/src/fieldTypes/single_block.ts +1 -1
  31. package/src/generated/ApiTypes.ts +4 -0
  32. package/src/generated/Client.ts +1 -1
  33. package/src/generated/RawApiTypes.ts +4 -0
  34. package/src/utilities/buildBlockRecord.ts +18 -2
  35. package/src/utilities/duplicateBlockRecord.ts +2 -1
  36. package/src/utilities/schemaRepository.ts +141 -0
@@ -1,6 +1,14 @@
1
1
  import type { NewBlockInRequest } from '../fieldTypes';
2
2
  import type * as ApiTypes from '../generated/ApiTypes';
3
- import type { ItemTypeDefinition } from './itemDefinition';
3
+ import type { ItemTypeDefinition, ToItemAttributesInRequest } from './itemDefinition';
4
4
  type NoInfer<T> = [T][T extends any ? 0 : never];
5
- export declare function buildBlockRecord<D extends ItemTypeDefinition = ItemTypeDefinition>(body: ApiTypes.ItemUpdateSchema<NoInfer<D>>): NewBlockInRequest<NoInfer<D>>;
5
+ type UpdateOrCreateBlockRecordSchema<D extends ItemTypeDefinition = ItemTypeDefinition> = {
6
+ id?: ApiTypes.ItemIdentity;
7
+ type?: ApiTypes.ItemType1;
8
+ item_type: ApiTypes.ItemTypeData<D>;
9
+ meta?: ApiTypes.ItemUpdateSchema['meta'];
10
+ creator?: ApiTypes.ItemUpdateSchema['creator'];
11
+ __itemTypeId?: D['itemTypeId'];
12
+ } & ToItemAttributesInRequest<D>;
13
+ export declare function buildBlockRecord<D extends ItemTypeDefinition = ItemTypeDefinition>(body: UpdateOrCreateBlockRecordSchema<NoInfer<D>>): NewBlockInRequest<NoInfer<D>>;
6
14
  export {};
@@ -24,6 +24,14 @@ interface GenericClient {
24
24
  data: RawApiTypes.Plugin[];
25
25
  }>;
26
26
  };
27
+ site: {
28
+ rawFind(params: {
29
+ include: string;
30
+ }): Promise<{
31
+ data: any;
32
+ included?: Array<RawApiTypes.ItemType | RawApiTypes.Field | any>;
33
+ }>;
34
+ };
27
35
  }
28
36
  /**
29
37
  * Repository for DatoCMS schema entities including item types, fields, and plugins.
@@ -244,5 +252,37 @@ export declare class SchemaRepository {
244
252
  * @throws Error if the plugin is not found
245
253
  */
246
254
  getRawPluginByPackageName(packageName: string): Promise<RawApiTypes.Plugin>;
255
+ /**
256
+ * Prefetches all models and their fields in a single optimized API call.
257
+ * This method populates the internal caches for both item types and fields,
258
+ * making subsequent lookups very fast without additional API calls.
259
+ *
260
+ * This is more efficient than lazy loading when you know you'll need access
261
+ * to multiple models and fields, as it reduces the number of API requests
262
+ * from potentially dozens down to just one.
263
+ *
264
+ * @returns Promise that resolves when all data has been fetched and cached
265
+ */
266
+ prefetchAllModelsAndFields(): Promise<void>;
267
+ /**
268
+ * Gets all models that directly or indirectly embed the given block models.
269
+ * This method recursively traverses the schema to find all models that reference
270
+ * the provided blocks, either directly through block fields or indirectly through
271
+ * other block models that reference them.
272
+ *
273
+ * @param blocks - Array of block models to find references to
274
+ * @returns Promise that resolves to array of models that embed these blocks
275
+ */
276
+ getRawModelsEmbeddingBlocks(blocks: Array<ApiTypes.ItemType | RawApiTypes.ItemType>): Promise<Array<RawApiTypes.ItemType>>;
277
+ /**
278
+ * Gets all models that directly or indirectly embed the given block models.
279
+ * This method recursively traverses the schema to find all models that reference
280
+ * the provided blocks, either directly through block fields or indirectly through
281
+ * other block models that reference them.
282
+ *
283
+ * @param blocks - Array of block models to find references to
284
+ * @returns Promise that resolves to array of models that embed these blocks
285
+ */
286
+ getModelsEmbeddingBlocks(blocks: Array<ApiTypes.ItemType | RawApiTypes.ItemType>): Promise<Array<ApiTypes.ItemType>>;
247
287
  }
248
288
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datocms/cma-client",
3
- "version": "5.1.12",
3
+ "version": "5.1.14",
4
4
  "description": "JS client for DatoCMS REST Content Management API",
5
5
  "keywords": [
6
6
  "datocms",
@@ -37,13 +37,13 @@
37
37
  "url": "https://github.com/datocms/js-rest-api-clients/issues"
38
38
  },
39
39
  "dependencies": {
40
- "@datocms/rest-client-utils": "^5.1.6",
40
+ "@datocms/rest-client-utils": "^5.1.13",
41
41
  "datocms-structured-text-utils": "^5.1.4",
42
42
  "uuid": "^9.0.1"
43
43
  },
44
44
  "devDependencies": {
45
- "@datocms/dashboard-client": "^5.1.12",
45
+ "@datocms/dashboard-client": "^5.1.13",
46
46
  "@types/uuid": "^9.0.7"
47
47
  },
48
- "gitHead": "20ef29ad11049203a14461a9565e0f7833593867"
48
+ "gitHead": "267e2d5a5dbab1bf3cec144445724bcec49ae7e3"
49
49
  }
@@ -93,7 +93,7 @@ type LocalizedFieldAttributesForFieldType<
93
93
  > & {
94
94
  field_type: FieldType;
95
95
  localized: true;
96
- default_value: FieldValue | Record<string, FieldValue>;
96
+ default_value: Record<string, FieldValue>;
97
97
  validators: FieldValidators;
98
98
  appearance: FieldAppearanceConfig<FieldAppearance>;
99
99
  };
@@ -68,7 +68,7 @@ export type UpdatedBlockInRequest<
68
68
  __itemTypeId?: D['itemTypeId'];
69
69
  type: RawApiTypes.ItemType1;
70
70
  id: RawApiTypes.ItemIdentity;
71
- relationships?: RawApiTypes.ItemRelationships<D>;
71
+ relationships: RawApiTypes.ItemRelationships<D>;
72
72
  meta?: RawApiTypes.ItemMeta;
73
73
  attributes: ToItemAttributesInRequest<D>;
74
74
  };
@@ -7412,6 +7412,7 @@ export type ItemValidateExistingSchema<
7412
7412
  | UserData
7413
7413
  | SsoUserData
7414
7414
  | OrganizationData;
7415
+ __itemTypeId?: D['itemTypeId'];
7415
7416
  } & ToItemAttributesInRequest<D>;
7416
7417
  /**
7417
7418
  * This interface was referenced by `Item`'s JSON-Schema
@@ -7428,6 +7429,7 @@ export type ItemValidateNewSchema<
7428
7429
  | UserData
7429
7430
  | SsoUserData
7430
7431
  | OrganizationData;
7432
+ __itemTypeId?: D['itemTypeId'];
7431
7433
  } & ToItemAttributesInRequest<D>;
7432
7434
  /**
7433
7435
  * This interface was referenced by `Item`'s JSON-Schema
@@ -7490,6 +7492,7 @@ export type ItemCreateSchema<
7490
7492
  */
7491
7493
  current_version?: string;
7492
7494
  };
7495
+ __itemTypeId?: D['itemTypeId'];
7493
7496
  } & ToItemAttributesInRequest<D>;
7494
7497
  /**
7495
7498
  * This interface was referenced by `Item`'s JSON-Schema
@@ -7564,6 +7567,7 @@ export type ItemUpdateSchema<
7564
7567
  */
7565
7568
  has_children?: null | boolean;
7566
7569
  };
7570
+ __itemTypeId?: D['itemTypeId'];
7567
7571
  } & ToItemAttributesInRequest<D>;
7568
7572
  /**
7569
7573
  * Information about the record
@@ -147,7 +147,7 @@ export class Client {
147
147
  ...this.config,
148
148
  ...options,
149
149
  logFn: this.config.logFn || console.log,
150
- userAgent: '@datocms/cma-client v5.1.12',
150
+ userAgent: '@datocms/cma-client v5.1.14',
151
151
  baseUrl: this.baseUrl,
152
152
  preCallStack: new Error().stack,
153
153
  extraHeaders: {
@@ -7133,6 +7133,7 @@ export type ItemValidateExistingSchema<
7133
7133
  | OrganizationData;
7134
7134
  };
7135
7135
  };
7136
+ __itemTypeId?: D['itemTypeId'];
7136
7137
  };
7137
7138
  };
7138
7139
  /**
@@ -7167,6 +7168,7 @@ export type ItemValidateNewSchema<
7167
7168
  | OrganizationData;
7168
7169
  };
7169
7170
  };
7171
+ __itemTypeId?: D['itemTypeId'];
7170
7172
  };
7171
7173
  };
7172
7174
  /**
@@ -7247,6 +7249,7 @@ export type ItemCreateSchema<
7247
7249
  | OrganizationData;
7248
7250
  };
7249
7251
  };
7252
+ __itemTypeId?: D['itemTypeId'];
7250
7253
  };
7251
7254
  };
7252
7255
  /**
@@ -7378,6 +7381,7 @@ export type ItemUpdateSchema<
7378
7381
  | OrganizationData;
7379
7382
  };
7380
7383
  };
7384
+ __itemTypeId?: D['itemTypeId'];
7381
7385
  };
7382
7386
  };
7383
7387
  /**
@@ -2,13 +2,29 @@ import * as Utils from '@datocms/rest-client-utils';
2
2
  import type { NewBlockInRequest } from '../fieldTypes';
3
3
  import type * as ApiTypes from '../generated/ApiTypes';
4
4
  import { Item } from '../generated/resources';
5
- import type { ItemTypeDefinition } from './itemDefinition';
5
+ import type {
6
+ ItemTypeDefinition,
7
+ ToItemAttributesInRequest,
8
+ } from './itemDefinition';
6
9
 
7
10
  type NoInfer<T> = [T][T extends any ? 0 : never];
8
11
 
12
+ type UpdateOrCreateBlockRecordSchema<
13
+ D extends ItemTypeDefinition = ItemTypeDefinition,
14
+ > = {
15
+ id?: ApiTypes.ItemIdentity;
16
+ type?: ApiTypes.ItemType1;
17
+ item_type: ApiTypes.ItemTypeData<D>;
18
+ meta?: ApiTypes.ItemUpdateSchema['meta'];
19
+ creator?: ApiTypes.ItemUpdateSchema['creator'];
20
+ __itemTypeId?: D['itemTypeId'];
21
+ } & ToItemAttributesInRequest<D>;
22
+
9
23
  export function buildBlockRecord<
10
24
  D extends ItemTypeDefinition = ItemTypeDefinition,
11
- >(body: ApiTypes.ItemUpdateSchema<NoInfer<D>>): NewBlockInRequest<NoInfer<D>> {
25
+ >(
26
+ body: UpdateOrCreateBlockRecordSchema<NoInfer<D>>,
27
+ ): NewBlockInRequest<NoInfer<D>> {
12
28
  return Utils.serializeRequestBody<{
13
29
  data: NewBlockInRequest<NoInfer<D>>;
14
30
  }>(body, {
@@ -14,13 +14,14 @@ export async function duplicateBlockRecord<
14
14
  existingBlock: ItemWithOptionalIdAndMeta<NoInfer<D>>,
15
15
  schemaRepository: SchemaRepository,
16
16
  ): Promise<NewBlockInRequest<NoInfer<D>>> {
17
- const { type, attributes, relationships } = existingBlock;
17
+ const { __itemTypeId, type, attributes, relationships } = existingBlock;
18
18
 
19
19
  const itemType = await schemaRepository.getRawItemTypeById(
20
20
  existingBlock.relationships.item_type.data.id,
21
21
  );
22
22
 
23
23
  const newBlock = {
24
+ __itemTypeId,
24
25
  type,
25
26
  relationships,
26
27
  attributes,
@@ -1,6 +1,7 @@
1
1
  import { deserializeResponseBody } from '@datocms/rest-client-utils';
2
2
  import type * as ApiTypes from '../generated/ApiTypes';
3
3
  import type * as RawApiTypes from '../generated/RawApiTypes';
4
+ import { blockModelIdsReferencedInField } from './fieldsContainingReferences';
4
5
 
5
6
  interface GenericClient {
6
7
  itemTypes: {
@@ -16,6 +17,12 @@ interface GenericClient {
16
17
  plugins: {
17
18
  rawList(): Promise<{ data: RawApiTypes.Plugin[] }>;
18
19
  };
20
+ site: {
21
+ rawFind(params: { include: string }): Promise<{
22
+ data: any;
23
+ included?: Array<RawApiTypes.ItemType | RawApiTypes.Field | any>;
24
+ }>;
25
+ };
19
26
  }
20
27
 
21
28
  /**
@@ -437,4 +444,138 @@ export class SchemaRepository {
437
444
 
438
445
  return plugin;
439
446
  }
447
+
448
+ /**
449
+ * Prefetches all models and their fields in a single optimized API call.
450
+ * This method populates the internal caches for both item types and fields,
451
+ * making subsequent lookups very fast without additional API calls.
452
+ *
453
+ * This is more efficient than lazy loading when you know you'll need access
454
+ * to multiple models and fields, as it reduces the number of API requests
455
+ * from potentially dozens down to just one.
456
+ *
457
+ * @returns Promise that resolves when all data has been fetched and cached
458
+ */
459
+ async prefetchAllModelsAndFields(): Promise<void> {
460
+ const { included } = await this.client.site.rawFind({
461
+ include: 'item_types,item_types.fields',
462
+ });
463
+
464
+ if (!included) {
465
+ throw new Error('This should not happen');
466
+ }
467
+
468
+ const allItemTypes = included.filter(
469
+ (item): item is RawApiTypes.ItemType => item.type === 'item_type',
470
+ );
471
+ const allFields = included.filter(
472
+ (item): item is RawApiTypes.Field => item.type === 'field',
473
+ );
474
+
475
+ // Populate item types caches
476
+ this.itemTypesPromise = Promise.resolve(allItemTypes);
477
+ for (const itemType of allItemTypes) {
478
+ this.itemTypesByApiKey.set(itemType.attributes.api_key, itemType);
479
+ this.itemTypesById.set(itemType.id, itemType);
480
+ }
481
+
482
+ // Group fields by item type and populate fields cache
483
+ const fieldsByItemTypeId = new Map<string, RawApiTypes.Field[]>();
484
+ for (const field of allFields) {
485
+ const itemTypeId = field.relationships.item_type.data.id;
486
+ if (!fieldsByItemTypeId.has(itemTypeId)) {
487
+ fieldsByItemTypeId.set(itemTypeId, []);
488
+ }
489
+ fieldsByItemTypeId.get(itemTypeId)!.push(field);
490
+ }
491
+
492
+ // Populate the fields cache
493
+ for (const [itemTypeId, fields] of fieldsByItemTypeId) {
494
+ this.fieldsByItemType.set(itemTypeId, fields);
495
+ }
496
+ }
497
+
498
+ /**
499
+ * Gets all models that directly or indirectly embed the given block models.
500
+ * This method recursively traverses the schema to find all models that reference
501
+ * the provided blocks, either directly through block fields or indirectly through
502
+ * other block models that reference them.
503
+ *
504
+ * @param blocks - Array of block models to find references to
505
+ * @returns Promise that resolves to array of models that embed these blocks
506
+ */
507
+ async getRawModelsEmbeddingBlocks(
508
+ blocks: Array<ApiTypes.ItemType | RawApiTypes.ItemType>,
509
+ ): Promise<Array<RawApiTypes.ItemType>> {
510
+ await this.prefetchAllModelsAndFields();
511
+
512
+ const allItemTypes = await this.getAllRawItemTypes();
513
+ const blockIds = new Set(blocks.map((block) => block.id));
514
+ const embeddingModels: Array<RawApiTypes.ItemType> = [];
515
+
516
+ // Helper function to check if a model points to any of the target blocks
517
+ const modelPointsToBlocks = async (
518
+ itemType: RawApiTypes.ItemType,
519
+ alreadyExplored: Set<string> = new Set(),
520
+ ): Promise<boolean> => {
521
+ if (alreadyExplored.has(itemType.id)) {
522
+ return false;
523
+ }
524
+
525
+ alreadyExplored.add(itemType.id);
526
+
527
+ const fields = await this.getRawItemTypeFields(itemType);
528
+
529
+ for (const field of fields) {
530
+ const referencedBlockIds = blockModelIdsReferencedInField(field);
531
+
532
+ // Check if this field directly references any of our target blocks
533
+ if (referencedBlockIds.some((id) => blockIds.has(id))) {
534
+ return true;
535
+ }
536
+
537
+ // Check if this field references other block models that might transitively reference our targets
538
+ const referencedBlocks = referencedBlockIds.map(
539
+ (id) => allItemTypes.find((it) => it.id === id)!,
540
+ );
541
+
542
+ for (const linkedBlock of referencedBlocks) {
543
+ if (
544
+ await modelPointsToBlocks(linkedBlock, new Set(alreadyExplored))
545
+ ) {
546
+ return true;
547
+ }
548
+ }
549
+ }
550
+
551
+ return false;
552
+ };
553
+
554
+ // Check each model to see if it embeds any of the target blocks
555
+ for (const itemType of allItemTypes) {
556
+ if (await modelPointsToBlocks(itemType)) {
557
+ embeddingModels.push(itemType);
558
+ }
559
+ }
560
+
561
+ return embeddingModels;
562
+ }
563
+
564
+ /**
565
+ * Gets all models that directly or indirectly embed the given block models.
566
+ * This method recursively traverses the schema to find all models that reference
567
+ * the provided blocks, either directly through block fields or indirectly through
568
+ * other block models that reference them.
569
+ *
570
+ * @param blocks - Array of block models to find references to
571
+ * @returns Promise that resolves to array of models that embed these blocks
572
+ */
573
+ async getModelsEmbeddingBlocks(
574
+ blocks: Array<ApiTypes.ItemType | RawApiTypes.ItemType>,
575
+ ): Promise<Array<ApiTypes.ItemType>> {
576
+ const rawResult = await this.getRawModelsEmbeddingBlocks(blocks);
577
+ return deserializeResponseBody<ApiTypes.ItemType[]>({
578
+ data: rawResult,
579
+ });
580
+ }
440
581
  }