UNPKG

@datocms/cma-client

Version:
412 lines 19.9 kB
/** * 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