@stackbit/cms-core 0.2.1 → 0.3.0-develop.0
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/content-store-utils.d.ts +5 -1
- package/dist/content-store-utils.d.ts.map +1 -1
- package/dist/content-store-utils.js +28 -3
- package/dist/content-store-utils.js.map +1 -1
- package/dist/content-store.d.ts +12 -1
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +399 -177
- package/dist/content-store.js.map +1 -1
- package/dist/types/content-store-document-fields.d.ts +26 -4
- package/dist/types/content-store-document-fields.d.ts.map +1 -1
- package/dist/types/content-store-documents.d.ts +14 -3
- package/dist/types/content-store-documents.d.ts.map +1 -1
- package/dist/types/content-store-types.d.ts +7 -1
- package/dist/types/content-store-types.d.ts.map +1 -1
- package/dist/utils/backward-compatibility.d.ts +184 -0
- package/dist/utils/backward-compatibility.d.ts.map +1 -0
- package/dist/utils/backward-compatibility.js +151 -0
- package/dist/utils/backward-compatibility.js.map +1 -0
- package/dist/utils/config-delegate.d.ts +11 -0
- package/dist/utils/config-delegate.d.ts.map +1 -0
- package/dist/utils/config-delegate.js +226 -0
- package/dist/utils/config-delegate.js.map +1 -0
- package/dist/utils/create-update-csi-docs.d.ts +7 -5
- package/dist/utils/create-update-csi-docs.d.ts.map +1 -1
- package/dist/utils/create-update-csi-docs.js +24 -24
- package/dist/utils/create-update-csi-docs.js.map +1 -1
- package/dist/utils/csi-to-store-docs-converter.d.ts +17 -3
- package/dist/utils/csi-to-store-docs-converter.d.ts.map +1 -1
- package/dist/utils/csi-to-store-docs-converter.js +187 -47
- package/dist/utils/csi-to-store-docs-converter.js.map +1 -1
- package/dist/utils/site-map.d.ts.map +1 -1
- package/dist/utils/site-map.js +4 -1
- package/dist/utils/site-map.js.map +1 -1
- package/dist/utils/store-to-api-docs-converter.d.ts +6 -1
- package/dist/utils/store-to-api-docs-converter.d.ts.map +1 -1
- package/dist/utils/store-to-api-docs-converter.js +140 -51
- package/dist/utils/store-to-api-docs-converter.js.map +1 -1
- package/dist/utils/store-to-csi-docs-converter.d.ts +1 -0
- package/dist/utils/store-to-csi-docs-converter.d.ts.map +1 -1
- package/dist/utils/store-to-csi-docs-converter.js +2 -1
- package/dist/utils/store-to-csi-docs-converter.js.map +1 -1
- package/package.json +5 -5
- package/src/content-store-utils.ts +40 -6
- package/src/content-store.ts +552 -299
- package/src/types/content-store-document-fields.ts +16 -4
- package/src/types/content-store-documents.ts +12 -3
- package/src/types/content-store-types.ts +4 -1
- package/src/utils/backward-compatibility.ts +269 -0
- package/src/utils/config-delegate.ts +277 -0
- package/src/utils/create-update-csi-docs.ts +47 -50
- package/src/utils/csi-to-store-docs-converter.ts +256 -43
- package/src/utils/site-map.ts +19 -7
- package/src/utils/store-to-api-docs-converter.ts +185 -52
- package/src/utils/store-to-csi-docs-converter.ts +1 -1
|
@@ -440,7 +440,7 @@ export type DocumentObjectFieldNonLocalized = {
|
|
|
440
440
|
| { isUnset: true }
|
|
441
441
|
| {
|
|
442
442
|
isUnset?: false;
|
|
443
|
-
|
|
443
|
+
getPreview: (options: { delegate?: CSITypes.ConfigDelegate; locale?: string }) => DocumentObjectFieldPreview;
|
|
444
444
|
fields: Record<string, DocumentField>;
|
|
445
445
|
}
|
|
446
446
|
);
|
|
@@ -453,12 +453,18 @@ export interface DocumentObjectFieldLocalized {
|
|
|
453
453
|
string,
|
|
454
454
|
{
|
|
455
455
|
locale: string;
|
|
456
|
-
|
|
456
|
+
getPreview: (options: { delegate?: CSITypes.ConfigDelegate; locale?: string }) => DocumentObjectFieldPreview;
|
|
457
457
|
fields: Record<string, DocumentField>;
|
|
458
458
|
}
|
|
459
459
|
>;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
export interface DocumentObjectFieldPreview {
|
|
463
|
+
previewTitle?: string;
|
|
464
|
+
previewSubtitle?: string;
|
|
465
|
+
previewImage?: unknown;
|
|
466
|
+
}
|
|
467
|
+
|
|
462
468
|
export type DocumentObjectFieldAPI = {
|
|
463
469
|
type: 'object';
|
|
464
470
|
label?: string;
|
|
@@ -486,9 +492,9 @@ export type DocumentModelFieldNonLocalized = {
|
|
|
486
492
|
| { isUnset: true }
|
|
487
493
|
| {
|
|
488
494
|
isUnset?: false;
|
|
489
|
-
srcObjectLabel: string;
|
|
490
495
|
srcModelName: string;
|
|
491
496
|
srcModelLabel: string;
|
|
497
|
+
getPreview: (options: { delegate?: CSITypes.ConfigDelegate; locale?: string }) => DocumentModelFieldPreview;
|
|
492
498
|
fields: Record<string, DocumentField>;
|
|
493
499
|
}
|
|
494
500
|
);
|
|
@@ -501,9 +507,9 @@ export interface DocumentModelFieldLocalized {
|
|
|
501
507
|
string,
|
|
502
508
|
{
|
|
503
509
|
locale: string;
|
|
504
|
-
srcObjectLabel: string;
|
|
505
510
|
srcModelName: string;
|
|
506
511
|
srcModelLabel: string;
|
|
512
|
+
getPreview: (options: { delegate?: CSITypes.ConfigDelegate; locale?: string }) => DocumentModelFieldPreview;
|
|
507
513
|
fields: Record<string, DocumentField>;
|
|
508
514
|
}
|
|
509
515
|
>;
|
|
@@ -525,6 +531,12 @@ export type DocumentModelFieldAPI = {
|
|
|
525
531
|
) &
|
|
526
532
|
({ localized?: false } | { localized: true; locale: string });
|
|
527
533
|
|
|
534
|
+
export interface DocumentModelFieldPreview {
|
|
535
|
+
previewTitle: string;
|
|
536
|
+
previewSubtitle?: string;
|
|
537
|
+
previewImage?: unknown;
|
|
538
|
+
}
|
|
539
|
+
|
|
528
540
|
/**
|
|
529
541
|
* reference
|
|
530
542
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DocumentStatus } from '@stackbit/types';
|
|
1
|
+
import { DocumentStatus, ConfigDelegate } from '@stackbit/types';
|
|
2
2
|
import { DocumentField, DocumentFieldAPI, DocumentFieldAPIForType, DocumentStringLikeFieldForType } from './content-store-document-fields';
|
|
3
3
|
|
|
4
4
|
export interface Document {
|
|
@@ -9,7 +9,9 @@ export interface Document {
|
|
|
9
9
|
srcEnvironment: string;
|
|
10
10
|
srcObjectId: string;
|
|
11
11
|
srcObjectUrl: string;
|
|
12
|
-
|
|
12
|
+
/** @deprecated used by older, non-csi cms interface */
|
|
13
|
+
srcObjectLabel?: string;
|
|
14
|
+
getPreview: (options: { delegate?: ConfigDelegate; locale?: string }) => DocumentPreview;
|
|
13
15
|
srcModelName: string;
|
|
14
16
|
srcModelLabel: string;
|
|
15
17
|
isChanged: boolean;
|
|
@@ -22,6 +24,12 @@ export interface Document {
|
|
|
22
24
|
fields: Record<string, DocumentField>;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
export interface DocumentPreview {
|
|
28
|
+
previewTitle: string;
|
|
29
|
+
previewSubtitle?: string;
|
|
30
|
+
previewImage?: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
export interface Asset {
|
|
26
34
|
type: 'asset';
|
|
27
35
|
srcType: string;
|
|
@@ -85,8 +93,9 @@ export interface AssetFileFieldProps {
|
|
|
85
93
|
*/
|
|
86
94
|
export type APIObject = APIDocumentObject | APIImageObject;
|
|
87
95
|
|
|
88
|
-
export interface APIDocumentObject extends Omit<Document, 'fields' | 'type'> {
|
|
96
|
+
export interface APIDocumentObject extends Omit<Document, 'getPreview' | 'fields' | 'type'> {
|
|
89
97
|
type: 'object';
|
|
98
|
+
srcObjectLabel: string;
|
|
90
99
|
fields: Record<string, DocumentFieldAPI>;
|
|
91
100
|
}
|
|
92
101
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as CSITypes from '@stackbit/types';
|
|
2
2
|
import { Model } from '@stackbit/sdk';
|
|
3
3
|
import { Asset, Document } from './content-store-documents';
|
|
4
|
+
import { BackCompatContentSourceInterface } from '../utils/backward-compatibility';
|
|
4
5
|
|
|
5
6
|
export interface ContentSourceData {
|
|
6
7
|
/* Internal content source id computed by concatenating srcType and srcProjectId */
|
|
7
8
|
id: string;
|
|
8
9
|
/* The content source instance loaded from stackbitConfig.contentSources */
|
|
9
|
-
instance:
|
|
10
|
+
instance: BackCompatContentSourceInterface;
|
|
11
|
+
version: { interfaceVersion: string; contentSourceVersion: string };
|
|
10
12
|
srcType: string;
|
|
11
13
|
srcProjectId: string;
|
|
12
14
|
locales?: CSITypes.Locale[];
|
|
@@ -16,6 +18,7 @@ export interface ContentSourceData {
|
|
|
16
18
|
/* Map of extended and validated Models by model name */
|
|
17
19
|
modelMap: Record<string, Model>;
|
|
18
20
|
/* Array of original Models (as provided by content source) */
|
|
21
|
+
csiSchema: CSITypes.Schema;
|
|
19
22
|
csiModels: CSITypes.Model[];
|
|
20
23
|
/* Map of original Models (as provided by content source) by model name */
|
|
21
24
|
csiModelMap: Record<string, CSITypes.Model>;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import * as CSITypes from '@stackbit/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AnyContentSourceInterface is the union of the previous ContentSourceInterface
|
|
5
|
+
* versions.
|
|
6
|
+
*
|
|
7
|
+
* When changing the ContentSourceInterface in a way that it may break previous
|
|
8
|
+
* content source modules, create a new type by omitting the changed methods and
|
|
9
|
+
* redefine them with their new signatures. Then add the new type to the union.
|
|
10
|
+
*/
|
|
11
|
+
export type AnyContentSourceInterface = ContentSourceInterface_v0_1_0 | ContentSourceInterface_v0_2_0;
|
|
12
|
+
|
|
13
|
+
export type ContentSourceInterface_v0_2_0 = CSITypes.ContentSourceInterface;
|
|
14
|
+
|
|
15
|
+
export type ContentSourceInterface_v0_1_0 = Omit<
|
|
16
|
+
CSITypes.ContentSourceInterface,
|
|
17
|
+
| 'getVersion'
|
|
18
|
+
| 'init'
|
|
19
|
+
| 'destroy'
|
|
20
|
+
| 'getSchema'
|
|
21
|
+
| 'onFilesChange'
|
|
22
|
+
| 'startWatchingContentUpdates'
|
|
23
|
+
| 'stopWatchingContentUpdates'
|
|
24
|
+
| 'hasAccess'
|
|
25
|
+
| 'getDocuments'
|
|
26
|
+
| 'createDocument'
|
|
27
|
+
| 'updateDocument'
|
|
28
|
+
> & {
|
|
29
|
+
init(options: {
|
|
30
|
+
logger: CSITypes.Logger;
|
|
31
|
+
userLogger: CSITypes.Logger;
|
|
32
|
+
userCommandSpawner?: CSITypes.UserCommandSpawner;
|
|
33
|
+
localDev: boolean;
|
|
34
|
+
webhookUrl?: string;
|
|
35
|
+
devAppRestartNeeded?: () => void;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
getModels(): Promise<CSITypes.Model[]>;
|
|
38
|
+
getLocales(): Promise<CSITypes.Locale[]>;
|
|
39
|
+
onFilesChange?(options: { updatedFiles: string[] }): Promise<{ schemaChanged?: boolean; contentChangeEvent?: CSITypes.ContentChangeEvent }>;
|
|
40
|
+
startWatchingContentUpdates(options: {
|
|
41
|
+
getModelMap: () => CSITypes.ModelMap;
|
|
42
|
+
getDocument: ({ documentId }: { documentId: string }) => CSITypes.Document | undefined;
|
|
43
|
+
getAsset: ({ assetId }: { assetId: string }) => CSITypes.Asset | undefined;
|
|
44
|
+
onContentChange: (contentChangeEvent: CSITypes.ContentChangeEvent) => Promise<void>;
|
|
45
|
+
onSchemaChange: () => void;
|
|
46
|
+
}): void;
|
|
47
|
+
stopWatchingContentUpdates(): void;
|
|
48
|
+
hasAccess(options: { userContext?: unknown }): boolean | Promise<{ hasConnection: boolean; hasPermissions: boolean }>;
|
|
49
|
+
getDocuments(options: { modelMap: CSITypes.ModelMap }): Promise<CSITypes.Document[]>;
|
|
50
|
+
createDocument(options: {
|
|
51
|
+
updateOperationFields: Record<string, CSITypes.UpdateOperationField>;
|
|
52
|
+
model: CSITypes.Model;
|
|
53
|
+
modelMap: CSITypes.ModelMap;
|
|
54
|
+
locale?: string;
|
|
55
|
+
defaultLocaleDocumentId?: string;
|
|
56
|
+
userContext?: unknown;
|
|
57
|
+
}): Promise<CSITypes.Document>;
|
|
58
|
+
updateDocument(options: {
|
|
59
|
+
document: CSITypes.Document;
|
|
60
|
+
operations: CSITypes.UpdateOperation[];
|
|
61
|
+
modelMap: CSITypes.ModelMap;
|
|
62
|
+
userContext?: unknown;
|
|
63
|
+
}): Promise<CSITypes.Document>;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* BackCompatContentSourceInterface redefines the ContentSourceInterface such
|
|
68
|
+
* that when its methods are called, it can correctly invoke the previous
|
|
69
|
+
* content source module versions.
|
|
70
|
+
*
|
|
71
|
+
* The parameters of its methods must be intersections of the parameters in the
|
|
72
|
+
* previous versions. For example, if a method in an older version received
|
|
73
|
+
* 'options.x', and a method in the newer version receives 'options.y', the
|
|
74
|
+
* matching method should receive both options '{ x } & { y }' to ensure that
|
|
75
|
+
* both the older and the newer content sources will receive what they need.
|
|
76
|
+
*
|
|
77
|
+
* The method return values must match the most recent content source versions.
|
|
78
|
+
*/
|
|
79
|
+
export type BackCompatContentSourceInterface = Omit<
|
|
80
|
+
CSITypes.ContentSourceInterface,
|
|
81
|
+
'getVersion' | 'onFilesChange' | 'startWatchingContentUpdates' | 'hasAccess' | 'getDocuments' | 'createDocument' | 'updateDocument'
|
|
82
|
+
> & {
|
|
83
|
+
getVersion(): GetVersionReturn;
|
|
84
|
+
onFilesChange(options: BCOnFilesChangeOptions): OnFilesChangeReturn;
|
|
85
|
+
startWatchingContentUpdates?(options: BCStartWatchingOptions): StartWatchingReturn;
|
|
86
|
+
hasAccess(options: BCHasAccessOptions): HasAccessReturn;
|
|
87
|
+
getDocuments(options: BCGetDocumentsOptions): GetDocumentsReturn;
|
|
88
|
+
createDocument(options: BCCreateDocumentOptions): CreateDocumentReturn;
|
|
89
|
+
updateDocument(options: BCUpdateDocumentOptions): UpdateDocumentReturn;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export function backwardCompatibleContentSource(contentSource: AnyContentSourceInterface): BackCompatContentSourceInterface {
|
|
93
|
+
return new Proxy(contentSource, {
|
|
94
|
+
get(target: AnyContentSourceInterface, prop: keyof CSITypes.ContentSourceInterface): any {
|
|
95
|
+
switch (prop) {
|
|
96
|
+
case 'getVersion':
|
|
97
|
+
return getVersion.bind(undefined, target);
|
|
98
|
+
case 'destroy':
|
|
99
|
+
return destroy.bind(undefined, target);
|
|
100
|
+
case 'onFilesChange':
|
|
101
|
+
return onFilesChange.bind(undefined, target);
|
|
102
|
+
case 'startWatchingContentUpdates':
|
|
103
|
+
return startWatchingContentUpdates.bind(undefined, target);
|
|
104
|
+
case 'getSchema':
|
|
105
|
+
return getSchema.bind(undefined, target);
|
|
106
|
+
case 'hasAccess':
|
|
107
|
+
return hasAccess.bind(undefined, target);
|
|
108
|
+
case 'getDocuments':
|
|
109
|
+
return getDocuments.bind(undefined, target);
|
|
110
|
+
case 'createDocument':
|
|
111
|
+
return createDocument.bind(undefined, target);
|
|
112
|
+
case 'updateDocument':
|
|
113
|
+
return updateDocument.bind(undefined, target);
|
|
114
|
+
default:
|
|
115
|
+
return target[prop];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}) as BackCompatContentSourceInterface;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type ReturnTypeOfMethod<Method extends keyof CSITypes.ContentSourceInterface> = ReturnType<NonNullable<CSITypes.ContentSourceInterface[Method]>>;
|
|
122
|
+
|
|
123
|
+
type GetVersionReturn = Promise<{ interfaceVersion: string; contentSourceVersion: string }>;
|
|
124
|
+
export async function getVersion(contentSource: AnyContentSourceInterface): GetVersionReturn {
|
|
125
|
+
if ('getVersion' in contentSource) {
|
|
126
|
+
return contentSource.getVersion();
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
interfaceVersion: '0.1.0',
|
|
130
|
+
contentSourceVersion: ''
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
type DestroyReturn = ReturnTypeOfMethod<'destroy'>;
|
|
135
|
+
export async function destroy(contentSource: AnyContentSourceInterface): DestroyReturn {
|
|
136
|
+
if ('destroy' in contentSource) {
|
|
137
|
+
return contentSource.destroy();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
type BCOnFilesChangeOptions = { updatedFiles: string[] };
|
|
142
|
+
type OnFilesChangeReturn = ReturnTypeOfMethod<'onFilesChange'>;
|
|
143
|
+
/**
|
|
144
|
+
* Converts the old onFilesChange API to the new one
|
|
145
|
+
* OLD: onFilesChange?(options: { updatedFiles: string[]; }):
|
|
146
|
+
* Promise<{ schemaChanged?: boolean; contentChangeEvent?: ContentChangeEvent<DocumentContext, AssetContext> }>
|
|
147
|
+
* NEW: onFilesChange?(options: { updatedFiles: string[]; }):
|
|
148
|
+
* Promise<{ invalidateSchema?: boolean; contentChanges?: ContentChanges<DocumentContext, AssetContext> }>
|
|
149
|
+
*/
|
|
150
|
+
export async function onFilesChange(contentSource: AnyContentSourceInterface, options: BCOnFilesChangeOptions): OnFilesChangeReturn {
|
|
151
|
+
const value = await contentSource.onFilesChange?.(options);
|
|
152
|
+
if (!value) {
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
// if there are properties from the new API return the value as is
|
|
156
|
+
if ('invalidateSchema' in value || 'contentChanges' in value) {
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
// if there are properties from the old API return convert to the new API value
|
|
160
|
+
const result: Awaited<OnFilesChangeReturn> = {};
|
|
161
|
+
if ('schemaChanged' in value) {
|
|
162
|
+
result.invalidateSchema = value.schemaChanged;
|
|
163
|
+
}
|
|
164
|
+
if ('contentChangeEvent' in value) {
|
|
165
|
+
result.contentChanges = value.contentChangeEvent;
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
type BCStartWatchingOptions = {
|
|
171
|
+
getModelMap: () => CSITypes.ModelMap;
|
|
172
|
+
getDocument: ({ documentId }: { documentId: string }) => CSITypes.Document | undefined;
|
|
173
|
+
getAsset: ({ assetId }: { assetId: string }) => CSITypes.Asset | undefined;
|
|
174
|
+
onContentChange: (contentChangeEvent: CSITypes.ContentChangeEvent) => Promise<void>;
|
|
175
|
+
onSchemaChange: () => void;
|
|
176
|
+
};
|
|
177
|
+
type StartWatchingReturn = ReturnTypeOfMethod<'startWatchingContentUpdates'>;
|
|
178
|
+
/**
|
|
179
|
+
* Converts between the old startWatchingContentUpdates API and the new one.
|
|
180
|
+
* OLD: startWatchingContentUpdates(options: startWatchingContentUpdatesOptionsOld): void;
|
|
181
|
+
* NEW: startWatchingContentUpdates?(): void;
|
|
182
|
+
*/
|
|
183
|
+
export function startWatchingContentUpdates(contentSource: AnyContentSourceInterface, options: BCStartWatchingOptions): StartWatchingReturn {
|
|
184
|
+
contentSource.startWatchingContentUpdates?.(options);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Converts the old getModels() and getLocales() API methods to the new getSchema() API method.
|
|
189
|
+
* OLD:
|
|
190
|
+
* getModels(): Promise<Model[]>;
|
|
191
|
+
* getLocales(): Promise<Locale[]>
|
|
192
|
+
* NEW:
|
|
193
|
+
* getSchema(): Promise<Schema<SchemaContext>>
|
|
194
|
+
*/
|
|
195
|
+
export async function getSchema(contentSource: AnyContentSourceInterface): Promise<CSITypes.Schema> {
|
|
196
|
+
if ('getSchema' in contentSource) {
|
|
197
|
+
return contentSource.getSchema();
|
|
198
|
+
}
|
|
199
|
+
const models = await contentSource.getModels();
|
|
200
|
+
const locales = await contentSource.getLocales();
|
|
201
|
+
return { models, locales, context: null };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
type HasAccessReturn = ReturnTypeOfMethod<'hasAccess'>;
|
|
205
|
+
type BCHasAccessOptions = { userContext?: unknown };
|
|
206
|
+
/**
|
|
207
|
+
* Converts the old hasAccess API to the new one
|
|
208
|
+
* OLD: hasAccess(options: { userContext?: UserContext }): Promise<boolean>
|
|
209
|
+
* NEW: hasAccess(options: { userContext?: UserContext }): Promise<{ hasConnection: boolean; hasPermissions: boolean }>
|
|
210
|
+
*/
|
|
211
|
+
export async function hasAccess(contentSource: AnyContentSourceInterface, options: BCHasAccessOptions): HasAccessReturn {
|
|
212
|
+
const result = await contentSource.hasAccess(options);
|
|
213
|
+
if (typeof result === 'boolean') {
|
|
214
|
+
return {
|
|
215
|
+
hasConnection: result,
|
|
216
|
+
hasPermissions: result
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
type BCGetDocumentsOptions = { modelMap: CSITypes.ModelMap };
|
|
223
|
+
type GetDocumentsReturn = ReturnTypeOfMethod<'getDocuments'>;
|
|
224
|
+
/**
|
|
225
|
+
* Converts the old getDocuments API to the new one
|
|
226
|
+
* OLD: getDocuments(options: { modelMap: ModelMap }): Promise<Document[]>
|
|
227
|
+
* NEW: getDocuments(): Promise<Document[]>
|
|
228
|
+
*/
|
|
229
|
+
export async function getDocuments(contentSource: AnyContentSourceInterface, options: BCGetDocumentsOptions): GetDocumentsReturn {
|
|
230
|
+
return contentSource.getDocuments(options);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
type BCCreateDocumentOptions = {
|
|
234
|
+
updateOperationFields: Record<string, CSITypes.UpdateOperationField>;
|
|
235
|
+
model: CSITypes.Model;
|
|
236
|
+
modelMap: CSITypes.ModelMap;
|
|
237
|
+
locale?: string;
|
|
238
|
+
defaultLocaleDocumentId?: string;
|
|
239
|
+
userContext?: unknown;
|
|
240
|
+
};
|
|
241
|
+
type CreateDocumentReturn = ReturnTypeOfMethod<'createDocument'>;
|
|
242
|
+
/**
|
|
243
|
+
* Converts the old createDocument API to the new one
|
|
244
|
+
* OLD: createDocument(options: Options & { modelMap: ModelMap }): Promise<Document<DocumentContext>>
|
|
245
|
+
* NEW: createDocument(options: Options): Promise<{ documentId: string }>
|
|
246
|
+
*/
|
|
247
|
+
export async function createDocument(contentSource: AnyContentSourceInterface, options: BCCreateDocumentOptions): CreateDocumentReturn {
|
|
248
|
+
const result = await contentSource.createDocument(options);
|
|
249
|
+
if ('id' in result) {
|
|
250
|
+
return { documentId: result.id };
|
|
251
|
+
}
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
type UpdateDocumentReturn = ReturnTypeOfMethod<'updateDocument'>;
|
|
256
|
+
type BCUpdateDocumentOptions = {
|
|
257
|
+
document: CSITypes.Document;
|
|
258
|
+
operations: CSITypes.UpdateOperation[];
|
|
259
|
+
modelMap: CSITypes.ModelMap;
|
|
260
|
+
userContext?: unknown;
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Converts the old updateDocument API to the new one
|
|
264
|
+
* OLD: updateDocument(options: Options & { modelMap: ModelMap }): Promise<CSITypes.Document>;
|
|
265
|
+
* NEW: updateDocument(options: Options): Promise<void>
|
|
266
|
+
*/
|
|
267
|
+
export async function updateDocument(contentSource: AnyContentSourceInterface, options: BCUpdateDocumentOptions): UpdateDocumentReturn {
|
|
268
|
+
await contentSource.updateDocument(options);
|
|
269
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import * as CSITypes from '@stackbit/types';
|
|
3
|
+
import { getLocalizedFieldForLocale } from '@stackbit/types';
|
|
4
|
+
import * as ContentStoreTypes from '../types';
|
|
5
|
+
import { getContentSourceId } from '../content-store-utils';
|
|
6
|
+
import { mapStoreDocumentToCSIDocumentWithSource } from './store-to-csi-docs-converter';
|
|
7
|
+
|
|
8
|
+
export function getCreateConfigDelegateThunk({
|
|
9
|
+
getContentSourceDataById,
|
|
10
|
+
logger
|
|
11
|
+
}: {
|
|
12
|
+
getContentSourceDataById: () => Record<string, ContentStoreTypes.ContentSourceData>;
|
|
13
|
+
logger: CSITypes.Logger;
|
|
14
|
+
}): () => CSITypes.ConfigDelegate {
|
|
15
|
+
return () =>
|
|
16
|
+
createConfigDelegate({
|
|
17
|
+
contentSourceDataById: getContentSourceDataById(),
|
|
18
|
+
logger: logger
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createConfigDelegate({
|
|
23
|
+
contentSourceDataById,
|
|
24
|
+
logger
|
|
25
|
+
}: {
|
|
26
|
+
contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
|
|
27
|
+
logger: CSITypes.Logger;
|
|
28
|
+
}): CSITypes.ConfigDelegate {
|
|
29
|
+
return {
|
|
30
|
+
getDocumentById: ({ id, srcType, srcProjectId }) => {
|
|
31
|
+
const document = findDocumentByIdAndSourceTypeOrId({
|
|
32
|
+
contentSourceDataById,
|
|
33
|
+
documentId: id,
|
|
34
|
+
srcType,
|
|
35
|
+
srcProjectId,
|
|
36
|
+
logger
|
|
37
|
+
});
|
|
38
|
+
if (document) {
|
|
39
|
+
return mapStoreDocumentToCSIDocumentWithSource(document);
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
},
|
|
43
|
+
getModelByName: ({ modelName, srcType, srcProjectId }) => {
|
|
44
|
+
return findModelByNameAndSourceTypeOrId({
|
|
45
|
+
contentSourceDataById,
|
|
46
|
+
modelName,
|
|
47
|
+
srcType,
|
|
48
|
+
srcProjectId,
|
|
49
|
+
logger
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
getDefaultLocaleBySource: ({ srcType, srcProjectId }) => {
|
|
53
|
+
// if srcType and srcProjectId are provided, use them to get the specific contentSourceData without trying to infer the right model
|
|
54
|
+
if (srcType && srcProjectId) {
|
|
55
|
+
const contentSourceId = getContentSourceId(srcType, srcProjectId);
|
|
56
|
+
return contentSourceDataById[contentSourceId]?.defaultLocaleCode;
|
|
57
|
+
}
|
|
58
|
+
const contentSourcesData = findContentSourcesDataForTypeOrId({
|
|
59
|
+
contentSourceDataById,
|
|
60
|
+
srcType,
|
|
61
|
+
srcProjectId
|
|
62
|
+
});
|
|
63
|
+
if (contentSourcesData.length === 1) {
|
|
64
|
+
return contentSourcesData[0]!.defaultLocaleCode;
|
|
65
|
+
} else if (contentSourcesData.length > 1) {
|
|
66
|
+
logger.warn(
|
|
67
|
+
`The getDefaultLocaleBySource() found more than one content sources for '${srcType}'. ` +
|
|
68
|
+
`Please specify 'srcType' and 'srcProjectId' to narrow down the search.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
},
|
|
73
|
+
getDocumentFieldForFieldPath: ({ document, fromField, fieldPath, locale }): CSITypes.DocumentFieldNonLocalized | undefined => {
|
|
74
|
+
const fieldPathArr = _.toPath(fieldPath);
|
|
75
|
+
const contentSourceId = getContentSourceId(document.srcType, document.srcProjectId);
|
|
76
|
+
const contentSource = contentSourceDataById[contentSourceId];
|
|
77
|
+
if (!contentSource) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getNestedFieldFromFieldsForPath(
|
|
82
|
+
fields: Record<string, CSITypes.DocumentField>,
|
|
83
|
+
fieldPathArr: string[],
|
|
84
|
+
currentContentSource: ContentStoreTypes.ContentSourceData
|
|
85
|
+
): CSITypes.DocumentFieldNonLocalized | undefined {
|
|
86
|
+
const fieldName = fieldPathArr[0];
|
|
87
|
+
fieldPathArr = fieldPathArr.slice(1);
|
|
88
|
+
if (!fieldName) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
const field = fields[fieldName];
|
|
92
|
+
if (!field) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const resolvedLocale = locale ?? currentContentSource.defaultLocaleCode;
|
|
96
|
+
const nonLocalizedField = getLocalizedFieldForLocale(field, resolvedLocale);
|
|
97
|
+
if (!nonLocalizedField) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (fieldPathArr.length === 0) {
|
|
101
|
+
return nonLocalizedField;
|
|
102
|
+
}
|
|
103
|
+
return getNestedFieldFromLocalizedFieldForPath(nonLocalizedField, fieldPathArr, currentContentSource);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getNestedFieldFromLocalizedFieldForPath(
|
|
107
|
+
nonLocalizedField: CSITypes.DocumentFieldNonLocalized,
|
|
108
|
+
fieldPathArr: string[],
|
|
109
|
+
currentContentSource: ContentStoreTypes.ContentSourceData
|
|
110
|
+
): CSITypes.DocumentFieldNonLocalized | undefined {
|
|
111
|
+
if (nonLocalizedField.type === 'object' || nonLocalizedField.type === 'model') {
|
|
112
|
+
return getNestedFieldFromFieldsForPath(nonLocalizedField.fields, fieldPathArr, currentContentSource);
|
|
113
|
+
} else if (nonLocalizedField.type === 'reference') {
|
|
114
|
+
const refDocument = currentContentSource.documentMap[nonLocalizedField.refId];
|
|
115
|
+
if (!refDocument) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
const fields = mapStoreDocumentToCSIDocumentWithSource(refDocument).fields;
|
|
119
|
+
return getNestedFieldFromFieldsForPath(fields, fieldPathArr, currentContentSource);
|
|
120
|
+
} else if (nonLocalizedField.type === 'cross-reference') {
|
|
121
|
+
const contentSourceId = getContentSourceId(nonLocalizedField.refSrcType, nonLocalizedField.refProjectId);
|
|
122
|
+
const contentSource = contentSourceDataById[contentSourceId];
|
|
123
|
+
if (!contentSource) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const refDocument = contentSource.documentMap[nonLocalizedField.refId];
|
|
127
|
+
if (!refDocument) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
const fields = mapStoreDocumentToCSIDocumentWithSource(refDocument).fields;
|
|
131
|
+
return getNestedFieldFromFieldsForPath(fields, fieldPathArr, contentSource);
|
|
132
|
+
} else if (nonLocalizedField.type === 'list') {
|
|
133
|
+
const index = _.toNumber(fieldPathArr[0]);
|
|
134
|
+
fieldPathArr = fieldPathArr.slice(1);
|
|
135
|
+
if (_.isNaN(index)) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
const localizedItem = nonLocalizedField.items[index];
|
|
139
|
+
if (!localizedItem) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
if (fieldPathArr.length === 0) {
|
|
143
|
+
return localizedItem;
|
|
144
|
+
}
|
|
145
|
+
return getNestedFieldFromLocalizedFieldForPath(localizedItem, fieldPathArr, currentContentSource);
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (fromField) {
|
|
151
|
+
const resolvedLocale = locale ?? contentSource.defaultLocaleCode;
|
|
152
|
+
const nonLocalizedField = getLocalizedFieldForLocale(fromField, resolvedLocale);
|
|
153
|
+
if (!nonLocalizedField) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
return getNestedFieldFromLocalizedFieldForPath(nonLocalizedField, fieldPathArr, contentSource);
|
|
157
|
+
} else {
|
|
158
|
+
return getNestedFieldFromFieldsForPath(document.fields, fieldPathArr, contentSource);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function findDocumentByIdAndSourceTypeOrId({
|
|
165
|
+
contentSourceDataById,
|
|
166
|
+
documentId,
|
|
167
|
+
srcType,
|
|
168
|
+
srcProjectId,
|
|
169
|
+
logger
|
|
170
|
+
}: {
|
|
171
|
+
contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
|
|
172
|
+
documentId: string;
|
|
173
|
+
srcType?: string;
|
|
174
|
+
srcProjectId?: string;
|
|
175
|
+
logger: CSITypes.Logger;
|
|
176
|
+
}): ContentStoreTypes.Document | undefined {
|
|
177
|
+
// if srcType and srcProjectId are provided, use them to get the specific contentSourceData without trying to infer the right document
|
|
178
|
+
if (srcType && srcProjectId) {
|
|
179
|
+
const contentSourceId = getContentSourceId(srcType, srcProjectId);
|
|
180
|
+
return contentSourceDataById[contentSourceId]?.documentMap[documentId];
|
|
181
|
+
}
|
|
182
|
+
const contentSourcesData = findContentSourcesDataForTypeOrId({
|
|
183
|
+
contentSourceDataById,
|
|
184
|
+
srcType,
|
|
185
|
+
srcProjectId
|
|
186
|
+
});
|
|
187
|
+
const matchingDocuments = contentSourcesData.reduce((matchingDocuments: ContentStoreTypes.Document[], contentSourceData) => {
|
|
188
|
+
const document = contentSourceData.documentMap[documentId];
|
|
189
|
+
if (document) {
|
|
190
|
+
matchingDocuments.push(document);
|
|
191
|
+
}
|
|
192
|
+
return matchingDocuments;
|
|
193
|
+
}, []);
|
|
194
|
+
if (matchingDocuments.length === 1) {
|
|
195
|
+
return matchingDocuments[0];
|
|
196
|
+
} else if (matchingDocuments.length > 1) {
|
|
197
|
+
const matchedContentSources = matchingDocuments.map((document) => `srcType: ${document.srcType}, srcProjectId: ${document.srcProjectId}`).join('; ');
|
|
198
|
+
logger.warn(
|
|
199
|
+
`The getDocumentById() found more than one documents with ID '${documentId}' ` +
|
|
200
|
+
`in the following content sources ${matchedContentSources}. ` +
|
|
201
|
+
`Please specify 'srcType' and/or 'srcProjectId' to narrow down the search.`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function findModelByNameAndSourceTypeOrId({
|
|
208
|
+
contentSourceDataById,
|
|
209
|
+
modelName,
|
|
210
|
+
srcType,
|
|
211
|
+
srcProjectId,
|
|
212
|
+
logger
|
|
213
|
+
}: {
|
|
214
|
+
contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
|
|
215
|
+
modelName: string;
|
|
216
|
+
srcType?: string;
|
|
217
|
+
srcProjectId?: string;
|
|
218
|
+
logger: CSITypes.Logger;
|
|
219
|
+
}): CSITypes.ModelWithSource | undefined {
|
|
220
|
+
// if srcType and srcProjectId are provided, use them to get the specific contentSourceData without trying to infer the right model
|
|
221
|
+
if (srcType && srcProjectId) {
|
|
222
|
+
const contentSourceId = getContentSourceId(srcType, srcProjectId);
|
|
223
|
+
const contentSourceData = contentSourceDataById[contentSourceId];
|
|
224
|
+
const model = contentSourceData?.modelMap[modelName];
|
|
225
|
+
if (model) {
|
|
226
|
+
return {
|
|
227
|
+
...model,
|
|
228
|
+
srcType: contentSourceData.srcType,
|
|
229
|
+
srcProjectId: contentSourceData.srcProjectId
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
const contentSourcesData = findContentSourcesDataForTypeOrId({
|
|
235
|
+
contentSourceDataById,
|
|
236
|
+
srcType,
|
|
237
|
+
srcProjectId
|
|
238
|
+
});
|
|
239
|
+
const matchingModels = contentSourcesData.reduce((matchingModels: CSITypes.ModelWithSource[], contentSourceData) => {
|
|
240
|
+
const model = contentSourceData.modelMap[modelName];
|
|
241
|
+
if (model) {
|
|
242
|
+
matchingModels.push({
|
|
243
|
+
...model,
|
|
244
|
+
srcType: contentSourceData.srcType,
|
|
245
|
+
srcProjectId: contentSourceData.srcProjectId
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return matchingModels;
|
|
249
|
+
}, []);
|
|
250
|
+
if (matchingModels.length === 1) {
|
|
251
|
+
return matchingModels[0]!;
|
|
252
|
+
} else if (matchingModels.length > 1) {
|
|
253
|
+
const matchedContentSources = matchingModels.map((model) => `srcType: ${model.srcType}, srcProjectId: ${model.srcProjectId}`).join('; ');
|
|
254
|
+
logger.warn(
|
|
255
|
+
`The getModelByName() found more than one model with name '${modelName}' ` +
|
|
256
|
+
`in the following content sources ${matchedContentSources}. ` +
|
|
257
|
+
`Please specify 'srcType' and/or 'srcProjectId' to narrow down the search.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function findContentSourcesDataForTypeOrId({
|
|
264
|
+
contentSourceDataById,
|
|
265
|
+
srcType,
|
|
266
|
+
srcProjectId
|
|
267
|
+
}: {
|
|
268
|
+
contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
|
|
269
|
+
srcType?: string;
|
|
270
|
+
srcProjectId?: string;
|
|
271
|
+
}): ContentStoreTypes.ContentSourceData[] {
|
|
272
|
+
return _.filter(contentSourceDataById, (contentSourceData) => {
|
|
273
|
+
const srcTypeMatch = !srcType || contentSourceData.srcType === srcType;
|
|
274
|
+
const srcProjectIdMatch = !srcProjectId || contentSourceData.srcProjectId === srcProjectId;
|
|
275
|
+
return srcTypeMatch && srcProjectIdMatch;
|
|
276
|
+
});
|
|
277
|
+
}
|