@narrative.io/jsonforms-provider-protocols 1.2.0-beta.1 → 1.2.0-beta.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.
package/README.md CHANGED
@@ -141,6 +141,94 @@ const CustomProtocol: Protocol = {
141
141
  }
142
142
  ```
143
143
 
144
+ ### Data Transforms
145
+ Transform API response data before mapping to form items using a pipeline of transforms:
146
+
147
+ ```json
148
+ {
149
+ "provider": {
150
+ "protocol": "rest_api",
151
+ "config": {
152
+ "url": "https://api.example.com/data",
153
+ "items": "$.data[*]",
154
+ "transforms": [
155
+ {
156
+ "name": "flatten",
157
+ "key": "children",
158
+ "labelFormat": "{parent.name} → {name}"
159
+ },
160
+ {
161
+ "name": "filter",
162
+ "key": "active",
163
+ "values": [true]
164
+ }
165
+ ],
166
+ "map": {
167
+ "label": "$.name",
168
+ "value": "$.id"
169
+ }
170
+ }
171
+ }
172
+ }
173
+ ```
174
+
175
+ #### Built-in Transforms
176
+
177
+ **Flatten Transform**
178
+ Recursively flattens nested tree structures into a single-level array:
179
+
180
+ ```json
181
+ {
182
+ "name": "flatten",
183
+ "key": "children",
184
+ "labelFormat": "{parent.name} → {name}"
185
+ }
186
+ ```
187
+
188
+ - `key`: The property containing nested children arrays
189
+ - `labelFormat` (optional): Template for formatting labels using parent and child properties
190
+ - Adds `_depth`, `_parent`, and `_formattedLabel` metadata to items
191
+
192
+ **Filter Transform**
193
+ Filters items based on property values:
194
+
195
+ ```json
196
+ {
197
+ "name": "filter",
198
+ "key": "category",
199
+ "values": ["A", "B"]
200
+ }
201
+ ```
202
+
203
+ - `key`: The property to check
204
+ - `values` (optional): Array of values to match. If omitted, filters by key existence
205
+
206
+ **Combining Transforms**
207
+ Transforms are applied sequentially in pipeline order:
208
+
209
+ ```json
210
+ {
211
+ "transforms": [
212
+ { "name": "flatten", "key": "children" },
213
+ { "name": "filter", "key": "type", "values": ["product"] }
214
+ ]
215
+ }
216
+ ```
217
+
218
+ **Custom Transforms**
219
+ Register custom transforms for your specific needs:
220
+
221
+ ```typescript
222
+ import { registerTransform } from '@narrative.io/jsonforms-provider-protocols'
223
+
224
+ registerTransform('uppercase', (items, config) => {
225
+ return items.map(item => ({
226
+ ...item,
227
+ name: item.name.toUpperCase()
228
+ }))
229
+ })
230
+ ```
231
+
144
232
  ### Template Variables
145
233
  Create dynamic URLs using form data:
146
234
 
@@ -11,7 +11,12 @@ export interface FlattenTransform extends Transform {
11
11
  key: string;
12
12
  labelFormat?: string;
13
13
  }
14
- export type TransformStep = FlattenTransform;
14
+ export interface FilterTransform extends Transform {
15
+ name: "filter";
16
+ key: string;
17
+ values?: unknown[];
18
+ }
19
+ export type TransformStep = FlattenTransform | FilterTransform;
15
20
  export type TransformPipeline = TransformStep[];
16
21
  /**
17
22
  * Registry of transform functions
@@ -1 +1 @@
1
- {"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../../src/core/transforms.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,aAAa,GAAG,gBAAgB,CAAC;AAE7C,MAAM,MAAM,iBAAiB,GAAG,aAAa,EAAE,CAAC;AAEhD;;GAEG;AACH,KAAK,iBAAiB,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,EAAE,CAAC;AAI5E;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAE3E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EAAE,EAChB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,EAAE,CAYX"}
1
+ {"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../../src/core/transforms.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAE/D,MAAM,MAAM,iBAAiB,GAAG,aAAa,EAAE,CAAC;AAEhD;;GAEG;AACH,KAAK,iBAAiB,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,EAAE,CAAC;AAI5E;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAE3E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EAAE,EAChB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,EAAE,CAYX"}
@@ -17,40 +17,63 @@ function flattenTransform(items, config) {
17
17
  const flattenConfig = config;
18
18
  const { key, labelFormat } = flattenConfig;
19
19
  const flattened = [];
20
- for (const item of items) {
21
- if (typeof item !== "object" || item === null) continue;
20
+ function flattenRecursive(item, parent = null, depth = 0) {
21
+ if (typeof item !== "object" || item === null) return;
22
22
  const itemObj = item;
23
+ if (labelFormat && parent) {
24
+ const formattedItem = { ...itemObj };
25
+ let formattedLabel = labelFormat;
26
+ formattedLabel = formattedLabel.replace(
27
+ /\{parent\.(\w+)\}/g,
28
+ (_, prop) => String(parent[prop] ?? "")
29
+ );
30
+ formattedLabel = formattedLabel.replace(
31
+ /\{(\w+)\}/g,
32
+ (_, prop) => String(itemObj[prop] ?? "")
33
+ );
34
+ formattedItem._formattedLabel = formattedLabel;
35
+ formattedItem._parent = parent;
36
+ formattedItem._depth = depth;
37
+ flattened.push(formattedItem);
38
+ } else if (parent) {
39
+ flattened.push({
40
+ ...itemObj,
41
+ _parent: parent,
42
+ _depth: depth
43
+ });
44
+ } else {
45
+ flattened.push({
46
+ ...itemObj,
47
+ _depth: depth
48
+ });
49
+ }
23
50
  const children = itemObj[key];
24
51
  if (Array.isArray(children)) {
25
52
  for (const child of children) {
26
- if (typeof child !== "object" || child === null) continue;
27
- const childObj = child;
28
- if (labelFormat) {
29
- const formattedChild = { ...childObj };
30
- let formattedLabel = labelFormat;
31
- formattedLabel = formattedLabel.replace(
32
- /\{parent\.(\w+)\}/g,
33
- (_, prop) => String(itemObj[prop] ?? "")
34
- );
35
- formattedLabel = formattedLabel.replace(
36
- /\{(\w+)\}/g,
37
- (_, prop) => String(childObj[prop] ?? "")
38
- );
39
- formattedChild._formattedLabel = formattedLabel;
40
- formattedChild._parent = itemObj;
41
- flattened.push(formattedChild);
42
- } else {
43
- flattened.push({
44
- ...childObj,
45
- _parent: itemObj
46
- });
47
- }
53
+ flattenRecursive(child, itemObj, depth + 1);
48
54
  }
49
55
  }
50
56
  }
57
+ for (const item of items) {
58
+ flattenRecursive(item, null, 0);
59
+ }
51
60
  return flattened;
52
61
  }
62
+ function filterTransform(items, config) {
63
+ const filterConfig = config;
64
+ const { key, values } = filterConfig;
65
+ return items.filter((item) => {
66
+ if (typeof item !== "object" || item === null) return false;
67
+ const itemObj = item;
68
+ if (!values || values.length === 0) {
69
+ return key in itemObj;
70
+ }
71
+ const itemValue = itemObj[key];
72
+ return values.includes(itemValue);
73
+ });
74
+ }
53
75
  registerTransform("flatten", flattenTransform);
76
+ registerTransform("filter", filterTransform);
54
77
  export {
55
78
  applyTransformPipeline,
56
79
  registerTransform
@@ -1 +1 @@
1
- {"version":3,"file":"transforms.js","sources":["../../src/core/transforms.ts"],"sourcesContent":["/**\n * Transform pipeline system for manipulating API response data\n * Transforms are applied sequentially in the order they appear in the pipeline\n */\n\nexport interface Transform {\n name: string;\n [key: string]: unknown;\n}\n\nexport interface FlattenTransform extends Transform {\n name: \"flatten\";\n key: string; // The key containing the nested array to flatten\n labelFormat?: string; // Optional format string like \"{parent.name} → {name}\"\n}\n\nexport type TransformStep = FlattenTransform;\n\nexport type TransformPipeline = TransformStep[];\n\n/**\n * Registry of transform functions\n */\ntype TransformFunction = (items: unknown[], config: Transform) => unknown[];\n\nconst transformRegistry: Record<string, TransformFunction> = {};\n\n/**\n * Register a transform function\n */\nexport function registerTransform(name: string, fn: TransformFunction): void {\n transformRegistry[name] = fn;\n}\n\n/**\n * Apply a pipeline of transforms to data\n */\nexport function applyTransformPipeline(\n items: unknown[],\n pipeline: TransformPipeline,\n): unknown[] {\n let result = items;\n\n for (const transform of pipeline) {\n const fn = transformRegistry[transform.name];\n if (!fn) {\n throw new Error(`Unknown transform: ${transform.name}`);\n }\n result = fn(result, transform);\n }\n\n return result;\n}\n\n/**\n * Flatten transform - flattens nested arrays into a single level\n */\nfunction flattenTransform(items: unknown[], config: Transform): unknown[] {\n const flattenConfig = config as FlattenTransform;\n const { key, labelFormat } = flattenConfig;\n const flattened: unknown[] = [];\n\n for (const item of items) {\n if (typeof item !== \"object\" || item === null) continue;\n\n const itemObj = item as Record<string, unknown>;\n const children = itemObj[key];\n\n if (Array.isArray(children)) {\n for (const child of children) {\n if (typeof child !== \"object\" || child === null) continue;\n\n const childObj = child as Record<string, unknown>;\n\n // If labelFormat is provided, use it to format the label\n if (labelFormat) {\n const formattedChild = { ...childObj };\n\n // Replace placeholders like {parent.name} and {name}\n let formattedLabel = labelFormat;\n formattedLabel = formattedLabel.replace(\n /\\{parent\\.(\\w+)\\}/g,\n (_, prop) => String(itemObj[prop] ?? \"\"),\n );\n formattedLabel = formattedLabel.replace(/\\{(\\w+)\\}/g, (_, prop) =>\n String(childObj[prop] ?? \"\"),\n );\n\n formattedChild._formattedLabel = formattedLabel;\n formattedChild._parent = itemObj;\n flattened.push(formattedChild);\n } else {\n // Just add parent reference\n flattened.push({\n ...childObj,\n _parent: itemObj,\n });\n }\n }\n }\n }\n\n return flattened;\n}\n\n// Register built-in transforms\nregisterTransform(\"flatten\", flattenTransform);\n"],"names":[],"mappings":"AAyBA,MAAM,oBAAuD,CAAA;AAKtD,SAAS,kBAAkB,MAAc,IAA6B;AAC3E,oBAAkB,IAAI,IAAI;AAC5B;AAKO,SAAS,uBACd,OACA,UACW;AACX,MAAI,SAAS;AAEb,aAAW,aAAa,UAAU;AAChC,UAAM,KAAK,kBAAkB,UAAU,IAAI;AAC3C,QAAI,CAAC,IAAI;AACP,YAAM,IAAI,MAAM,sBAAsB,UAAU,IAAI,EAAE;AAAA,IACxD;AACA,aAAS,GAAG,QAAQ,SAAS;AAAA,EAC/B;AAEA,SAAO;AACT;AAKA,SAAS,iBAAiB,OAAkB,QAA8B;AACxE,QAAM,gBAAgB;AACtB,QAAM,EAAE,KAAK,YAAA,IAAgB;AAC7B,QAAM,YAAuB,CAAA;AAE7B,aAAW,QAAQ,OAAO;AACxB,QAAI,OAAO,SAAS,YAAY,SAAS,KAAM;AAE/C,UAAM,UAAU;AAChB,UAAM,WAAW,QAAQ,GAAG;AAE5B,QAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,iBAAW,SAAS,UAAU;AAC5B,YAAI,OAAO,UAAU,YAAY,UAAU,KAAM;AAEjD,cAAM,WAAW;AAGjB,YAAI,aAAa;AACf,gBAAM,iBAAiB,EAAE,GAAG,SAAA;AAG5B,cAAI,iBAAiB;AACrB,2BAAiB,eAAe;AAAA,YAC9B;AAAA,YACA,CAAC,GAAG,SAAS,OAAO,QAAQ,IAAI,KAAK,EAAE;AAAA,UAAA;AAEzC,2BAAiB,eAAe;AAAA,YAAQ;AAAA,YAAc,CAAC,GAAG,SACxD,OAAO,SAAS,IAAI,KAAK,EAAE;AAAA,UAAA;AAG7B,yBAAe,kBAAkB;AACjC,yBAAe,UAAU;AACzB,oBAAU,KAAK,cAAc;AAAA,QAC/B,OAAO;AAEL,oBAAU,KAAK;AAAA,YACb,GAAG;AAAA,YACH,SAAS;AAAA,UAAA,CACV;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGA,kBAAkB,WAAW,gBAAgB;"}
1
+ {"version":3,"file":"transforms.js","sources":["../../src/core/transforms.ts"],"sourcesContent":["/**\n * Transform pipeline system for manipulating API response data\n * Transforms are applied sequentially in the order they appear in the pipeline\n */\n\nexport interface Transform {\n name: string;\n [key: string]: unknown;\n}\n\nexport interface FlattenTransform extends Transform {\n name: \"flatten\";\n key: string; // The key containing the nested array to flatten\n labelFormat?: string; // Optional format string like \"{parent.name} → {name}\"\n}\n\nexport interface FilterTransform extends Transform {\n name: \"filter\";\n key: string; // The key to check\n values?: unknown[]; // Optional array of values to match against\n}\n\nexport type TransformStep = FlattenTransform | FilterTransform;\n\nexport type TransformPipeline = TransformStep[];\n\n/**\n * Registry of transform functions\n */\ntype TransformFunction = (items: unknown[], config: Transform) => unknown[];\n\nconst transformRegistry: Record<string, TransformFunction> = {};\n\n/**\n * Register a transform function\n */\nexport function registerTransform(name: string, fn: TransformFunction): void {\n transformRegistry[name] = fn;\n}\n\n/**\n * Apply a pipeline of transforms to data\n */\nexport function applyTransformPipeline(\n items: unknown[],\n pipeline: TransformPipeline,\n): unknown[] {\n let result = items;\n\n for (const transform of pipeline) {\n const fn = transformRegistry[transform.name];\n if (!fn) {\n throw new Error(`Unknown transform: ${transform.name}`);\n }\n result = fn(result, transform);\n }\n\n return result;\n}\n\n/**\n * Flatten transform - recursively flattens nested arrays into a single level\n */\nfunction flattenTransform(items: unknown[], config: Transform): unknown[] {\n const flattenConfig = config as FlattenTransform;\n const { key, labelFormat } = flattenConfig;\n const flattened: unknown[] = [];\n\n function flattenRecursive(\n item: unknown,\n parent: Record<string, unknown> | null = null,\n depth: number = 0,\n ): void {\n if (typeof item !== \"object\" || item === null) return;\n\n const itemObj = item as Record<string, unknown>;\n\n // Add the current item\n if (labelFormat && parent) {\n const formattedItem = { ...itemObj };\n\n // Replace placeholders like {parent.name} and {name}\n let formattedLabel = labelFormat;\n formattedLabel = formattedLabel.replace(/\\{parent\\.(\\w+)\\}/g, (_, prop) =>\n String(parent[prop] ?? \"\"),\n );\n formattedLabel = formattedLabel.replace(/\\{(\\w+)\\}/g, (_, prop) =>\n String(itemObj[prop] ?? \"\"),\n );\n\n formattedItem._formattedLabel = formattedLabel;\n formattedItem._parent = parent;\n formattedItem._depth = depth;\n flattened.push(formattedItem);\n } else if (parent) {\n // Child node with parent reference\n flattened.push({\n ...itemObj,\n _parent: parent,\n _depth: depth,\n });\n } else {\n // Root node\n flattened.push({\n ...itemObj,\n _depth: depth,\n });\n }\n\n // Recursively flatten children\n const children = itemObj[key];\n if (Array.isArray(children)) {\n for (const child of children) {\n flattenRecursive(child, itemObj, depth + 1);\n }\n }\n }\n\n // Start flattening from root items\n for (const item of items) {\n flattenRecursive(item, null, 0);\n }\n\n return flattened;\n}\n\n/**\n * Filter transform - filters items based on a key and optional values\n */\nfunction filterTransform(items: unknown[], config: Transform): unknown[] {\n const filterConfig = config as FilterTransform;\n const { key, values } = filterConfig;\n\n return items.filter((item) => {\n if (typeof item !== \"object\" || item === null) return false;\n\n const itemObj = item as Record<string, unknown>;\n\n // If no values array provided, just check if the key exists\n if (!values || values.length === 0) {\n return key in itemObj;\n }\n\n // If values array provided, check if item[key] matches any of the values\n const itemValue = itemObj[key];\n return values.includes(itemValue);\n });\n}\n\n// Register built-in transforms\nregisterTransform(\"flatten\", flattenTransform);\nregisterTransform(\"filter\", filterTransform);\n"],"names":[],"mappings":"AA+BA,MAAM,oBAAuD,CAAA;AAKtD,SAAS,kBAAkB,MAAc,IAA6B;AAC3E,oBAAkB,IAAI,IAAI;AAC5B;AAKO,SAAS,uBACd,OACA,UACW;AACX,MAAI,SAAS;AAEb,aAAW,aAAa,UAAU;AAChC,UAAM,KAAK,kBAAkB,UAAU,IAAI;AAC3C,QAAI,CAAC,IAAI;AACP,YAAM,IAAI,MAAM,sBAAsB,UAAU,IAAI,EAAE;AAAA,IACxD;AACA,aAAS,GAAG,QAAQ,SAAS;AAAA,EAC/B;AAEA,SAAO;AACT;AAKA,SAAS,iBAAiB,OAAkB,QAA8B;AACxE,QAAM,gBAAgB;AACtB,QAAM,EAAE,KAAK,YAAA,IAAgB;AAC7B,QAAM,YAAuB,CAAA;AAE7B,WAAS,iBACP,MACA,SAAyC,MACzC,QAAgB,GACV;AACN,QAAI,OAAO,SAAS,YAAY,SAAS,KAAM;AAE/C,UAAM,UAAU;AAGhB,QAAI,eAAe,QAAQ;AACzB,YAAM,gBAAgB,EAAE,GAAG,QAAA;AAG3B,UAAI,iBAAiB;AACrB,uBAAiB,eAAe;AAAA,QAAQ;AAAA,QAAsB,CAAC,GAAG,SAChE,OAAO,OAAO,IAAI,KAAK,EAAE;AAAA,MAAA;AAE3B,uBAAiB,eAAe;AAAA,QAAQ;AAAA,QAAc,CAAC,GAAG,SACxD,OAAO,QAAQ,IAAI,KAAK,EAAE;AAAA,MAAA;AAG5B,oBAAc,kBAAkB;AAChC,oBAAc,UAAU;AACxB,oBAAc,SAAS;AACvB,gBAAU,KAAK,aAAa;AAAA,IAC9B,WAAW,QAAQ;AAEjB,gBAAU,KAAK;AAAA,QACb,GAAG;AAAA,QACH,SAAS;AAAA,QACT,QAAQ;AAAA,MAAA,CACT;AAAA,IACH,OAAO;AAEL,gBAAU,KAAK;AAAA,QACb,GAAG;AAAA,QACH,QAAQ;AAAA,MAAA,CACT;AAAA,IACH;AAGA,UAAM,WAAW,QAAQ,GAAG;AAC5B,QAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,iBAAW,SAAS,UAAU;AAC5B,yBAAiB,OAAO,SAAS,QAAQ,CAAC;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAGA,aAAW,QAAQ,OAAO;AACxB,qBAAiB,MAAM,MAAM,CAAC;AAAA,EAChC;AAEA,SAAO;AACT;AAKA,SAAS,gBAAgB,OAAkB,QAA8B;AACvE,QAAM,eAAe;AACrB,QAAM,EAAE,KAAK,OAAA,IAAW;AAExB,SAAO,MAAM,OAAO,CAAC,SAAS;AAC5B,QAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AAEtD,UAAM,UAAU;AAGhB,QAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,aAAO,OAAO;AAAA,IAChB;AAGA,UAAM,YAAY,QAAQ,GAAG;AAC7B,WAAO,OAAO,SAAS,SAAS;AAAA,EAClC,CAAC;AACH;AAGA,kBAAkB,WAAW,gBAAgB;AAC7C,kBAAkB,UAAU,eAAe;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/jsonforms-provider-protocols",
3
- "version": "1.2.0-beta.1",
3
+ "version": "1.2.0-beta.3",
4
4
  "description": "Dynamic data provider capabilities for JSONForms with Vue 3 integration",
5
5
  "type": "module",
6
6
  "author": "Narrative I/O",
@@ -14,7 +14,13 @@ export interface FlattenTransform extends Transform {
14
14
  labelFormat?: string; // Optional format string like "{parent.name} → {name}"
15
15
  }
16
16
 
17
- export type TransformStep = FlattenTransform;
17
+ export interface FilterTransform extends Transform {
18
+ name: "filter";
19
+ key: string; // The key to check
20
+ values?: unknown[]; // Optional array of values to match against
21
+ }
22
+
23
+ export type TransformStep = FlattenTransform | FilterTransform;
18
24
 
19
25
  export type TransformPipeline = TransformStep[];
20
26
 
@@ -53,55 +59,94 @@ export function applyTransformPipeline(
53
59
  }
54
60
 
55
61
  /**
56
- * Flatten transform - flattens nested arrays into a single level
62
+ * Flatten transform - recursively flattens nested arrays into a single level
57
63
  */
58
64
  function flattenTransform(items: unknown[], config: Transform): unknown[] {
59
65
  const flattenConfig = config as FlattenTransform;
60
66
  const { key, labelFormat } = flattenConfig;
61
67
  const flattened: unknown[] = [];
62
68
 
63
- for (const item of items) {
64
- if (typeof item !== "object" || item === null) continue;
69
+ function flattenRecursive(
70
+ item: unknown,
71
+ parent: Record<string, unknown> | null = null,
72
+ depth: number = 0,
73
+ ): void {
74
+ if (typeof item !== "object" || item === null) return;
65
75
 
66
76
  const itemObj = item as Record<string, unknown>;
67
- const children = itemObj[key];
68
77
 
78
+ // Add the current item
79
+ if (labelFormat && parent) {
80
+ const formattedItem = { ...itemObj };
81
+
82
+ // Replace placeholders like {parent.name} and {name}
83
+ let formattedLabel = labelFormat;
84
+ formattedLabel = formattedLabel.replace(/\{parent\.(\w+)\}/g, (_, prop) =>
85
+ String(parent[prop] ?? ""),
86
+ );
87
+ formattedLabel = formattedLabel.replace(/\{(\w+)\}/g, (_, prop) =>
88
+ String(itemObj[prop] ?? ""),
89
+ );
90
+
91
+ formattedItem._formattedLabel = formattedLabel;
92
+ formattedItem._parent = parent;
93
+ formattedItem._depth = depth;
94
+ flattened.push(formattedItem);
95
+ } else if (parent) {
96
+ // Child node with parent reference
97
+ flattened.push({
98
+ ...itemObj,
99
+ _parent: parent,
100
+ _depth: depth,
101
+ });
102
+ } else {
103
+ // Root node
104
+ flattened.push({
105
+ ...itemObj,
106
+ _depth: depth,
107
+ });
108
+ }
109
+
110
+ // Recursively flatten children
111
+ const children = itemObj[key];
69
112
  if (Array.isArray(children)) {
70
113
  for (const child of children) {
71
- if (typeof child !== "object" || child === null) continue;
72
-
73
- const childObj = child as Record<string, unknown>;
74
-
75
- // If labelFormat is provided, use it to format the label
76
- if (labelFormat) {
77
- const formattedChild = { ...childObj };
78
-
79
- // Replace placeholders like {parent.name} and {name}
80
- let formattedLabel = labelFormat;
81
- formattedLabel = formattedLabel.replace(
82
- /\{parent\.(\w+)\}/g,
83
- (_, prop) => String(itemObj[prop] ?? ""),
84
- );
85
- formattedLabel = formattedLabel.replace(/\{(\w+)\}/g, (_, prop) =>
86
- String(childObj[prop] ?? ""),
87
- );
88
-
89
- formattedChild._formattedLabel = formattedLabel;
90
- formattedChild._parent = itemObj;
91
- flattened.push(formattedChild);
92
- } else {
93
- // Just add parent reference
94
- flattened.push({
95
- ...childObj,
96
- _parent: itemObj,
97
- });
98
- }
114
+ flattenRecursive(child, itemObj, depth + 1);
99
115
  }
100
116
  }
101
117
  }
102
118
 
119
+ // Start flattening from root items
120
+ for (const item of items) {
121
+ flattenRecursive(item, null, 0);
122
+ }
123
+
103
124
  return flattened;
104
125
  }
105
126
 
127
+ /**
128
+ * Filter transform - filters items based on a key and optional values
129
+ */
130
+ function filterTransform(items: unknown[], config: Transform): unknown[] {
131
+ const filterConfig = config as FilterTransform;
132
+ const { key, values } = filterConfig;
133
+
134
+ return items.filter((item) => {
135
+ if (typeof item !== "object" || item === null) return false;
136
+
137
+ const itemObj = item as Record<string, unknown>;
138
+
139
+ // If no values array provided, just check if the key exists
140
+ if (!values || values.length === 0) {
141
+ return key in itemObj;
142
+ }
143
+
144
+ // If values array provided, check if item[key] matches any of the values
145
+ const itemValue = itemObj[key];
146
+ return values.includes(itemValue);
147
+ });
148
+ }
149
+
106
150
  // Register built-in transforms
107
151
  registerTransform("flatten", flattenTransform);
152
+ registerTransform("filter", filterTransform);