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

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.
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Transform pipeline system for manipulating API response data
3
+ * Transforms are applied sequentially in the order they appear in the pipeline
4
+ */
5
+ export interface Transform {
6
+ name: string;
7
+ [key: string]: unknown;
8
+ }
9
+ export interface FlattenTransform extends Transform {
10
+ name: "flatten";
11
+ key: string;
12
+ labelFormat?: string;
13
+ }
14
+ export type TransformStep = FlattenTransform;
15
+ export type TransformPipeline = TransformStep[];
16
+ /**
17
+ * Registry of transform functions
18
+ */
19
+ type TransformFunction = (items: unknown[], config: Transform) => unknown[];
20
+ /**
21
+ * Register a transform function
22
+ */
23
+ export declare function registerTransform(name: string, fn: TransformFunction): void;
24
+ /**
25
+ * Apply a pipeline of transforms to data
26
+ */
27
+ export declare function applyTransformPipeline(items: unknown[], pipeline: TransformPipeline): unknown[];
28
+ export {};
29
+ //# sourceMappingURL=transforms.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,58 @@
1
+ const transformRegistry = {};
2
+ function registerTransform(name, fn) {
3
+ transformRegistry[name] = fn;
4
+ }
5
+ function applyTransformPipeline(items, pipeline) {
6
+ let result = items;
7
+ for (const transform of pipeline) {
8
+ const fn = transformRegistry[transform.name];
9
+ if (!fn) {
10
+ throw new Error(`Unknown transform: ${transform.name}`);
11
+ }
12
+ result = fn(result, transform);
13
+ }
14
+ return result;
15
+ }
16
+ function flattenTransform(items, config) {
17
+ const flattenConfig = config;
18
+ const { key, labelFormat } = flattenConfig;
19
+ const flattened = [];
20
+ for (const item of items) {
21
+ if (typeof item !== "object" || item === null) continue;
22
+ const itemObj = item;
23
+ const children = itemObj[key];
24
+ if (Array.isArray(children)) {
25
+ 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
+ }
48
+ }
49
+ }
50
+ }
51
+ return flattened;
52
+ }
53
+ registerTransform("flatten", flattenTransform);
54
+ export {
55
+ applyTransformPipeline,
56
+ registerTransform
57
+ };
58
+ //# sourceMappingURL=transforms.js.map
@@ -0,0 +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;"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { cache } from "./core/cache";
4
4
  export * from "./core/jsonpath";
5
5
  export { registry } from "./core/registry";
6
6
  export * from "./core/templating";
7
+ export * from "./core/transforms";
7
8
  export * from "./core/types";
8
9
  export { RestApiProtocol } from "./protocols/rest_api";
9
10
  export { providerRenderers, primevueRenderers, ProviderAutocomplete, ProviderSelect, ProviderMultiSelect, useProvider, JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean, } from "./vue";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAElC,cAAc,cAAc,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,OAAO,CAAC;AAEf,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;;iBAGc,GAAG,SAAS,cAAc;;AADzC,wBAYE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAElC,cAAc,cAAc,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,OAAO,CAAC;AAEf,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;;iBAGc,GAAG,SAAS,cAAc;;AADzC,wBAYE"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { cache } from "./core/cache.js";
2
2
  import { registry } from "./core/registry.js";
3
3
  import { jp } from "./core/jsonpath.js";
4
4
  import { renderObj, renderTpl } from "./core/templating.js";
5
+ import { applyTransformPipeline, registerTransform } from "./core/transforms.js";
5
6
  import { RestApiProtocol } from "./protocols/rest_api.js";
6
7
  import { providerRenderers } from "./vue/index.js";
7
8
  import { default as default2 } from "./vue/components/ProviderAutocomplete.vue.js";
