Skip to content

Commit fe9a5f5

Browse files
committed
refactor(api): rework the JSON processing nodes and add new ones.
1 parent 8c347f5 commit fe9a5f5

47 files changed

Lines changed: 7426 additions & 183 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/api/src/nodes/cloudflare-node-registry.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,33 @@ import { SvgToPngNode } from "./image/svg-to-png-node";
153153
import { UformGen2Qwen500mNode } from "./image/uform-gen2-qwen-500m-node";
154154
import { WebcamNode } from "./image/webcam-node";
155155
import { JavaScriptEditorNode } from "./javascript/javascript-editor-node";
156-
import { JsonBooleanExtractorNode } from "./json/json-boolean-extractor-node";
156+
import { JsonAggNode } from "./json/json-agg-node";
157+
import { JsonArrayLengthNode } from "./json/json-array-length-node";
158+
import { JsonContainsNode } from "./json/json-contains-node";
159+
import { JsonContainsPathNode } from "./json/json-contains-path-node";
157160
import { JsonEditorNode } from "./json/json-editor-node";
158-
import { JsonExtractorNode } from "./json/json-extractor-node";
159-
import { JsonJavascriptProcessorNode } from "./json/json-javascript-processor-node";
160-
import { JsonNumberExtractorNode } from "./json/json-number-extractor-node";
161-
import { JsonStringExtractorNode } from "./json/json-string-extractor-node";
161+
import { JsonExecuteJavascriptNode } from "./json/json-execute-javascript-node";
162+
import { JsonExtractAllNode } from "./json/json-extract-all-node";
163+
import { JsonExtractBooleanNode } from "./json/json-extract-boolean-node";
164+
import { JsonExtractNumberNode } from "./json/json-extract-number-node";
165+
import { JsonExtractObjectNode } from "./json/json-extract-object-node";
166+
import { JsonExtractStringNode } from "./json/json-extract-string-node";
167+
import { JsonFlattenNode } from "./json/json-flatten-node";
168+
import { JsonInsertNode } from "./json/json-insert-node";
169+
import { JsonKeysNode } from "./json/json-keys-node";
170+
import { JsonMergeNode } from "./json/json-merge-node";
171+
import { JsonObjectAggNode } from "./json/json-object-agg-node";
172+
import { JsonObjectEntriesNode } from "./json/json-object-entries-node";
173+
import { JsonObjectKeysNode } from "./json/json-object-keys-node";
174+
import { JsonObjectValuesNode } from "./json/json-object-values-node";
175+
import { JsonRemoveNode } from "./json/json-remove-node";
176+
import { JsonReplaceNode } from "./json/json-replace-node";
177+
import { JsonSetNode } from "./json/json-set-node";
178+
import { JsonStripNullsNode } from "./json/json-strip-nulls-node";
162179
import { JsonTemplateNode } from "./json/json-template-node";
180+
import { JsonToGeojsonNode } from "./json/json-to-geojson-node";
181+
import { JsonTypeofNode } from "./json/json-typeof-node";
182+
import { JsonValidNode } from "./json/json-valid-node";
163183
import { ConditionalForkNode } from "./logic/conditional-fork-node";
164184
import { ConditionalJoinNode } from "./logic/conditional-join-node";
165185
import { AbsoluteValueNode } from "./math/absolute-value-node";
@@ -287,13 +307,33 @@ export class CloudflareNodeRegistry extends BaseNodeRegistry {
287307
this.registerImplementation(StableDiffusionXLLightningNode);
288308
this.registerImplementation(StableDiffusionV15Img2ImgNode);
289309
this.registerImplementation(StableDiffusionV15InpaintingNode);
290-
this.registerImplementation(JsonStringExtractorNode);
291-
this.registerImplementation(JsonBooleanExtractorNode);
292-
this.registerImplementation(JsonNumberExtractorNode);
293-
this.registerImplementation(JsonExtractorNode);
294-
this.registerImplementation(JsonJavascriptProcessorNode);
310+
this.registerImplementation(JsonAggNode);
311+
this.registerImplementation(JsonExtractStringNode);
312+
this.registerImplementation(JsonExtractBooleanNode);
313+
this.registerImplementation(JsonExtractNumberNode);
314+
this.registerImplementation(JsonExtractObjectNode);
315+
this.registerImplementation(JsonExtractAllNode);
316+
this.registerImplementation(JsonExecuteJavascriptNode);
295317
this.registerImplementation(JsonTemplateNode);
296318
this.registerImplementation(JsonEditorNode);
319+
this.registerImplementation(JsonArrayLengthNode);
320+
this.registerImplementation(JsonContainsNode);
321+
this.registerImplementation(JsonContainsPathNode);
322+
this.registerImplementation(JsonFlattenNode);
323+
this.registerImplementation(JsonInsertNode);
324+
this.registerImplementation(JsonKeysNode);
325+
this.registerImplementation(JsonMergeNode);
326+
this.registerImplementation(JsonObjectAggNode);
327+
this.registerImplementation(JsonObjectEntriesNode);
328+
this.registerImplementation(JsonObjectKeysNode);
329+
this.registerImplementation(JsonObjectValuesNode);
330+
this.registerImplementation(JsonRemoveNode);
331+
this.registerImplementation(JsonReplaceNode);
332+
this.registerImplementation(JsonSetNode);
333+
this.registerImplementation(JsonStripNullsNode);
334+
this.registerImplementation(JsonToGeojsonNode);
335+
this.registerImplementation(JsonTypeofNode);
336+
this.registerImplementation(JsonValidNode);
297337
this.registerImplementation(MultiVariableStringTemplateNode);
298338
this.registerImplementation(SingleVariableStringTemplateNode);
299339
this.registerImplementation(JavaScriptEditorNode);
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { Node } from "@dafthunk/types";
2+
import { describe, expect, it } from "vitest";
3+
4+
import { NodeContext } from "../types";
5+
import { JsonAggNode } from "./json-agg-node";
6+
7+
describe("JsonAggNode", () => {
8+
it("should aggregate single value into array", async () => {
9+
const nodeId = "json-agg";
10+
const node = new JsonAggNode({
11+
nodeId,
12+
} as unknown as Node);
13+
14+
const context = {
15+
nodeId,
16+
inputs: {
17+
values: "test",
18+
},
19+
} as unknown as NodeContext;
20+
21+
const result = await node.execute(context);
22+
expect(result.status).toBe("completed");
23+
expect(result.outputs?.result).toEqual(["test"]);
24+
});
25+
26+
it("should aggregate multiple values into array", async () => {
27+
const nodeId = "json-agg";
28+
const node = new JsonAggNode({
29+
nodeId,
30+
} as unknown as Node);
31+
32+
const context = {
33+
nodeId,
34+
inputs: {
35+
values: ["a", "b", "c"],
36+
},
37+
} as unknown as NodeContext;
38+
39+
const result = await node.execute(context);
40+
expect(result.status).toBe("completed");
41+
expect(result.outputs?.result).toEqual(["a", "b", "c"]);
42+
});
43+
44+
it("should handle mixed data types", async () => {
45+
const nodeId = "json-agg";
46+
const node = new JsonAggNode({
47+
nodeId,
48+
} as unknown as Node);
49+
50+
const context = {
51+
nodeId,
52+
inputs: {
53+
values: ["string", 42, true, { key: "value" }],
54+
},
55+
} as unknown as NodeContext;
56+
57+
const result = await node.execute(context);
58+
expect(result.status).toBe("completed");
59+
expect(result.outputs?.result).toEqual([
60+
"string",
61+
42,
62+
true,
63+
{ key: "value" },
64+
]);
65+
});
66+
67+
it("should filter out null and undefined values", async () => {
68+
const nodeId = "json-agg";
69+
const node = new JsonAggNode({
70+
nodeId,
71+
} as unknown as Node);
72+
73+
const context = {
74+
nodeId,
75+
inputs: {
76+
values: ["a", null, "b", undefined, "c"],
77+
},
78+
} as unknown as NodeContext;
79+
80+
const result = await node.execute(context);
81+
expect(result.status).toBe("completed");
82+
expect(result.outputs?.result).toEqual(["a", "b", "c"]);
83+
});
84+
85+
it("should return empty array for null input", async () => {
86+
const nodeId = "json-agg";
87+
const node = new JsonAggNode({
88+
nodeId,
89+
} as unknown as Node);
90+
91+
const context = {
92+
nodeId,
93+
inputs: {
94+
values: null,
95+
},
96+
} as unknown as NodeContext;
97+
98+
const result = await node.execute(context);
99+
expect(result.status).toBe("completed");
100+
expect(result.outputs?.result).toEqual([]);
101+
});
102+
103+
it("should return empty array for undefined input", async () => {
104+
const nodeId = "json-agg";
105+
const node = new JsonAggNode({
106+
nodeId,
107+
} as unknown as Node);
108+
109+
const context = {
110+
nodeId,
111+
inputs: {
112+
values: undefined,
113+
},
114+
} as unknown as NodeContext;
115+
116+
const result = await node.execute(context);
117+
expect(result.status).toBe("completed");
118+
expect(result.outputs?.result).toEqual([]);
119+
});
120+
121+
it("should handle empty array input", async () => {
122+
const nodeId = "json-agg";
123+
const node = new JsonAggNode({
124+
nodeId,
125+
} as unknown as Node);
126+
127+
const context = {
128+
nodeId,
129+
inputs: {
130+
values: [],
131+
},
132+
} as unknown as NodeContext;
133+
134+
const result = await node.execute(context);
135+
expect(result.status).toBe("completed");
136+
expect(result.outputs?.result).toEqual([]);
137+
});
138+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { NodeExecution, NodeType } from "@dafthunk/types";
2+
3+
import { ExecutableNode } from "../types";
4+
import { NodeContext } from "../types";
5+
6+
/**
7+
* JSON Aggregation node implementation that aggregates values into a JSON array
8+
* Inspired by SQLite/PostgreSQL's json_agg function
9+
*/
10+
export class JsonAggNode extends ExecutableNode {
11+
public static readonly nodeType: NodeType = {
12+
id: "json-agg",
13+
name: "JSON Aggregate",
14+
type: "json-agg",
15+
description: "Aggregates multiple values into a JSON array",
16+
tags: ["JSON"],
17+
icon: "list",
18+
inlinable: true,
19+
asTool: true,
20+
inputs: [
21+
{
22+
name: "values",
23+
type: "any",
24+
description:
25+
"Values to aggregate into a JSON array (supports multiple connections)",
26+
required: true,
27+
repeated: true,
28+
},
29+
],
30+
outputs: [
31+
{
32+
name: "result",
33+
type: "json",
34+
description: "The aggregated JSON array",
35+
},
36+
],
37+
};
38+
39+
async execute(context: NodeContext): Promise<NodeExecution> {
40+
try {
41+
const { values } = context.inputs;
42+
43+
// Handle missing input
44+
if (values === null || values === undefined) {
45+
return this.createSuccessResult({
46+
result: [],
47+
});
48+
}
49+
50+
// Handle single value input
51+
if (!Array.isArray(values)) {
52+
return this.createSuccessResult({
53+
result: [values],
54+
});
55+
}
56+
57+
// Handle array of values (multiple connections)
58+
if (Array.isArray(values)) {
59+
// Filter out null/undefined values and create the result array
60+
const result = values.filter(
61+
(value) => value !== null && value !== undefined
62+
);
63+
64+
return this.createSuccessResult({
65+
result,
66+
});
67+
}
68+
69+
return this.createErrorResult(
70+
"Invalid input type: expected any value or array of values"
71+
);
72+
} catch (error) {
73+
return this.createErrorResult(
74+
error instanceof Error ? error.message : "Unknown error"
75+
);
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)