@stackbit/cms-core 0.1.3-alpha.2 → 0.1.3

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 (52) hide show
  1. package/dist/content-source-interface.d.ts +14 -9
  2. package/dist/content-source-interface.d.ts.map +1 -1
  3. package/dist/content-source-interface.js.map +1 -1
  4. package/dist/content-store-types.d.ts +24 -25
  5. package/dist/content-store-types.d.ts.map +1 -1
  6. package/dist/content-store.d.ts +4 -17
  7. package/dist/content-store.d.ts.map +1 -1
  8. package/dist/content-store.js +949 -147
  9. package/dist/content-store.js.map +1 -1
  10. package/dist/encoder.d.ts.map +1 -1
  11. package/dist/encoder.js +5 -1
  12. package/dist/encoder.js.map +1 -1
  13. package/dist/index.d.ts +0 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -3
  16. package/dist/index.js.map +1 -1
  17. package/package.json +5 -6
  18. package/src/content-source-interface.ts +15 -6
  19. package/src/content-store-types.ts +10 -23
  20. package/src/content-store.ts +1079 -137
  21. package/src/encoder.ts +6 -2
  22. package/src/index.ts +1 -3
  23. package/dist/content-store-utils.d.ts +0 -9
  24. package/dist/content-store-utils.d.ts.map +0 -1
  25. package/dist/content-store-utils.js +0 -139
  26. package/dist/content-store-utils.js.map +0 -1
  27. package/dist/types/search-filter.d.ts +0 -42
  28. package/dist/types/search-filter.d.ts.map +0 -1
  29. package/dist/types/search-filter.js +0 -3
  30. package/dist/types/search-filter.js.map +0 -1
  31. package/dist/utils/create-update-csi-docs.d.ts +0 -68
  32. package/dist/utils/create-update-csi-docs.d.ts.map +0 -1
  33. package/dist/utils/create-update-csi-docs.js +0 -376
  34. package/dist/utils/create-update-csi-docs.js.map +0 -1
  35. package/dist/utils/csi-to-store-docs-converter.d.ts +0 -15
  36. package/dist/utils/csi-to-store-docs-converter.d.ts.map +0 -1
  37. package/dist/utils/csi-to-store-docs-converter.js +0 -287
  38. package/dist/utils/csi-to-store-docs-converter.js.map +0 -1
  39. package/dist/utils/search-utils.d.ts +0 -21
  40. package/dist/utils/search-utils.d.ts.map +0 -1
  41. package/dist/utils/search-utils.js +0 -323
  42. package/dist/utils/search-utils.js.map +0 -1
  43. package/dist/utils/store-to-api-docs-converter.d.ts +0 -5
  44. package/dist/utils/store-to-api-docs-converter.d.ts.map +0 -1
  45. package/dist/utils/store-to-api-docs-converter.js +0 -247
  46. package/dist/utils/store-to-api-docs-converter.js.map +0 -1
  47. package/src/content-store-utils.ts +0 -149
  48. package/src/types/search-filter.ts +0 -53
  49. package/src/utils/create-update-csi-docs.ts +0 -440
  50. package/src/utils/csi-to-store-docs-converter.ts +0 -365
  51. package/src/utils/search-utils.ts +0 -436
  52. package/src/utils/store-to-api-docs-converter.ts +0 -246