@@ -39,11 +40,13 @@ export {
39
40
  default10 as ProviderMultiSelect,
40
41
  default9 as ProviderSelect,
41
42
  RestApiProtocol,
43
+ applyTransformPipeline,
42
44
  cache,
43
45
  index as default,
44
46
  jp,
45
47
  primevueRenderers,
46
48
  providerRenderers,
49
+ registerTransform,
47
50
  registry,
48
51
  renderObj,
49
52
  renderTpl,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { App } from \"vue\";\nimport { cache as globalCache } from \"./core/cache\";\nimport { registry as globalRegistry } from \"./core/registry\";\nimport type { Protocol, AuthConfig } from \"./core/types\";\n\nexport { cache } from \"./core/cache\";\nexport * from \"./core/jsonpath\";\nexport { registry } from \"./core/registry\";\nexport * from \"./core/templating\";\n// Core exports\nexport * from \"./core/types\";\n\n// Protocol exports\nexport { RestApiProtocol } from \"./protocols/rest_api\";\n\n// Vue exports - using named imports to avoid potential bundling issues\nexport {\n providerRenderers,\n primevueRenderers,\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n useProvider,\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./vue\";\n\nexport interface ProviderConfig {\n protocols?: Protocol[];\n auth?: AuthConfig;\n}\n\nexport default {\n install(app: App, opts?: ProviderConfig) {\n const reg = globalRegistry;\n if (opts?.protocols) {\n for (const p of opts.protocols) {\n reg.register(p);\n }\n }\n app.provide(\"providerRegistry\", reg);\n app.provide(\"providerCache\", globalCache);\n app.provide(\"providerAuth\", opts?.auth ?? {});\n },\n};\n"],"names":["globalRegistry","globalCache"],"mappings":";;;;;;;;;;;;;;;;;AAoCA,MAAA,QAAe;AAAA,EACb,QAAQ,KAAU,MAAuB;AACvC,UAAM,MAAMA;AACZ,QAAI,MAAM,WAAW;AACnB,iBAAW,KAAK,KAAK,WAAW;AAC9B,YAAI,SAAS,CAAC;AAAA,MAChB;AAAA,IACF;AACA,QAAI,QAAQ,oBAAoB,GAAG;AACnC,QAAI,QAAQ,iBAAiBC,KAAW;AACxC,QAAI,QAAQ,gBAAgB,MAAM,QAAQ,CAAA,CAAE;AAAA,EAC9C;AACF;"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { App } from \"vue\";\nimport { cache as globalCache } from \"./core/cache\";\nimport { registry as globalRegistry } from \"./core/registry\";\nimport type { Protocol, AuthConfig } from \"./core/types\";\n\nexport { cache } from \"./core/cache\";\nexport * from \"./core/jsonpath\";\nexport { registry } from \"./core/registry\";\nexport * from \"./core/templating\";\nexport * from \"./core/transforms\";\n// Core exports\nexport * from \"./core/types\";\n\n// Protocol exports\nexport { RestApiProtocol } from \"./protocols/rest_api\";\n\n// Vue exports - using named imports to avoid potential bundling issues\nexport {\n providerRenderers,\n primevueRenderers,\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n useProvider,\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./vue\";\n\nexport interface ProviderConfig {\n protocols?: Protocol[];\n auth?: AuthConfig;\n}\n\nexport default {\n install(app: App, opts?: ProviderConfig) {\n const reg = globalRegistry;\n if (opts?.protocols) {\n for (const p of opts.protocols) {\n reg.register(p);\n }\n }\n app.provide(\"providerRegistry\", reg);\n app.provide(\"providerCache\", globalCache);\n app.provide(\"providerAuth\", opts?.auth ?? {});\n },\n};\n"],"names":["globalRegistry","globalCache"],"mappings":";;;;;;;;;;;;;;;;;;AAqCA,MAAA,QAAe;AAAA,EACb,QAAQ,KAAU,MAAuB;AACvC,UAAM,MAAMA;AACZ,QAAI,MAAM,WAAW;AACnB,iBAAW,KAAK,KAAK,WAAW;AAC9B,YAAI,SAAS,CAAC;AAAA,MAChB;AAAA,IACF;AACA,QAAI,QAAQ,oBAAoB,GAAG;AACnC,QAAI,QAAQ,iBAAiBC,KAAW;AACxC,QAAI,QAAQ,gBAAgB,MAAM,QAAQ,CAAA,CAAE;AAAA,EAC9C;AACF;"}
