transform.ts
1 /** 2 * Pipeline steps: data transforms — select, map, filter, sort, limit. 3 */ 4 5 import type { IPage } from '../../types.js'; 6 import { render, evalExpr } from '../template.js'; 7 8 import { isRecord } from '../../utils.js'; 9 10 export async function stepSelect(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 11 const pathStr = String(render(params, { args, data })); 12 if (data && typeof data === 'object') { 13 let current: unknown = data; 14 for (const part of pathStr.split('.')) { 15 if (isRecord(current)) current = current[part]; 16 else if (Array.isArray(current) && /^\d+$/.test(part)) current = current[parseInt(part, 10)]; 17 else return null; 18 } 19 return current; 20 } 21 return data; 22 } 23 24 export async function stepMap(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 25 if (!data || typeof data !== 'object') return data; 26 let source: unknown = data; 27 28 // Support inline select: { map: { select: 'path', key: '${{ item.x }}' } } 29 if (isRecord(params) && 'select' in params) { 30 source = await stepSelect(null, params.select, data, args); 31 } 32 33 if (!source || typeof source !== 'object') return source; 34 35 let items: unknown[] = Array.isArray(source) ? source : [source]; 36 if (isRecord(source) && Array.isArray(source.data)) items = source.data; 37 const result: Array<Record<string, unknown>> = []; 38 const templateParams = isRecord(params) ? params : {}; 39 for (let i = 0; i < items.length; i++) { 40 const item = items[i]; 41 const row: Record<string, unknown> = {}; 42 for (const [key, template] of Object.entries(templateParams)) { 43 if (key === 'select') continue; 44 row[key] = render(template, { args, data: source, root: data, item, index: i }); 45 } 46 result.push(row); 47 } 48 return result; 49 } 50 51 export async function stepFilter(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 52 if (!Array.isArray(data)) return data; 53 return data.filter((item, i) => evalExpr(String(params), { args, item, index: i })); 54 } 55 56 export async function stepSort(_page: IPage | null, params: unknown, data: unknown, _args: Record<string, unknown>): Promise<unknown> { 57 if (!Array.isArray(data)) return data; 58 const key = isRecord(params) ? String(params.by ?? '') : String(params); 59 const reverse = isRecord(params) ? params.order === 'desc' : false; 60 return [...data].sort((a, b) => { 61 const left = isRecord(a) ? a[key] : undefined; 62 const right = isRecord(b) ? b[key] : undefined; 63 const cmp = String(left ?? '').localeCompare(String(right ?? ''), undefined, { numeric: true }); 64 return reverse ? -cmp : cmp; 65 }); 66 } 67 68 export async function stepLimit(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 69 if (!Array.isArray(data)) return data; 70 return data.slice(0, Number(render(params, { args, data }))); 71 }