@datocms/cma-client
Version:
JS client for DatoCMS REST Content Management API
412 lines • 19.9 kB
JavaScript
/**
* DatoCMS Block Field Value Processing Utilities
*
* This utility provides a unified interface for working with blocks embedded within DatoCMS field values.
* DatoCMS supports three field types that can contain blocks:
* - Modular Content fields: arrays of blocks
* - Single Block fields: a single block
* - Structured Text fields: complex document structures with embedded blocks
*
* The challenge this solves: Each field type stores blocks differently and requires different
* traversal logic, making it complex to perform operations like transformations, filtering,
* or searching across blocks regardless of their containing field type.
*
* This utility abstracts away these differences, providing a consistent API to:
* - Visit/iterate through all blocks in any field type
* - Transform blocks while preserving field structure
* - Filter blocks based on conditions
* - Search for specific blocks
* - Perform functional operations (map, reduce, some, every)
*
* All functions come in both sync and async variants to support different use cases,
* particularly useful when block transformations require async operations like API calls.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
import { collectNodesAsync, filterNodesAsync, mapNodes, mapNodesAsync, } from 'datocms-structured-text-utils';
function iterateBlocksAsync(fieldType, nonLocalizedFieldValue) {
return __asyncGenerator(this, arguments, function* iterateBlocksAsync_1() {
if (fieldType === 'rich_text') {
const richTextValue = nonLocalizedFieldValue;
if (richTextValue) {
for (let index = 0; index < richTextValue.length; index++) {
const item = richTextValue[index];
yield yield __await({ item, path: [index] });
}
}
return yield __await(void 0);
}
if (fieldType === 'single_block') {
const singleBlockValue = nonLocalizedFieldValue;
if (singleBlockValue) {
yield yield __await({ item: singleBlockValue, path: [] });
}
return yield __await(void 0);
}
if (fieldType === 'structured_text') {
const structuredTextValue = nonLocalizedFieldValue;
if (structuredTextValue) {
const foundNodes = yield __await(collectNodesAsync(structuredTextValue.document, (node) => __awaiter(this, void 0, void 0, function* () { return node.type === 'block' || node.type === 'inlineBlock'; })));
for (const { node, path } of foundNodes) {
if (node.type === 'block' || node.type === 'inlineBlock') {
yield yield __await({ item: node.item, path });
}
}
}
return yield __await(void 0);
}
});
}
/**
* Visit every block in a field value, calling the visitor function for each block found.
* Supports rich text, single block, and structured text field types.
*
* @param fieldType - The type of DatoCMS field definition that determines how the value is processed
* @param nonLocalizedFieldValue - The non-localized field value containing blocks to visit
* @param visitor - Asynchronous function called for each block. Receives the block item and its path
* @returns Promise that resolves when all blocks have been visited
*/
export function nonRecursiveVisitBlocksInNonLocalizedFieldValueAsync(fieldType, nonLocalizedFieldValue, visitor) {
var _a, e_1, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
try {
for (var _d = true, _e = __asyncValues(iterateBlocksAsync(fieldType, nonLocalizedFieldValue)), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
_c = _f.value;
_d = false;
try {
const { item, path } = _c;
yield visitor(item, path);
}
finally {
_d = true;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
});
}
/**
* Transform blocks in a field value by applying a mapping function to each block.
* Creates a new field value structure with transformed blocks while preserving the original structure.
* Supports rich text, single block, and structured text field types.
*
* @param fieldType - The type of DatoCMS field definition that determines how the value is processed
* @param nonLocalizedFieldValue - The non-localized field value containing blocks to transform
* @param mapper - Synchronous function that transforms each block. Receives block item and path, returns new block
* @returns The new field value with transformed blocks
*/
export function nonRecursiveMapBlocksInNonLocalizedFieldValue(fieldType, nonLocalizedFieldValue, mapper) {
if (fieldType === 'rich_text') {
const richTextValue = nonLocalizedFieldValue;
return richTextValue
? richTextValue.map((item, index) => mapper(item, [index]))
: richTextValue;
}
if (fieldType === 'single_block') {
const singleBlockValue = nonLocalizedFieldValue;
return singleBlockValue ? mapper(singleBlockValue, []) : null;
}
if (fieldType === 'structured_text') {
const structuredTextValue = nonLocalizedFieldValue;
if (!structuredTextValue) {
return null;
}
return {
schema: 'dast',
document: mapNodes(structuredTextValue.document, (node, _parent, path) => {
if (node.type === 'block' || node.type === 'inlineBlock') {
return Object.assign(Object.assign({}, node), { item: mapper(node.item, path) });
}
return node;
}),
};
}
return nonLocalizedFieldValue;
}
/**
* Transform blocks in a field value by applying a mapping function to each block.
* Creates a new field value structure with transformed blocks while preserving the original structure.
* Supports rich text, single block, and structured text field types.
*
* @param fieldType - The type of DatoCMS field definition that determines how the value is processed
* @param nonLocalizedFieldValue - The non-localized field value containing blocks to transform
* @param mapper - Asynchronous function that transforms each block. Receives block item and path, returns new block
* @returns Promise that resolves to the new field value with transformed blocks
*/
export function nonRecursiveMapBlocksInNonLocalizedFieldValueAsync(fieldType, nonLocalizedFieldValue, mapper) {
return __awaiter(this, void 0, void 0, function* () {
if (fieldType === 'rich_text') {
const richTextValue = nonLocalizedFieldValue;
return richTextValue
? yield Promise.all(richTextValue.map((item, index) => mapper(item, [index])))
: richTextValue;
}
if (fieldType === 'single_block') {
const singleBlockValue = nonLocalizedFieldValue;
return singleBlockValue ? yield mapper(singleBlockValue, []) : null;
}
if (fieldType === 'structured_text') {
const structuredTextValue = nonLocalizedFieldValue;
if (!structuredTextValue) {
return null;
}
return {
schema: 'dast',
document: yield mapNodesAsync(structuredTextValue.document, (node, _parent, path) => __awaiter(this, void 0, void 0, function* () {
if (node.type === 'block' || node.type === 'inlineBlock') {
return Object.assign(Object.assign({}, node), { item: yield mapper(node.item, path) });
}
return node;
})),
};
}
return nonLocalizedFieldValue;
});
}
/**
* Find all blocks that match the predicate function.
* Searches through all blocks in the non-localized field value and returns all matches.
*
* @param fieldType - The type of DatoCMS field definition that determines how the value is processed
* @param nonLocalizedFieldValue - The non-localized field value containing blocks to search
* @param predicate - Asynchronous function that tests each block. Should return true for matching blocks
* @returns Promise that resolves to an array of objects, each containing a matching block and its path
*/
export function nonRecursiveFindAllBlocksInNonLocalizedFieldValueAsync(fieldType, nonLocalizedFieldValue, predicate) {
var _a, e_2, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const results = [];
try {
for (var _d = true, _e = __asyncValues(iterateBlocksAsync(fieldType, nonLocalizedFieldValue)), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
_c = _f.value;
_d = false;
try {
const { item, path } = _c;
if (yield predicate(item, path)) {
results.push({ item, path });
}
}
finally {
_d = true;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_2) throw e_2.error; }
}
return results;
});
}
/**
* Filter blocks in a field value, removing those that don't match the predicate.
* Creates a new field value containing only blocks that pass the predicate test.
* Preserves the original field value structure and hierarchy.
*
* @param fieldType - The type of DatoCMS field definition that determines how the value is processed
* @param nonLocalizedFieldValue - The non-localized field value containing blocks to filter
* @param predicate - Asynchronous function that tests each block. Blocks returning false are removed
* @returns Promise that resolves to the new field value with filtered blocks
*/
export function nonRecursiveFilterBlocksInNonLocalizedFieldValueAsync(fieldType, nonLocalizedFieldValue, predicate) {
var _a, e_3, _b, _c, _d, e_4, _e, _f;
return __awaiter(this, void 0, void 0, function* () {
if (fieldType === 'rich_text') {
const filteredItems = [];
try {
for (var _g = true, _h = __asyncValues(iterateBlocksAsync(fieldType, nonLocalizedFieldValue)), _j; _j = yield _h.next(), _a = _j.done, !_a;) {
_c = _j.value;
_g = false;
try {
const { item, path } = _c;
if (yield predicate(item, path)) {
filteredItems.push(item);
}
}
finally {
_g = true;
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (!_g && !_a && (_b = _h.return)) yield _b.call(_h);
}
finally { if (e_3) throw e_3.error; }
}
return filteredItems;
}
if (fieldType === 'single_block') {
try {
for (var _k = true, _l = __asyncValues(iterateBlocksAsync(fieldType, nonLocalizedFieldValue)), _m; _m = yield _l.next(), _d = _m.done, !_d;) {
_f = _m.value;
_k = false;
try {
const { item, path } = _f;
if (yield predicate(item, path)) {
return item;
}
}
finally {
_k = true;
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (!_k && !_d && (_e = _l.return)) yield _e.call(_l);
}
finally { if (e_4) throw e_4.error; }
}
return null;
}
if (fieldType === 'structured_text') {
const structuredTextValue = nonLocalizedFieldValue;
if (!structuredTextValue) {
return null;
}
const filteredDocument = yield filterNodesAsync(structuredTextValue.document, (node, _parent, path) => __awaiter(this, void 0, void 0, function* () {
if (node.type === 'block' || node.type === 'inlineBlock') {
return yield predicate(node.item, path);
}
return true;
}));
return filteredDocument
? {
schema: 'dast',
document: filteredDocument,
}
: null;
}
return nonLocalizedFieldValue;
});
}
/**
* Reduce all blocks in a field value to a single value by applying a reducer function.
* Processes each block in the non-localized field value and accumulates the results into a single value.
*
* @template R - The type of the accumulated result
* @param fieldType - The type of DatoCMS field definition that determines how the value is processed
* @param nonLocalizedFieldValue - The non-localized field value containing blocks to reduce
* @param reducer - Asynchronous function that processes each block and updates the accumulator
* @param initialValue - The initial value for the accumulator
* @returns Promise that resolves to the final accumulated value
*/
export function nonRecursiveReduceBlocksInNonLocalizedFieldValueAsync(fieldType, nonLocalizedFieldValue, reducer, initialValue) {
var _a, e_5, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
let accumulator = initialValue;
try {
for (var _d = true, _e = __asyncValues(iterateBlocksAsync(fieldType, nonLocalizedFieldValue)), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
_c = _f.value;
_d = false;
try {
const { item, path } = _c;
accumulator = yield reducer(accumulator, item, path);
}
finally {
_d = true;
}
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_5) throw e_5.error; }
}
return accumulator;
});
}
/**
* Check if any block in the non-localized field value matches the predicate function.
* Returns true as soon as the first matching block is found (short-circuit evaluation).
*
* @param fieldType - The type of DatoCMS field definition that determines how the value is processed
* @param nonLocalizedFieldValue - The non-localized field value containing blocks to test
* @param predicate - Asynchronous function that tests each block. Should return true for matching blocks
* @returns Promise that resolves to true if any block matches, false otherwise
*/
export function nonRecursiveSomeBlocksInNonLocalizedFieldValueAsync(fieldType, nonLocalizedFieldValue, predicate) {
var _a, e_6, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
try {
for (var _d = true, _e = __asyncValues(iterateBlocksAsync(fieldType, nonLocalizedFieldValue)), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
_c = _f.value;
_d = false;
try {
const { item, path } = _c;
if (yield predicate(item, path)) {
return true;
}
}
finally {
_d = true;
}
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_6) throw e_6.error; }
}
return false;
});
}
/**
* Check if every block in the non-localized field value matches the predicate function.
* Returns false as soon as the first non-matching block is found (short-circuit evaluation).
*
* @param fieldType - The type of DatoCMS field definition that determines how the value is processed
* @param nonLocalizedFieldValue - The non-localized field value containing blocks to test
* @param predicate - Asynchronous function that tests each block. Should return true for valid blocks
* @returns Promise that resolves to true if all blocks match, false otherwise
*/
export function nonRecursiveEveryBlockInNonLocalizedFieldValueAsync(fieldType, nonLocalizedFieldValue, predicate) {
return __awaiter(this, void 0, void 0, function* () {
return !(yield nonRecursiveSomeBlocksInNonLocalizedFieldValueAsync(fieldType, nonLocalizedFieldValue, (item, path) => __awaiter(this, void 0, void 0, function* () {
return !(yield predicate(item, path));
})));
});
}
//# sourceMappingURL=nonRecursiveBlocks.js.map