@@ -1,440 +0,0 @@
1
- import _ from 'lodash';
2
- import slugify from 'slugify';
3
-
4
- import { Field, FieldObjectProps, FieldSpecificProps, Model } from '@stackbit/sdk';
5
- import { mapPromise } from '@stackbit/utils';
6
-
7
- import * as CSITypes from '../content-source-interface';
8
- import * as ContentStoreTypes from '../content-store-types';
9
-
10
- export type CreateDocumentCallback = ({
11
- updateOperationFields,
12
- modelName
13
- }: {
14
- updateOperationFields: Record<string, CSITypes.UpdateOperationField>;
15
- modelName: string;
16
- }) => Promise<CSITypes.Document>;
17
-
18
- export function getCreateDocumentThunk({
19
- csiModelMap,
20
- locale,
21
- userContext,
22
- contentSourceInstance
23
- }: {
24
- csiModelMap: Record<string, Model>;
25
- locale?: string;
26
- userContext: unknown;
27
- contentSourceInstance: CSITypes.ContentSourceInterface;
28
- }): CreateDocumentCallback {
29
- return async ({ updateOperationFields, modelName }) => {
30
- // When passing model and modelMap to contentSourceInstance, we have to pass
31
- // the original models (i.e., csiModel and csiModelMap) that we've received
32
- // from that contentSourceInstance. We can't pass internal models as they
33
- // might
34
- const csiModel = csiModelMap[modelName];
35
- if (!csiModel) {
36
- throw new Error(`no model with name '${modelName}' was found`);
37
- }
38
- return await contentSourceInstance.createDocument({
39
- updateOperationFields: updateOperationFields,
40
- model: csiModel,
41
- modelMap: csiModelMap,
42
- locale,
43
- userContext
44
- });
45
- };
46
- }
47
-
48
- /**
49
- * Receives a plain `object`, and creates a map of `CSITypes.UpdateOperationField`
50
- * by recursively iterating the `object` fields. Then invokes the `createDocument`
51
- * callback to delegate the creation if CSI document.
52
- *
53
- * If the `object` has fields of type `reference` or `model` with the special
54
- * `$$type` property holding the model name of the nested object, this function
55
- * will recursively create new documents and nested objects for these fields.
56
- * Other fields of will be used to populate the fields of the new document or
57
- * nested object.
58
- *
59
- * @example
60
- * {
61
- * title: 'hello world',
62
- * button: {
63
- * $$type: 'Button', // a new nested object of type Button will be
64
- * label: 'Click me' // created with 'label' field set to 'Click me'
65
- * }
66
- * }
67
- *
68
- * If the `object` has fields of type `reference` or `image` with the special
69
- * `$$ref` property holding an ID of an existing document, this function will
70
- * link an existing documents or assets with that ID.
71
- *
72
- * @example
73
- * {
74
- * title: 'hello world',
75
- * author: {
76
- * $$ref: 'xyz' // the 'author' field will be linked to a document
77
- * // with ID 'xyz'
78
- * }
79
- * }
80
- *
81
- * Returns an object with two fields:
82
- * 1. `document` holding the created CSITypes.Document for the passed object.
83
- * 2. `newRefDocuments` holding list of created CSITypes.Document that were
84
- * created recursively for `reference` fields having the `$$type` property.
85
- */
86
- export async function createDocumentRecursively({
87
- object,
88
- modelName,
89
- modelMap,
90
- createDocument
91
- }: {
92
- object?: Record<string, any>;
93
- modelName: string;
94
- modelMap: Record<string, Model>;
95
- createDocument: CreateDocumentCallback;
96
- }): Promise<{ document: CSITypes.Document; newRefDocuments: CSITypes.Document[] }> {
97
- const model = modelMap[modelName];
98
- if (!model) {
99
- throw new Error(`no model with name '${modelName}' was found`);
100
- }
101
- if (model.type === 'page') {
102
- const tokens = extractTokensFromString(String(model.urlPath));
103
- const slugField = _.last(tokens);
104
- if (object && slugField && slugField in object) {
105
- const slugFieldValue = object[slugField];
106
- object[slugField] = sanitizeSlug(slugFieldValue);
107
- }
108
- }
109
-
110
- const nestedResult = await createNestedObjectRecursively({
111
- object,
112
- modelFields: model.fields ?? [],
113
- fieldPath: [],
114
- modelMap,
115
- createDocument
116
- });
117
-
118
- const document = await createDocument({
119
- updateOperationFields: nestedResult.fields,
120
- modelName: modelName
121
- });
122
- return {
123
- document: document,
124
- newRefDocuments: nestedResult.newRefDocuments
125
- };
126
- }
127
-
128
- function extractTokensFromString(input: string): string[] {
129
- return input.match(/(?<={)[^}]+(?=})/g) || [];
130
- }
131
-
132
- function sanitizeSlug(slug: string) {
133
- return slug
134
- .split('/')
135
- .map((part) => slugify(part, { lower: true }))
136
- .join('/');
137
- }
138
-
139
- async function createNestedObjectRecursively({
140
- object,
141
- modelFields,
142
- fieldPath,
143
- modelMap,
144
- createDocument
145
- }: {
146
- object?: Record<string, any>;
147
- modelFields: Field[];
148
- fieldPath: (string | number)[];
149
- modelMap: Record<string, Model>;
150
- createDocument: CreateDocumentCallback;
151
- }): Promise<{
152
- fields: Record<string, CSITypes.UpdateOperationField>;
153
- newRefDocuments: CSITypes.Document[];
154
- }> {
155
- object = object ?? {};
156
- const result: {
157
- fields: Record<string, CSITypes.UpdateOperationField>;
158
- newRefDocuments: CSITypes.Document[];
159
- } = {
160
- fields: {},
161
- newRefDocuments: []
162
- };
163
- const objectFieldNames = Object.keys(object);
164
- for (const modelField of modelFields) {
165
- const fieldName = modelField.name;
166
- let value;
167
- if (fieldName in object) {
168
- value = object[fieldName];
169
- _.pull(objectFieldNames, fieldName);
170
- } else if (modelField.const) {
171
- value = modelField.const;
172
- } else if (!_.isNil(modelField.default)) {
173
- value = modelField.default;
174
- }
175
- if (!_.isNil(value)) {
176
- const fieldResult = await createNestedField({
177
- value,
178
- modelField,
179
- fieldPath: fieldPath.concat(fieldName),
180
- modelMap,
181
- createDocument
182
- });
183
- result.fields[fieldName] = fieldResult.field;
184
- result.newRefDocuments = result.newRefDocuments.concat(fieldResult.newRefDocuments);
185
- }
186
- }
187
- if (objectFieldNames.length > 0) {
188
- throw new Error(`no model fields found when creating a document with fields: '${objectFieldNames.join(', ')}'`);
189
- }
190
-
191
- return result;
192
- }
193
-
194
- async function createNestedField({
195
- value,
196
- modelField,
197
- fieldPath,
198
- modelMap,
199
- createDocument
200
- }: {
201
- value: any;
202
- modelField: FieldSpecificProps;
203
- fieldPath: (string | number)[];
204
- modelMap: Record<string, Model>;
205
- createDocument: CreateDocumentCallback;
206
- }): Promise<{ field: CSITypes.UpdateOperationField; newRefDocuments: CSITypes.Document[] }> {
207
- if (modelField.type === 'object') {
208
- const result = await createNestedObjectRecursively({
209
- object: value,
210
- modelFields: modelField.fields,
211
- fieldPath,
212
- modelMap,
213
- createDocument
214
- });
215
- return {
216
- field: {
217
- type: 'object',
218
- fields: result.fields
219
- },
220
- newRefDocuments: result.newRefDocuments
221
- };
222
- } else if (modelField.type === 'model') {
223
- let { $$type, ...rest } = value;
224
- const modelNames = modelField.models;
225
- // for backward compatibility check if the object has 'type' instead of '$$type' because older projects use
226
- // the 'type' property in default values
227
- if (!$$type && 'type' in rest) {
228
- $$type = rest.type;
229
- rest = _.omit(rest, 'type');
230
- }
231
- const modelName = $$type ?? (modelNames.length === 1 ? modelNames[0] : null);
232
- if (!modelName) {
233
- throw new Error(`no $$type was specified for nested model`);
234
- }
235
- const model = modelMap[modelName];
236
- if (!model) {
237
- throw new Error(`no model with name '${modelName}' was found`);
238
- }
239
- const result = await createNestedObjectRecursively({
240
- object: rest,
241
- modelFields: model.fields ?? [],
242
- fieldPath,
243
- modelMap,
244
- createDocument
245
- });
246
- return {
247
- field: {
248
- type: 'model',
249
- modelName: modelName,
250
- fields: result.fields
251
- },
252
- newRefDocuments: result.newRefDocuments
253
- };
254
- } else if (modelField.type === 'image') {
255
- let refId: string | undefined;
256
- if (_.isPlainObject(value)) {
257
- refId = value.$$ref;
258
- } else {
259
- refId = value;
260
- }
261
- if (!refId) {
262
- throw new Error(`reference field must specify a value`);
263
- }
264
- return {
265
- field: {
266
- type: 'reference',
267
- refType: 'asset',
268
- refId: refId
269
- },
270
- newRefDocuments: []
271
- };
272
- } else if (modelField.type === 'reference') {
273
- let { $$ref: refId = null, $$type: modelName = null, ...rest } = _.isPlainObject(value) ? value : { $$ref: value };
274
- if (refId) {
275
- return {
276
- field: {
277
- type: 'reference',
278
- refType: 'document',
279
- refId: refId
280
- },
281
- newRefDocuments: []
282
- };
283
- } else {
284
- const modelNames = modelField.models;
285
- if (!modelName) {
286
- // for backward compatibility check if the object has 'type' instead of '$$type' because older projects use
287
- // the 'type' property in default values
288
- if ('type' in rest) {
289
- modelName = rest.type;
290
- rest = _.omit(rest, 'type');
291
- } else if (modelNames.length === 1) {
292
- modelName = modelNames[0];
293
- }
294
- }
295
- const { document, newRefDocuments } = await createDocumentRecursively({
296
- object: rest,
297
- modelName,
298
- modelMap,
299
- createDocument
300
- });
301
- return {
302
- field: {
303
- type: 'reference',
304
- refType: 'document',
305
- refId: document.id
306
- },
307
- newRefDocuments: [document, ...newRefDocuments]
308
- };
309
- }
310
- } else if (modelField.type === 'list') {
311
- if (!Array.isArray(value)) {
312
- throw new Error(`value for list field must be array`);
313
- }
314
- const itemsField = modelField.items;
315
- if (!itemsField) {
316
- throw new Error(`list field does not define items`);
317
- }
318
- const arrayResult = await mapPromise(value, async (item, index) => {
319
- return createNestedField({
320
- value: item,
321
- modelField: itemsField,
322
- fieldPath: fieldPath.concat(index),
323
- modelMap,
324
- createDocument
325
- });
326
- });
327
- return {
328
- field: {
329
- type: 'list',
330
- items: arrayResult.map((result) => result.field)
331
- },
332
- newRefDocuments: arrayResult.reduce((result: CSITypes.Document[], { newRefDocuments }) => result.concat(newRefDocuments), [])
333
- };
334
- }
335
- return {
336
- field: {
337
- type: modelField.type,
338
- value: value
339
- },
340
- newRefDocuments: []
341
- };
342
- }
343
-
344
- export async function convertOperationField({
345
- operationField,
346
- fieldPath,
347
- modelField,
348
- modelMap,
349
- createDocument
350
- }: {
351
- operationField: ContentStoreTypes.UpdateOperationField;
352
- fieldPath: (string | number)[];
353
- modelField: Field;
354
- modelMap: Record<string, Model>;
355
- createDocument: CreateDocumentCallback;
356
- }): Promise<CSITypes.UpdateOperationField> {
357
- // for insert operations, the modelField will be of the list, so get the modelField of the list items
358
- const modelFieldOrListItems: FieldSpecificProps = modelField.type === 'list' ? modelField.items! : modelField;
359
- switch (operationField.type) {
360
- case 'object': {
361
- const result = await createNestedObjectRecursively({
362
- object: operationField.object,
363
- modelFields: (modelFieldOrListItems as FieldObjectProps).fields,
364
- fieldPath: fieldPath,
365
- modelMap,
366
- createDocument
367
- });
368
- return {
369
- type: operationField.type,
370
- fields: result.fields
371
- };
372
- }
373
- case 'model': {
374
- const model = modelMap[operationField.modelName];
375
- if (!model) {
376
- throw new Error(`error updating document, could not find document model: '${operationField.modelName}'`);
377
- }
378
- const result = await createNestedObjectRecursively({
379
- object: operationField.object,
380
- modelFields: model.fields!,
381
- fieldPath,
382
- modelMap,
383
- createDocument
384
- });
385
- return {
386
- type: operationField.type,
387
- modelName: operationField.modelName,
388
- fields: result.fields
389
- };
390
- }
391
- case 'list': {
392
- if (modelField.type !== 'list') {
393
- throw new Error(`'the operation field type '${operationField.type}' does not match the model field type '${modelField.type}'`);
394
- }
395
- const result = await mapPromise(operationField.items, async (item) => {
396
- const result = await createNestedField({
397
- value: item,
398
- modelField: modelField.items!,
399
- fieldPath,
400
- modelMap,
401
- createDocument
402
- });
403
- return result.field;
404
- });
405
- return {
406
- type: operationField.type,
407
- items: result
408
- };
409
- }
410
- case 'string':
411
- // When inserting new string value into a list, the client does not
412
- // send value. Set an empty string value.
413
- if (typeof operationField.value !== 'string') {
414
- return {
415
- type: operationField.type,
416
- value: ''
417
- };
418
- }
419
- return operationField as CSITypes.UpdateOperationField;
420
- case 'enum':
421
- // When inserting new enum value into a list, the client does not
422
- // send value. Set first option as the value.
423
- if (typeof operationField.value !== 'string') {
424
- if (modelFieldOrListItems.type !== 'enum') {
425
- throw new Error(`'the operation field type 'enum' does not match the model field type '${modelFieldOrListItems.type}'`);
426
- }
427
- const option = modelFieldOrListItems.options[0]!;
428
- const optionValue = typeof option === 'object' ? option.value : option;
429
- return {
430
- type: operationField.type,
431
- value: optionValue
432
- };
433
- }
434
- return operationField as CSITypes.UpdateOperationField;
435
- case 'image':
436
- return operationField as CSITypes.UpdateOperationField;
437
- default:
438
- return operationField as CSITypes.UpdateOperationField;
439
- }
440
- }