@@ -1,4 +1,5 @@
1
- import type { Protocol, ProviderItem, AuthConfig } from "../core/types";
1
+ import { type TransformPipeline } from "../core/transforms";
2
+ import type { Protocol, AuthConfig } from "../core/types";
2
3
  export type RestApiCfg = {
3
4
  url: string;
4
5
  method?: "GET" | "POST";
@@ -11,7 +12,7 @@ export type RestApiCfg = {
11
12
  value: string;
12
13
  meta?: Record<string, string>;
13
14
  };
14
- transform?: (items: unknown[]) => ProviderItem[];
15
+ transforms?: TransformPipeline;
15
16
  paginate?: {
16
17
  cursorPath: string;
17
18
  param: string;
@@ -1 +1 @@
1
- {"version":3,"file":"rest_api.d.ts","sourceRoot":"","sources":["../../src/protocols/rest_api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EAEZ,UAAU,EACX,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACrE,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,CAAC;IACjD,QAAQ,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AA0DF,eAAO,MAAM,eAAe,QAAO,QAAQ,CAAC,UAAU,CA8EpD,CAAC"}
1
+ {"version":3,"file":"rest_api.d.ts","sourceRoot":"","sources":["../../src/protocols/rest_api.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,QAAQ,EAGR,UAAU,EACX,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACrE,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AA0DF,eAAO,MAAM,eAAe,QAAO,QAAQ,CAAC,UAAU,CAoFpD,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { jp } from "../core/jsonpath.js";
2
2
  import { renderTpl, renderObj } from "../core/templating.js";
3
+ import { applyTransformPipeline } from "../core/transforms.js";
3
4
  function buildAuthHeaders(auth, globalAuth) {
4
5
  const headers = {};
5
6
  if (!auth) return headers;
@@ -80,19 +81,19 @@ const RestApiProtocol = () => ({
80
81
  return { items: [], ttl: 0 };
81
82
  }
82
83
  const json = await res.json();
83
- const items = jp(json, cfg.items);
84
- if (cfg.transform) {
85
- const transformedItems = cfg.transform(items);
86
- out.push(...transformedItems);
87
- } else {
88
- for (const it of items) {
89
- const label = jp(it, cfg.map.label)[0];
90
- const value = jp(it, cfg.map.value)[0];
91
- const meta = cfg.map.meta ? Object.fromEntries(
92
- Object.entries(cfg.map.meta).map(([k, p]) => [k, jp(it, p)[0]])
93
- ) : void 0;
94
- out.push({ label: String(label ?? ""), value, meta });
95
- }
84
+ let items = jp(json, cfg.items);
85
+ if (cfg.transforms && cfg.transforms.length > 0) {
86
+ items = applyTransformPipeline(items, cfg.transforms);
87
+ }
88
+ for (const it of items) {
89
+ const itemObj = it;
90
+ const formattedLabel = itemObj._formattedLabel;
91
+ const label = formattedLabel ? String(formattedLabel) : jp(it, cfg.map.label)[0];
92
+ const value = jp(it, cfg.map.value)[0];
93
+ const meta = cfg.map.meta ? Object.fromEntries(
94
+ Object.entries(cfg.map.meta).map(([k, p]) => [k, jp(it, p)[0]])
95
+ ) : void 0;
96
+ out.push({ label: String(label ?? ""), value, meta });
96
97
  }
97
98
  cursor = cfg.paginate ? jp(json, cfg.paginate.cursorPath)[0] : null;
98
99
  page += 1;
@@ -1 +1 @@
1
- {"version":3,"file":"rest_api.js","sources":["../../src/protocols/rest_api.ts"],"sourcesContent":["import { jp } from \"../core/jsonpath\";\nimport { renderObj, renderTpl } from \"../core/templating\";\nimport type {\n Protocol,\n ProviderItem,\n ProviderOutput,\n AuthConfig,\n} from \"../core/types\";\n\nexport type RestApiCfg = {\n url: string;\n method?: \"GET\" | \"POST\";\n headers?: Record<string, string>;\n query?: Record<string, unknown>;\n body?: unknown;\n items: string; // JSONPath to array; e.g. \"$.items[*]\"\n map: { label: string; value: string; meta?: Record<string, string> }; // relative to each item\n transform?: (items: unknown[]) => ProviderItem[]; // Optional transform function that overrides map\n paginate?: { cursorPath: string; param: string; maxPages?: number };\n auth?: AuthConfig;\n showError?: boolean; // Whether to show error messages, defaults to true\n};\n\nfunction buildAuthHeaders(\n auth?: AuthConfig,\n globalAuth?: Record<string, unknown>,\n): Record<string, string> {\n const headers: Record<string, string> = {};\n\n if (!auth) return headers;\n\n // Handle \"use\" reference to global auth\n if (auth.use && globalAuth?.[auth.use]) {\n const globalValue = globalAuth[auth.use];\n const value =\n typeof globalValue === \"function\" ? globalValue() : globalValue;\n\n if (auth.use === \"apiKey\") {\n headers[\"X-API-Key\"] = String(value);\n } else if (auth.use === \"bearer\") {\n headers[\"Authorization\"] = `Bearer ${value}`;\n } else if (auth.use === \"token\") {\n headers[\"Authorization\"] = `Token ${value}`;\n }\n return headers;\n }\n\n // Handle direct auth values\n if (auth.apiKey) {\n const value =\n typeof auth.apiKey === \"function\" ? auth.apiKey() : auth.apiKey;\n headers[\"X-API-Key\"] = String(value);\n }\n\n if (auth.bearer) {\n const value =\n typeof auth.bearer === \"function\" ? auth.bearer() : auth.bearer;\n headers[\"Authorization\"] = `Bearer ${value}`;\n }\n\n if (auth.token) {\n const value = typeof auth.token === \"function\" ? auth.token() : auth.token;\n headers[\"Authorization\"] = `Token ${value}`;\n }\n\n // Handle custom auth fields\n for (const [key, value] of Object.entries(auth)) {\n if (\n ![\"use\", \"apiKey\", \"bearer\", \"token\"].includes(key) &&\n value !== undefined\n ) {\n const authValue = typeof value === \"function\" ? value() : value;\n headers[key] = String(authValue);\n }\n }\n\n return headers;\n}\n\nexport const RestApiProtocol = (): Protocol<RestApiCfg> => ({\n protocol: \"rest_api\",\n async resolve(cfg, ctx): Promise<ProviderOutput> {\n const ac = ctx.signal;\n const out: ProviderItem[] = [];\n let cursor: unknown = null;\n let page = 0;\n do {\n const renderedUrl = renderTpl(cfg.url, { data: ctx.data, ui: ctx.ui });\n\n // Check if URL has unresolved template variables (empty string after template rendering)\n if (cfg.url.includes(\"{{\") && renderedUrl.endsWith(\"/\")) {\n return { items: [], ttl: 0 };\n }\n\n const url = new URL(renderedUrl);\n const q = renderObj(cfg.query ?? {}, {\n data: ctx.data,\n ui: ctx.ui,\n }) as Record<string, unknown>;\n for (const [k, v] of Object.entries(q))\n if (v !== undefined && v !== \"\") url.searchParams.set(k, String(v));\n if (cursor && cfg.paginate)\n url.searchParams.set(cfg.paginate.param, String(cursor));\n\n // Build headers with auth\n const baseHeaders = renderObj(cfg.headers ?? {}, {\n data: ctx.data,\n }) as Record<string, string>;\n const authHeaders = buildAuthHeaders(cfg.auth, ctx.auth);\n const headers = { ...baseHeaders, ...authHeaders };\n\n const method = cfg.method ?? \"GET\";\n const requestInit: RequestInit = {\n method,\n headers,\n signal: ac,\n };\n\n // Only include body for non-GET requests\n if (method !== \"GET\" && cfg.body) {\n requestInit.body = JSON.stringify(\n renderObj(cfg.body, { data: ctx.data }),\n );\n }\n\n const res = await fetch(url.toString(), requestInit);\n if (!res.ok) {\n if (cfg.showError !== false) {\n throw new Error(`REST ${res.status}`);\n }\n // If showError is false, return empty items instead of throwing\n return { items: [], ttl: 0 };\n }\n const json = await res.json();\n const items = jp(json, cfg.items);\n\n // Use transform function if provided, otherwise use map\n if (cfg.transform) {\n const transformedItems = cfg.transform(items);\n out.push(...transformedItems);\n } else {\n for (const it of items) {\n const label = jp(it, cfg.map.label)[0];\n const value = jp(it, cfg.map.value)[0];\n const meta = cfg.map.meta\n ? Object.fromEntries(\n Object.entries(cfg.map.meta).map(([k, p]) => [k, jp(it, p)[0]]),\n )\n : undefined;\n out.push({ label: String(label ?? \"\"), value, meta });\n }\n }\n cursor = cfg.paginate ? jp(json, cfg.paginate.cursorPath)[0] : null;\n page += 1;\n } while (cfg.paginate && cursor && page < (cfg.paginate.maxPages ?? 5));\n return { items: out, ttl: 300 };\n },\n});\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rest_api.js","sources":["../../src/protocols/rest_api.ts"],"sourcesContent":["import { jp } from \"../core/jsonpath\";\nimport { renderObj, renderTpl } from \"../core/templating\";\nimport {\n applyTransformPipeline,\n type TransformPipeline,\n} from \"../core/transforms\";\nimport type {\n Protocol,\n ProviderItem,\n ProviderOutput,\n AuthConfig,\n} from \"../core/types\";\n\nexport type RestApiCfg = {\n url: string;\n method?: \"GET\" | \"POST\";\n headers?: Record<string, string>;\n query?: Record<string, unknown>;\n body?: unknown;\n items: string; // JSONPath to array; e.g. \"$.items[*]\"\n map: { label: string; value: string; meta?: Record<string, string> }; // relative to each item\n transforms?: TransformPipeline; // Optional transform pipeline applied before mapping\n paginate?: { cursorPath: string; param: string; maxPages?: number };\n auth?: AuthConfig;\n showError?: boolean; // Whether to show error messages, defaults to true\n};\n\nfunction buildAuthHeaders(\n auth?: AuthConfig,\n globalAuth?: Record<string, unknown>,\n): Record<string, string> {\n const headers: Record<string, string> = {};\n\n if (!auth) return headers;\n\n // Handle \"use\" reference to global auth\n if (auth.use && globalAuth?.[auth.use]) {\n const globalValue = globalAuth[auth.use];\n const value =\n typeof globalValue === \"function\" ? globalValue() : globalValue;\n\n if (auth.use === \"apiKey\") {\n headers[\"X-API-Key\"] = String(value);\n } else if (auth.use === \"bearer\") {\n headers[\"Authorization\"] = `Bearer ${value}`;\n } else if (auth.use === \"token\") {\n headers[\"Authorization\"] = `Token ${value}`;\n }\n return headers;\n }\n\n // Handle direct auth values\n if (auth.apiKey) {\n const value =\n typeof auth.apiKey === \"function\" ? auth.apiKey() : auth.apiKey;\n headers[\"X-API-Key\"] = String(value);\n }\n\n if (auth.bearer) {\n const value =\n typeof auth.bearer === \"function\" ? auth.bearer() : auth.bearer;\n headers[\"Authorization\"] = `Bearer ${value}`;\n }\n\n if (auth.token) {\n const value = typeof auth.token === \"function\" ? auth.token() : auth.token;\n headers[\"Authorization\"] = `Token ${value}`;\n }\n\n // Handle custom auth fields\n for (const [key, value] of Object.entries(auth)) {\n if (\n ![\"use\", \"apiKey\", \"bearer\", \"token\"].includes(key) &&\n value !== undefined\n ) {\n const authValue = typeof value === \"function\" ? value() : value;\n headers[key] = String(authValue);\n }\n }\n\n return headers;\n}\n\nexport const RestApiProtocol = (): Protocol<RestApiCfg> => ({\n protocol: \"rest_api\",\n async resolve(cfg, ctx): Promise<ProviderOutput> {\n const ac = ctx.signal;\n const out: ProviderItem[] = [];\n let cursor: unknown = null;\n let page = 0;\n do {\n const renderedUrl = renderTpl(cfg.url, { data: ctx.data, ui: ctx.ui });\n\n // Check if URL has unresolved template variables (empty string after template rendering)\n if (cfg.url.includes(\"{{\") && renderedUrl.endsWith(\"/\")) {\n return { items: [], ttl: 0 };\n }\n\n const url = new URL(renderedUrl);\n const q = renderObj(cfg.query ?? {}, {\n data: ctx.data,\n ui: ctx.ui,\n }) as Record<string, unknown>;\n for (const [k, v] of Object.entries(q))\n if (v !== undefined && v !== \"\") url.searchParams.set(k, String(v));\n if (cursor && cfg.paginate)\n url.searchParams.set(cfg.paginate.param, String(cursor));\n\n // Build headers with auth\n const baseHeaders = renderObj(cfg.headers ?? {}, {\n data: ctx.data,\n }) as Record<string, string>;\n const authHeaders = buildAuthHeaders(cfg.auth, ctx.auth);\n const headers = { ...baseHeaders, ...authHeaders };\n\n const method = cfg.method ?? \"GET\";\n const requestInit: RequestInit = {\n method,\n headers,\n signal: ac,\n };\n\n // Only include body for non-GET requests\n if (method !== \"GET\" && cfg.body) {\n requestInit.body = JSON.stringify(\n renderObj(cfg.body, { data: ctx.data }),\n );\n }\n\n const res = await fetch(url.toString(), requestInit);\n if (!res.ok) {\n if (cfg.showError !== false) {\n throw new Error(`REST ${res.status}`);\n }\n // If showError is false, return empty items instead of throwing\n return { items: [], ttl: 0 };\n }\n const json = await res.json();\n let items = jp(json, cfg.items);\n\n // Apply transform pipeline if provided\n if (cfg.transforms && cfg.transforms.length > 0) {\n items = applyTransformPipeline(items, cfg.transforms);\n }\n\n // Map items to ProviderItem format\n for (const it of items) {\n // Check if item has _formattedLabel from transforms\n const itemObj = it as Record<string, unknown>;\n const formattedLabel = itemObj._formattedLabel;\n\n const label = formattedLabel\n ? String(formattedLabel)\n : jp(it, cfg.map.label)[0];\n const value = jp(it, cfg.map.value)[0];\n const meta = cfg.map.meta\n ? Object.fromEntries(\n Object.entries(cfg.map.meta).map(([k, p]) => [k, jp(it, p)[0]]),\n )\n : undefined;\n out.push({ label: String(label ?? \"\"), value, meta });\n }\n cursor = cfg.paginate ? jp(json, cfg.paginate.cursorPath)[0] : null;\n page += 1;\n } while (cfg.paginate && cursor && page < (cfg.paginate.maxPages ?? 5));\n return { items: out, ttl: 300 };\n },\n});\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/jsonforms-provider-protocols",
3
- "version": "1.2.0-beta.0",
3
+ "version": "1.2.0-beta.1",
4
4
  "description": "Dynamic data provider capabilities for JSONForms with Vue 3 integration",
5
5
  "type": "module",
6
6
  "author": "Narrative I/O",
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Transform pipeline system for manipulating API response data
3
+ * Transforms are applied sequentially in the order they appear in the pipeline
4
+ */
5
+
6
+ export interface Transform {
7
+ name: string;
8
+ [key: string]: unknown;
9
+ }
10
+
11
+ export interface FlattenTransform extends Transform {
12
+ name: "flatten";
13
+ key: string; // The key containing the nested array to flatten
14
+ labelFormat?: string; // Optional format string like "{parent.name} → {name}"
15
+ }
16
+
17
+ export type TransformStep = FlattenTransform;
18
+
19
+ export type TransformPipeline = TransformStep[];
20
+
21
+ /**
22
+ * Registry of transform functions
23
+ */
24
+ type TransformFunction = (items: unknown[], config: Transform) => unknown[];
25
+
26
+ const transformRegistry: Record<string, TransformFunction> = {};
27
+
28
+ /**
29
+ * Register a transform function
30
+ */
31
+ export function registerTransform(name: string, fn: TransformFunction): void {
32
+ transformRegistry[name] = fn;
33
+ }
34
+
35
+ /**
36
+ * Apply a pipeline of transforms to data
37
+ */
38
+ export function applyTransformPipeline(
39
+ items: unknown[],
40
+ pipeline: TransformPipeline,
41
+ ): unknown[] {
42
+ let result = items;
43
+
44
+ for (const transform of pipeline) {
45
+ const fn = transformRegistry[transform.name];
46
+ if (!fn) {
47
+ throw new Error(`Unknown transform: ${transform.name}`);
48
+ }
49
+ result = fn(result, transform);
50
+ }
51
+
52
+ return result;
53
+ }
54
+
55
+ /**
56
+ * Flatten transform - flattens nested arrays into a single level
57
+ */
58
+ function flattenTransform(items: unknown[], config: Transform): unknown[] {
59
+ const flattenConfig = config as FlattenTransform;
60
+ const { key, labelFormat } = flattenConfig;
61
+ const flattened: unknown[] = [];
62
+
63
+ for (const item of items) {
64
+ if (typeof item !== "object" || item === null) continue;
65
+
66
+ const itemObj = item as Record<string, unknown>;
67
+ const children = itemObj[key];
68
+
69
+ if (Array.isArray(children)) {
70
+ 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
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ return flattened;
104
+ }
105
+
106
+ // Register built-in transforms
107
+ registerTransform("flatten", flattenTransform);
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export { cache } from "./core/cache";
7
7
  export * from "./core/jsonpath";
8
8
  export { registry } from "./core/registry";
9
9
  export * from "./core/templating";
10
+ export * from "./core/transforms";
10
11
  // Core exports
11
12
  export * from "./core/types";
12
13
 
@@ -1,5 +1,9 @@
1
1
  import { jp } from "../core/jsonpath";
2
2
  import { renderObj, renderTpl } from "../core/templating";
3
+ import {
4
+ applyTransformPipeline,
5
+ type TransformPipeline,
6
+ } from "../core/transforms";
3
7
  import type {
4
8
  Protocol,
5
9
  ProviderItem,
@@ -15,7 +19,7 @@ export type RestApiCfg = {
15
19
  body?: unknown;
16
20
  items: string; // JSONPath to array; e.g. "$.items[*]"
17
21
  map: { label: string; value: string; meta?: Record<string, string> }; // relative to each item
18
- transform?: (items: unknown[]) => ProviderItem[]; // Optional transform function that overrides map
22
+ transforms?: TransformPipeline; // Optional transform pipeline applied before mapping
19
23
  paginate?: { cursorPath: string; param: string; maxPages?: number };
20
24
  auth?: AuthConfig;
21
25
  showError?: boolean; // Whether to show error messages, defaults to true
@@ -132,23 +136,29 @@ export const RestApiProtocol = (): Protocol<RestApiCfg> => ({
132
136
  return { items: [], ttl: 0 };
133
137
  }
134
138
  const json = await res.json();
135
- const items = jp(json, cfg.items);
136
-
137
- // Use transform function if provided, otherwise use map
138
- if (cfg.transform) {
139
- const transformedItems = cfg.transform(items);
140
- out.push(...transformedItems);
141
- } else {
142
- for (const it of items) {
143
- const label = jp(it, cfg.map.label)[0];
144
- const value = jp(it, cfg.map.value)[0];
145
- const meta = cfg.map.meta
146
- ? Object.fromEntries(
147
- Object.entries(cfg.map.meta).map(([k, p]) => [k, jp(it, p)[0]]),
148
- )
149
- : undefined;
150
- out.push({ label: String(label ?? ""), value, meta });
151
- }
139
+ let items = jp(json, cfg.items);
140
+
141
+ // Apply transform pipeline if provided
142
+ if (cfg.transforms && cfg.transforms.length > 0) {
143
+ items = applyTransformPipeline(items, cfg.transforms);
144
+ }
145
+
146
+ // Map items to ProviderItem format
147
+ for (const it of items) {
148
+ // Check if item has _formattedLabel from transforms
149
+ const itemObj = it as Record<string, unknown>;
150
+ const formattedLabel = itemObj._formattedLabel;
151
+
152
+ const label = formattedLabel
153
+ ? String(formattedLabel)
154
+ : jp(it, cfg.map.label)[0];
155
+ const value = jp(it, cfg.map.value)[0];
156
+ const meta = cfg.map.meta
157
+ ? Object.fromEntries(
158
+ Object.entries(cfg.map.meta).map(([k, p]) => [k, jp(it, p)[0]]),
159
+ )
160
+ : undefined;
161
+ out.push({ label: String(label ?? ""), value, meta });
152
162
  }
153
163
  cursor = cfg.paginate ? jp(json, cfg.paginate.cursorPath)[0] : null;
154
164
  page += 1;