@stackbit/cms-sanity 0.2.45-develop.1 → 0.2.45-staging.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 (34) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/sanity-content-source.d.ts +21 -10
  6. package/dist/sanity-content-source.d.ts.map +1 -1
  7. package/dist/sanity-content-source.js +74 -242
  8. package/dist/sanity-content-source.js.map +1 -1
  9. package/dist/sanity-document-converter.d.ts +11 -9
  10. package/dist/sanity-document-converter.d.ts.map +1 -1
  11. package/dist/sanity-document-converter.js +262 -205
  12. package/dist/sanity-document-converter.js.map +1 -1
  13. package/dist/sanity-operation-converter.d.ts +60 -0
  14. package/dist/sanity-operation-converter.d.ts.map +1 -0
  15. package/dist/sanity-operation-converter.js +664 -0
  16. package/dist/sanity-operation-converter.js.map +1 -0
  17. package/dist/sanity-schema-converter.d.ts +35 -3
  18. package/dist/sanity-schema-converter.d.ts.map +1 -1
  19. package/dist/sanity-schema-converter.js +290 -43
  20. package/dist/sanity-schema-converter.js.map +1 -1
  21. package/dist/sanity-schema-fetcher.d.ts +3 -3
  22. package/dist/sanity-schema-fetcher.d.ts.map +1 -1
  23. package/dist/utils.d.ts +53 -0
  24. package/dist/utils.d.ts.map +1 -1
  25. package/dist/utils.js +93 -1
  26. package/dist/utils.js.map +1 -1
  27. package/package.json +6 -5
  28. package/src/index.ts +1 -1
  29. package/src/sanity-content-source.ts +109 -317
  30. package/src/sanity-document-converter.ts +332 -231
  31. package/src/sanity-operation-converter.ts +785 -0
  32. package/src/sanity-schema-converter.ts +424 -70
  33. package/src/sanity-schema-fetcher.ts +3 -3
  34. package/src/utils.ts +98 -0
@@ -1,56 +1,46 @@
1
1
  import _ from 'lodash';
2
- import { v4 as uuid } from 'uuid';
3
- import tinycolor from 'tinycolor2';
4
2
  import https from 'https';
5
3
  import path from 'path';
6
4
  import fse from 'fs-extra';
7
5
  import { glob } from 'glob';
8
- import { MutationEvent, PatchOperations, SanityAssetDocument, SanityClient as SanityClientType, SanityDocument, SanityDocumentStub } from '@sanity/client';
9
- import type {
10
- DocumentVersion,
11
- DocumentVersionWithDocument,
12
- Field,
13
- FieldList,
14
- FieldListItems,
15
- FieldObjectProps,
16
- FieldSpecificProps,
17
- Model,
18
- Schema,
19
- UserCommandSpawner
20
- } from '@stackbit/types';
6
+ import { MutationEvent, SanityAssetDocument, SanityClient as SanityClientType, SanityDocument, SanityDocumentStub } from '@sanity/client';
21
7
  import type * as ContentSourceTypes from '@stackbit/types';
8
+ import type { DocumentVersion, DocumentVersionWithDocument, Field, Model, Schema, UserCommandSpawner } from '@stackbit/types';
22
9
  import { getVersion as stackbitUtilsGetVersion } from '@stackbit/types';
10
+ import type * as SanityTypes from '@sanity/types';
11
+ import { deferWhileRunning, getPackageManager, omitByNil } from '@stackbit/utils';
23
12
 
24
13
  import * as fetcher from './sanity-schema-fetcher';
25
14
  import {
26
- SanityClient,
27
- testToken,
28
15
  DocumentHistory,
29
16
  DocumentHistoryMap,
17
+ fetchDocumentForRevision,
18
+ fetchDocumentRevision,
19
+ fetchDocumentsHistory,
20
+ fetchScheduledActions,
30
21
  fetchUsers,
22
+ SanityClient,
31
23
  SanityUser,
32
- fetchScheduledActions,
33
- fetchDocumentsHistory,
34
- fetchDocumentForRevision,
35
- fetchDocumentRevision
24
+ testToken
36
25
  } from './sanity-api-client';
