@narrative.io/jsonforms-provider-protocols 1.2.0-beta.2 → 1.2.0

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"}
@@ -59,7 +59,21 @@ function flattenTransform(items, config) {
59
59
  }
60
60
  return flattened;
61
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
+ }
62
75
  registerTransform("flatten", flattenTransform);
76
+ registerTransform("filter", filterTransform);
63
77
  export {
64
78
  applyTransformPipeline,
65
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 - 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// 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,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;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.2",
3
+ "version": "1.2.0",
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
 
@@ -118,5 +124,29 @@ function flattenTransform(items: unknown[], config: Transform): unknown[] {
118
124
  return flattened;
119
125
  }
120
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
+
121
150
  // Register built-in transforms
122
151
  registerTransform("flatten", flattenTransform);
152
+ registerTransform("filter", filterTransform);