37
- import { convertSchema } from './sanity-schema-converter';
26
+ import { convertSchema, ModelContext, SchemaContext } from './sanity-schema-converter';
38
27
  import {
39
- convertDocuments,
40
- convertAssets,
41
- DocumentContext,
42
28
  AssetContext,
43
- ContextualDocument,
44
29
  ContextualAsset,
45
- getDraftObjectId,
46
- getPureObjectId,
47
- isDraftId,
48
- DRAFT_ID_PREFIX,
30
+ ContextualDocument,
31
+ convertAndFilterScheduledActions,
32
+ convertAssets,
33
+ convertDocuments,
49
34
  ConvertDocumentsOptions,
50
35
  convertScheduledAction,
51
- convertAndFilterScheduledActions
36
+ DocumentContext,
37
+ DRAFT_ID_PREFIX,
38
+ getDraftObjectId,
39
+ getPureObjectId,
40
+ isDraftId
52
41
  } from './sanity-document-converter';
53
- import { deferWhileRunning, getPackageManager, omitByNil } from '@stackbit/utils';
42
+ import { isLocalizedModelField } from './utils';
43
+ import { convertUpdateOperation, localizedValue, mapUpdateOperationFieldToSanityValue } from './sanity-operation-converter';
54
44
 
55
45
  export interface ContentSourceOptions {
56
46
  /**
@@ -107,22 +97,29 @@ export interface ContentSourceOptions {
107
97
  * 'query' - the listener will be notified of changes when they are available for querying.
108
98
  */
109
99
  listenerVisibility?: 'transaction' | 'query';
100
+
101
+ /**
102
+ * When using Sanity Internationalized-Array plugin, specify the default language
103
+ * that will be used by the Visual Editor. If not specified, the first language
104
+ * in the `languages` will be selected.
105
+ *
106
+ * https://www.sanity.io/plugins/internationalized-array
107
+ */
108
+ defaultLocale?: string;
110
109
  }
111
110
 
112
111
  export type UserContext = {
113
112
  accessToken: string;
114
113
  };
115
114
 
116
- export type SchemaContext = unknown;
117
-
118
- export type ContextualInitOptions = ContentSourceTypes.InitOptions<SchemaContext, DocumentContext, AssetContext>;
119
- type Cache = ContentSourceTypes.Cache<SchemaContext, DocumentContext, AssetContext>;
115
+ export type ContextualInitOptions = ContentSourceTypes.InitOptions<SchemaContext, DocumentContext, AssetContext, ModelContext>;
116
+ type Cache = ContentSourceTypes.Cache<SchemaContext, DocumentContext, AssetContext, ModelContext>;
120
117
  type ContentChanges = Required<ContentSourceTypes.ContentChanges<DocumentContext, AssetContext>>;
121
118
 
122
119
  const ASSET_TYPES = ['sanity.imageAsset', 'cloudinary.asset', 'bynder.asset', 'aprimo.cdnasset'];
123
120
  const SANITY_API_VERSION = '1';
124
121
 
125
- export class SanityContentSource implements ContentSourceTypes.ContentSourceInterface<UserContext, SchemaContext, DocumentContext, AssetContext> {
122
+ export class SanityContentSource implements ContentSourceTypes.ContentSourceInterface<UserContext, SchemaContext, DocumentContext, AssetContext, ModelContext> {
126
123
  private readonly projectId: string;
127
124
  private readonly dataset: string;
128
125
  private readonly token: string;
@@ -144,6 +141,7 @@ export class SanityContentSource implements ContentSourceTypes.ContentSourceInte
144
141
  private contentChangeSubscriptionInterval: any;
145
142
  private userMap: Record<string, SanityUser> = {};
146
143
  private cache!: Cache;
144
+ private defaultLocale?: string;
147
145
 
148
146
  constructor(options: ContentSourceOptions) {
149
147
  this.projectId = options.projectId;
@@ -156,6 +154,7 @@ export class SanityContentSource implements ContentSourceTypes.ContentSourceInte
156
154
  this.studioInstallCommand = options.studioInstallCommand;
157
155
  this.schemaGlob = options.schemaGlob;
158
156
  this.listenerVisibility = options.listenerVisibility;
157
+ this.defaultLocale = options.defaultLocale;
159
158
 
160
159
  if (!this.rootPath) {
161
160
  throw new Error(`Required parameter 'rootPath' is missing.`);
@@ -479,7 +478,7 @@ export class SanityContentSource implements ContentSourceTypes.ContentSourceInte
479
478
  };
480
479
  }
481
480
 
482
- async getSanitySchema(): Promise<any> {
481
+ async getSanitySchema(): Promise<{ models: SanityTypes.SchemaTypeDefinition[] }> {
483
482
  let sanitySchema;
484
483
  try {
485
484
  sanitySchema = await fetcher.spawnFetchSchema({
@@ -498,11 +497,19 @@ export class SanityContentSource implements ContentSourceTypes.ContentSourceInte
498
497
  return sanitySchema;
499
498
  }
500
499
 
501
- async getSchema(): Promise<Schema<SchemaContext>> {
500
+ async getSchema(): Promise<Schema<SchemaContext, ModelContext>> {
502
501
  this.logger.debug('getSchema');
503
502
  const sanitySchema = await this.getSanitySchema();
504
- const { models } = convertSchema(sanitySchema);
505
- return { models, context: {} };
503
+ const { models, locales } = convertSchema({
504
+ schema: sanitySchema,
505
+ logger: this.logger,
506
+ defaultLocale: this.defaultLocale
507
+ });
508
+ return {
509
+ models,
510
+ locales,
511
+ context: null
512
+ };
506
513
  }
507
514
 
508
515
  async getDocumentsHistory(documentIds: string[]): Promise<DocumentHistoryMap> {
@@ -663,7 +670,7 @@ export class SanityContentSource implements ContentSourceTypes.ContentSourceInte
663
670
  userContext
664
671
  }: {
665
672
  updateOperationFields: Record<string, ContentSourceTypes.UpdateOperationField>;
666
- model: Model;
673
+ model: Model<ModelContext>;
667
674
  locale?: string;
668
675
  userContext?: UserContext;
669
676
  }): Promise<{ documentId: string }> {
@@ -672,18 +679,41 @@ export class SanityContentSource implements ContentSourceTypes.ContentSourceInte
672
679
  _type: model.name
673
680
  };
674
681
 
675
- _.forEach(updateOperationFields, (updateOperationField, fieldName) => {
676
- const childModelField = _.find(model.fields, (field: Field) => field.name === fieldName);
677
- const value = mapUpdateOperationFieldToSanityValue(updateOperationField, this.cache.getModelByName, childModelField);
678
- _.set(sanityDocument, fieldName, value);
679
- });
680
- const userClient = this.getApiClientForUser({ userContext });
681
- const result = await userClient.create(sanityDocument).catch((err) => {
682
- this.logger.error('Error creating document', err);
683
- throw err;
684
- });
685
- const pureObjectId = getPureObjectId(result._id);
686
- return { documentId: pureObjectId };
682
+ try {
683
+ _.forEach(updateOperationFields, (updateOperationField, fieldName) => {
684
+ const childModelField = _.find(model.fields, (field: Field) => field.name === fieldName);
685
+ if (!childModelField) {
686
+ throw new Error(`No model field found for field: ${fieldName}`);
687
+ }
688
+ const value = mapUpdateOperationFieldToSanityValue({
689
+ updateOperationField,
690
+ getModelByName: this.cache.getModelByName,
691
+ modelField: childModelField,
692
+ rootModel: model,
693
+ modelFieldPath: [fieldName],
694
+ locale
695
+ });
696
+ if (isLocalizedModelField(childModelField)) {
697
+ _.set(sanityDocument, fieldName, [
698
+ localizedValue({
699
+ value,
700
+ model,
701
+ modelFieldPath: [fieldName],
702
+ locale
703
+ })
704
+ ]);
705
+ } else {
706
+ _.set(sanityDocument, fieldName, value);
707
+ }
708
+ });
709
+ const userClient = this.getApiClientForUser({ userContext });
710
+ const result = await userClient.create(sanityDocument);
711
+ const pureObjectId = getPureObjectId(result._id);
712
+ return { documentId: pureObjectId };
713
+ } catch (error: any) {
714
+ this.logger.error('Error creating document', error);
715
+ throw new Error(`Error creating document. ${error.message}`);
716
+ }
687
717
  }
688
718
 
689
719
  async updateDocument({
@@ -704,26 +734,31 @@ export class SanityContentSource implements ContentSourceTypes.ContentSourceInte
704
734
  const sanityDocument = (document.context.draftDocument || document.context.publishedDocument)!;
705
735
  const modelName = document.modelName;
706
736
  const model = this.cache.getModelByName(modelName);
707
- if (!model) {
708
- throw new Error(`Error updating document: could not find document model '${modelName}'.`);
737
+ try {
738
+ if (!model) {
739
+ throw new Error(`Could not find document model '${modelName}'.`);
740
+ }
741
+ const transaction = _.reduce(
742
+ operations,
743
+ (transaction, operation) => {
744
+ const patchObject = convertUpdateOperation({
745
+ operation,
746
+ sanityDocument,
747
+ getModelByName: this.cache.getModelByName,
748
+ model
749
+ });
750
+ return transaction.patch(draftObjectId, patchObject);
751
+ },
752
+ userClient.transaction().createIfNotExists({
753
+ ...sanityDocument,
754
+ _id: draftObjectId
755
+ })
756
+ );
757
+ await transaction.commit({ visibility: this.listenerVisibility === 'query' ? 'sync' : 'async', returnDocuments: true });
758
+ } catch (error: any) {
759
+ this.logger.error(`Error updating document ${document.id}`, error);
760
+ throw new Error(`Error updating document ${document.id}. ${error.message}`);
709
761
  }
710
- const transaction = _.reduce(
711
- operations,
712
- (transaction, operation) => {
713
- const opFunc = Operations[operation.opType] as OperationFunction<typeof operation.opType>;
714
- const patchObject = opFunc?.({ sanityDocument, operation, getModelByName: this.cache.getModelByName });
715
- return transaction.patch(draftObjectId, patchObject);
716
- },
717
- userClient.transaction().createIfNotExists({
718
- ...sanityDocument,
719
- _id: draftObjectId
720
- })
721
- );
722
-
723
- await transaction.commit({ visibility: this.listenerVisibility === 'query' ? 'sync' : 'async', returnDocuments: true }).catch((err) => {
724
- this.logger.error('Error updating document', err);
725
- throw err;
726
- });
727
762
  }
728
763
 
729
764
  async deleteDocument({ document, userContext }: { document: ContextualDocument; userContext?: UserContext }): Promise<void> {
@@ -973,7 +1008,7 @@ export class SanityContentSource implements ContentSourceTypes.ContentSourceInte
973
1008
  fetchDocumentRevision({ documentId, draftDocumentId, versionId, dataset: this.dataset, client: this.client }),
974
1009
  fetchDocumentForRevision({ documentId, draftDocumentId, versionId, dataset: this.dataset, client: this.client })
975
1010
  ]);
976
- const [contextualDocument] = await this.convertDocuments({
1011
+ const [contextualDocument] = this.convertDocuments({
977
1012
  documents: [document],
978
1013
  getModelByName: this.cache.getModelByName,
979
1014
  studioUrl: this.studioUrl
@@ -986,246 +1021,3 @@ export class SanityContentSource implements ContentSourceTypes.ContentSourceInte
986
1021
  return { version: this.convertVersionForDocument(version, contextualDocument) };
987
1022
  }
988
1023
  }
989
-
990
- function mapUpdateOperationFieldToSanityValue(
991
- updateOperationField: ContentSourceTypes.UpdateOperationField,
992
- getModelByName: ContentSourceTypes.Cache['getModelByName'],
993
- modelField?: FieldSpecificProps,
994
- isInList?: boolean
995
- ): any {
996
- if (updateOperationField.type === 'object') {
997
- const result = {};
998
- _.forEach(updateOperationField.fields, (updateOperationField, fieldName) => {
999
- const childModelField = _.find((modelField as FieldObjectProps).fields, (field) => field.name === fieldName);
1000
- const value = mapUpdateOperationFieldToSanityValue(updateOperationField, getModelByName, childModelField);
1001
- _.set(result, fieldName, value);
1002
- });
1003
- return listItem(result, isInList);
1004
- } else if (updateOperationField.type === 'model') {
1005
- const modelName = updateOperationField.modelName;
1006
- const childModel = getModelByName(modelName);
1007
- const result = listItem(
1008
- {
1009
- _type: modelName
1010
- },
1011
- isInList
1012
- );
1013
- _.forEach(updateOperationField.fields, (updateOperationField, fieldName) => {
1014
- const childModelField = _.find(childModel?.fields, (field) => field.name === fieldName);
1015
- const value = mapUpdateOperationFieldToSanityValue(updateOperationField, getModelByName, childModelField);
1016
- _.set(result, fieldName, value);
1017
- });
1018
- return result;
1019
- } else if (updateOperationField.type === 'image') {
1020
- const value = updateOperationField?.value;
1021
- if (modelField?.type === 'image') {
1022
- if (modelField?.source === 'cloudinary' || modelField?.source === 'aprimo') {
1023
- const type = modelField?.source === 'cloudinary' ? 'cloudinary.asset' : 'aprimo.cdnasset';
1024
- return listItem(
1025
- {
1026
- _type: type,
1027
- ...value
1028
- },
1029
- isInList
1030
- );
1031
- } else if (modelField?.source === 'bynder') {
1032
- let imageValue = value;
1033
- if (imageValue?.__typename) {
1034
- imageValue = _.omitBy(
1035
- {
1036
- id: value.id,
1037
- name: value.name,
1038
- databaseId: value.databaseId,
1039
- type: value.type,
1040
- previewUrl: value.type === 'VIDEO' ? value.previewUrls[0] : value.files.webImage.url,
1041
- previewImg: value.files.webImage.url,
1042
- datUrl: value.files.transformBaseUrl?.url,
1043
- videoUrl: value.type === 'VIDEO' ? value.files.original?.url : null,
1044
- description: value.description,
1045
- aspectRatio: value.height / value.width
1046
- },
1047
- _.isUndefined
1048
- );
1049
- }
1050
- return listItem(
1051
- {
1052
- _type: 'bynder.asset',
1053
- ...imageValue
1054
- },
1055
- isInList
1056
- );
1057
- }
1058
- }
1059
- // TODO: there is a bug right now because documentField is inferred from the model which is an "image", not reference
1060
- return listItem(linkForAssetId(value), isInList);
1061
- } else if (updateOperationField.type === 'reference') {
1062
- return listItem(
1063
- updateOperationField.refType === 'document'
1064
- ? {
1065
- _ref: updateOperationField.refId,
1066
- _type: 'reference',
1067
- //TODO lookup document and decide if reference needs to be weak and what is its type
1068
- _weak: true,
1069
- _strengthenOnPublish: {
1070
- // type: <type for referenced item>,
1071
- // template: {
1072
- // id: <type for referenced item>,
1073
- // }
1074
- }
1075
- }
1076
- : linkForAssetId(updateOperationField.refId),
1077
- isInList
1078
- );
1079
- } else if (updateOperationField.type === 'list') {
1080
- const listItemsModel = modelField?.type === 'list' && modelField.items;
1081
- return updateOperationField.items.map((item) => {
1082
- let listItemModelField: FieldListItems | undefined;
1083
- if (_.isArray(listItemsModel)) {
1084
- listItemModelField = (listItemsModel as FieldListItems[]).find((listItemsModel) => listItemsModel.type === item.type);
1085
- } else if (listItemsModel) {
1086
- listItemModelField = listItemsModel;
1087
- }
1088
- return mapUpdateOperationFieldToSanityValue(item, getModelByName, listItemModelField, true);
1089
- });
1090
- } else if (updateOperationField.type === 'number') {
1091
- return Number(updateOperationField.value);
1092
- } else if (updateOperationField.type === 'slug') {
1093
- return {
1094
- _type: 'slug',
1095
- current: updateOperationField.value
1096
- };
1097
- } else if (updateOperationField.type === 'color') {
1098
- const color = tinycolor(updateOperationField.value);
1099
- return {
1100
- _type: 'color',
1101
- hex: color.toHexString(),
1102
- alpha: color.getAlpha(),
1103
- hsl: {
1104
- _type: 'hslaColor',
1105
- ...color.toHsl()
1106
- },
1107
- hsv: {
1108
- _type: 'hsvaColor',
1109
- ...color.toHsv()
1110
- },
1111
- rgb: {
1112
- _type: 'rgbaColor',
1113
- ...color.toRgb()
1114
- }
1115
- };
1116
- }
1117
- return updateOperationField.value;
1118
- }
1119
-
1120
- function listItem(field: any, isInList?: boolean) {
1121
- if (isInList) {
1122
- _.set(field, '_key', uuid());
1123
- }
1124
- return field;
1125
- }
1126
-
1127
- function linkForAssetId(assetId?: string): any {
1128
- return {
1129
- _type: 'image',
1130
- asset: {
1131
- _ref: assetId,
1132
- _type: 'reference'
1133
- }
1134
- };
1135
- }
1136
-
1137
- type FindByOpType<Union, Type> = Union extends { opType: Type } ? Union : never;
1138
- type OperationForType<Type> = FindByOpType<ContentSourceTypes.UpdateOperation, Type>;
1139
- type OperationFunction<Type> = ({
1140
- sanityDocument,
1141
- operation,
1142
- getModelByName
1143
- }: {
1144
- sanityDocument: SanityDocument;
1145
- operation: OperationForType<Type>;
1146
- getModelByName: ContentSourceTypes.Cache['getModelByName'];
1147
- }) => PatchOperations;
1148
-
1149
- type OperationMap = {
1150
- [Type in ContentSourceTypes.UpdateOperation['opType']]: OperationFunction<Type>;
1151
- };
1152
-
1153
- const Operations: OperationMap = {
1154
- set: ({ operation, sanityDocument, getModelByName }) => {
1155
- const { field, fieldPath, modelField } = operation;
1156
- const value = mapUpdateOperationFieldToSanityValue(field, getModelByName, modelField);
1157
- const patch = {};
1158
- _.set(patch, ['set', keyedFieldPathToString(sanityDocument, fieldPath)], value);
1159
- return patch;
1160
- },
1161
- unset: ({ operation, sanityDocument }) => {
1162
- const { fieldPath } = operation;
1163
- return {
1164
- unset: [keyedFieldPathToString(sanityDocument, fieldPath)]
1165
- };
1166
- },
1167
- insert: ({ operation, sanityDocument, getModelByName }) => {
1168
- const { item, fieldPath, modelField, index } = operation;
1169
- const listItemModelField = (modelField as FieldList).items ?? { type: 'string' };
1170
- const value = mapUpdateOperationFieldToSanityValue(item, getModelByName, listItemModelField, true);
1171
- const array = _.get(sanityDocument, fieldPath);
1172
- if (!array?.length) {
1173
- return {
1174
- set: {
1175
- [keyedFieldPathToString(sanityDocument, fieldPath)]: [value]
1176
- }
1177
- };
1178
- }
1179
- if (_.isNil(index) || index >= array.length) {
1180
- return {
1181
- insert: {
1182
- after: `${keyedFieldPathToString(sanityDocument, fieldPath)}[-1]`,
1183
- items: [value]
1184
- }
1185
- };
1186
- }
1187
- return {
1188
- insert: {
1189
- before: `${keyedFieldPathToString(sanityDocument, fieldPath)}[${index}]`,
1190
- items: [value]
1191
- }
1192
- };
1193
- },
1194
- remove: ({ sanityDocument, operation }) => {
1195
- const { fieldPath, index } = operation;
1196
- return {
1197
- unset: [keyedFieldPathToString(sanityDocument, [...fieldPath, index])]
1198
- };
1199
- },
1200
- reorder: ({ sanityDocument, operation }) => {
1201
- const { fieldPath, order } = operation;
1202
- const array = _.get(sanityDocument, fieldPath);
1203
- const newEntryArr = order.map((newIndex) => array[newIndex]);
1204
- const patch = {};
1205
- _.set(patch, ['set', keyedFieldPathToString(sanityDocument, fieldPath)], newEntryArr);
1206
- return patch;
1207
- }
1208
- };
1209
-
1210
- export const keyedFieldPathToString = (document: SanityDocument, fieldPath: (string | number)[]) =>
1211
- _.reduce(
1212
- fieldPath,
1213
- (accumulator, fieldName, index) => {
1214
- if (_.isString(fieldName) && /\W/.test(fieldName)) {
1215
- // field name is a string with non alphanumeric character
1216
- accumulator += `['${fieldName}']`;
1217
- } else if (_.isNumber(fieldName)) {
1218
- // try to use Sanity _key as explicit accessor
1219
- const val = _.get(document, [...fieldPath.slice(0, index), Number(fieldName)]);
1220
- const key = val?._key;
1221
- accumulator += key ? `[_key=="${key}"]` : `[${Number(fieldName)}]`;
1222
- } else {
1223
- if (index > 0) {
1224
- accumulator += '.';
1225
- }
1226
- accumulator += fieldName;
1227
- }
1228
- return accumulator;
1229
- },
1230
- ''
1231
- );