toonify-n8n-node 1.0.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/PLAN.md +150 -0
- package/dist/nodes/ToToon/ToToon.node.d.ts +5 -0
- package/dist/nodes/ToToon/ToToon.node.js +60 -0
- package/nodes/ToToon/ToToon.node.ts +68 -0
- package/nodes/ToToon/toon.svg +10 -0
- package/package.json +31 -0
- package/test/manual-test.ts +55 -0
- package/tsconfig.json +14 -0
package/PLAN.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# n8n TOON Converter Node - Implementation Plan
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
Create a custom n8n node that converts incoming JSON items to TOON (Token-Oriented Object Notation) format to reduce token usage when sending data to LLMs. Each item is converted separately with metadata about token savings.
|
|
5
|
+
|
|
6
|
+
## Project Structure
|
|
7
|
+
```
|
|
8
|
+
toon/
|
|
9
|
+
├── package.json
|
|
10
|
+
├── tsconfig.json
|
|
11
|
+
├── nodes/
|
|
12
|
+
│ └── ToToon/
|
|
13
|
+
│ ├── ToToon.node.ts # Main node implementation
|
|
14
|
+
│ └── toon.svg # Node icon
|
|
15
|
+
└── dist/ # Build output
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Files to Create
|
|
19
|
+
|
|
20
|
+
### 1. `package.json`
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"name": "n8n-nodes-toon",
|
|
24
|
+
"version": "1.0.0",
|
|
25
|
+
"description": "n8n node to convert JSON to TOON format for reduced LLM token usage",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": { "name": "Diego" },
|
|
28
|
+
"n8n": {
|
|
29
|
+
"n8nNodesApiVersion": 1,
|
|
30
|
+
"nodes": ["dist/nodes/ToToon/ToToon.node.js"]
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"dev": "tsc --watch",
|
|
35
|
+
"lint": "eslint . --ext .ts"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@toon-format/toon": "^3.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20.0.0",
|
|
42
|
+
"n8n-workflow": "^1.0.0",
|
|
43
|
+
"typescript": "^5.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. `tsconfig.json`
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"compilerOptions": {
|
|
52
|
+
"target": "ES2019",
|
|
53
|
+
"module": "CommonJS",
|
|
54
|
+
"strict": true,
|
|
55
|
+
"esModuleInterop": true,
|
|
56
|
+
"outDir": "./dist",
|
|
57
|
+
"rootDir": ".",
|
|
58
|
+
"declaration": true
|
|
59
|
+
},
|
|
60
|
+
"include": ["nodes/**/*.ts"],
|
|
61
|
+
"exclude": ["node_modules", "dist"]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. `nodes/ToToon/ToToon.node.ts`
|
|
66
|
+
```typescript
|
|
67
|
+
import {
|
|
68
|
+
IExecuteFunctions,
|
|
69
|
+
INodeExecutionData,
|
|
70
|
+
INodeType,
|
|
71
|
+
INodeTypeDescription,
|
|
72
|
+
} from 'n8n-workflow';
|
|
73
|
+
import { encode } from '@toon-format/toon';
|
|
74
|
+
|
|
75
|
+
export class ToToon implements INodeType {
|
|
76
|
+
description: INodeTypeDescription = {
|
|
77
|
+
displayName: 'To TOON',
|
|
78
|
+
name: 'toToon',
|
|
79
|
+
icon: 'file:toon.svg',
|
|
80
|
+
group: ['transform'],
|
|
81
|
+
version: 1,
|
|
82
|
+
subtitle: 'Convert to TOON format',
|
|
83
|
+
description: 'Convert JSON items to TOON format for reduced LLM token usage',
|
|
84
|
+
defaults: { name: 'To TOON' },
|
|
85
|
+
inputs: ['main'],
|
|
86
|
+
outputs: ['main'],
|
|
87
|
+
properties: [
|
|
88
|
+
{
|
|
89
|
+
displayName: 'Output Field',
|
|
90
|
+
name: 'outputField',
|
|
91
|
+
type: 'string',
|
|
92
|
+
default: 'toon',
|
|
93
|
+
description: 'Field name to store the TOON output',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
99
|
+
const items = this.getInputData();
|
|
100
|
+
const outputField = this.getNodeParameter('outputField', 0) as string;
|
|
101
|
+
const results: INodeExecutionData[] = [];
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < items.length; i++) {
|
|
104
|
+
const item = items[i];
|
|
105
|
+
const jsonData = item.json;
|
|
106
|
+
|
|
107
|
+
// Calculate original JSON size
|
|
108
|
+
const originalJson = JSON.stringify(jsonData);
|
|
109
|
+
const originalSize = originalJson.length;
|
|
110
|
+
|
|
111
|
+
// Convert to TOON
|
|
112
|
+
const toonOutput = encode(jsonData);
|
|
113
|
+
const toonSize = toonOutput.length;
|
|
114
|
+
|
|
115
|
+
// Calculate savings
|
|
116
|
+
const savedBytes = originalSize - toonSize;
|
|
117
|
+
const savingsPercent = ((savedBytes / originalSize) * 100).toFixed(1);
|
|
118
|
+
|
|
119
|
+
results.push({
|
|
120
|
+
json: {
|
|
121
|
+
[outputField]: toonOutput,
|
|
122
|
+
_meta: {
|
|
123
|
+
originalSizeBytes: originalSize,
|
|
124
|
+
toonSizeBytes: toonSize,
|
|
125
|
+
savedBytes,
|
|
126
|
+
savingsPercent: `${savingsPercent}%`,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return [results];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 4. `nodes/ToToon/toon.svg`
|
|
138
|
+
Simple icon (purple/blue gradient with "T" letter)
|
|
139
|
+
|
|
140
|
+
## Implementation Steps
|
|
141
|
+
1. Create all files above
|
|
142
|
+
2. Run `npm install`
|
|
143
|
+
3. Run `npm run build`
|
|
144
|
+
4. Link to n8n or use `npm run dev`
|
|
145
|
+
|
|
146
|
+
## Verification
|
|
147
|
+
1. `npm install` - Install dependencies
|
|
148
|
+
2. `npm run build` - Compile TypeScript (should produce `dist/` folder)
|
|
149
|
+
3. Link to local n8n: `npm link` then in n8n folder `npm link n8n-nodes-toon`
|
|
150
|
+
4. Test workflow: Manual Trigger → Set (sample JSON) → To TOON → Check output has `toon` field + `_meta` with savings info
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ToToon = void 0;
|
|
4
|
+
const toon_1 = require("@toon-format/toon");
|
|
5
|
+
class ToToon {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.description = {
|
|
8
|
+
displayName: 'To TOON',
|
|
9
|
+
name: 'toToon',
|
|
10
|
+
icon: 'file:toon.svg',
|
|
11
|
+
group: ['transform'],
|
|
12
|
+
version: 1,
|
|
13
|
+
subtitle: 'Convert to TOON format',
|
|
14
|
+
description: 'Convert JSON items to TOON format for reduced LLM token usage',
|
|
15
|
+
defaults: { name: 'To TOON' },
|
|
16
|
+
inputs: ['main'],
|
|
17
|
+
outputs: ['main'],
|
|
18
|
+
properties: [
|
|
19
|
+
{
|
|
20
|
+
displayName: 'Output Field',
|
|
21
|
+
name: 'outputField',
|
|
22
|
+
type: 'string',
|
|
23
|
+
default: 'toon',
|
|
24
|
+
description: 'Field name to store the TOON output',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async execute() {
|
|
30
|
+
const items = this.getInputData();
|
|
31
|
+
const outputField = this.getNodeParameter('outputField', 0);
|
|
32
|
+
const results = [];
|
|
33
|
+
for (let i = 0; i < items.length; i++) {
|
|
34
|
+
const item = items[i];
|
|
35
|
+
const jsonData = item.json;
|
|
36
|
+
// Calculate original JSON size
|
|
37
|
+
const originalJson = JSON.stringify(jsonData);
|
|
38
|
+
const originalSize = originalJson.length;
|
|
39
|
+
// Convert to TOON
|
|
40
|
+
const toonOutput = (0, toon_1.encode)(jsonData);
|
|
41
|
+
const toonSize = toonOutput.length;
|
|
42
|
+
// Calculate savings
|
|
43
|
+
const savedBytes = originalSize - toonSize;
|
|
44
|
+
const savingsPercent = ((savedBytes / originalSize) * 100).toFixed(1);
|
|
45
|
+
results.push({
|
|
46
|
+
json: {
|
|
47
|
+
[outputField]: toonOutput,
|
|
48
|
+
_meta: {
|
|
49
|
+
originalSizeBytes: originalSize,
|
|
50
|
+
toonSizeBytes: toonSize,
|
|
51
|
+
savedBytes,
|
|
52
|
+
savingsPercent: `${savingsPercent}%`,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return [results];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.ToToon = ToToon;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IExecuteFunctions,
|
|
3
|
+
INodeExecutionData,
|
|
4
|
+
INodeType,
|
|
5
|
+
INodeTypeDescription,
|
|
6
|
+
} from 'n8n-workflow';
|
|
7
|
+
import { encode } from '@toon-format/toon';
|
|
8
|
+
|
|
9
|
+
export class ToToon implements INodeType {
|
|
10
|
+
description: INodeTypeDescription = {
|
|
11
|
+
displayName: 'To TOON',
|
|
12
|
+
name: 'toToon',
|
|
13
|
+
icon: 'file:toon.svg',
|
|
14
|
+
group: ['transform'],
|
|
15
|
+
version: 1,
|
|
16
|
+
subtitle: 'Convert to TOON format',
|
|
17
|
+
description: 'Convert JSON items to TOON format for reduced LLM token usage',
|
|
18
|
+
defaults: { name: 'To TOON' },
|
|
19
|
+
inputs: ['main'],
|
|
20
|
+
outputs: ['main'],
|
|
21
|
+
properties: [
|
|
22
|
+
{
|
|
23
|
+
displayName: 'Output Field',
|
|
24
|
+
name: 'outputField',
|
|
25
|
+
type: 'string',
|
|
26
|
+
default: 'toon',
|
|
27
|
+
description: 'Field name to store the TOON output',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
33
|
+
const items = this.getInputData();
|
|
34
|
+
const outputField = this.getNodeParameter('outputField', 0) as string;
|
|
35
|
+
const results: INodeExecutionData[] = [];
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < items.length; i++) {
|
|
38
|
+
const item = items[i];
|
|
39
|
+
const jsonData = item.json;
|
|
40
|
+
|
|
41
|
+
// Calculate original JSON size
|
|
42
|
+
const originalJson = JSON.stringify(jsonData);
|
|
43
|
+
const originalSize = originalJson.length;
|
|
44
|
+
|
|
45
|
+
// Convert to TOON
|
|
46
|
+
const toonOutput = encode(jsonData);
|
|
47
|
+
const toonSize = toonOutput.length;
|
|
48
|
+
|
|
49
|
+
// Calculate savings
|
|
50
|
+
const savedBytes = originalSize - toonSize;
|
|
51
|
+
const savingsPercent = ((savedBytes / originalSize) * 100).toFixed(1);
|
|
52
|
+
|
|
53
|
+
results.push({
|
|
54
|
+
json: {
|
|
55
|
+
[outputField]: toonOutput,
|
|
56
|
+
_meta: {
|
|
57
|
+
originalSizeBytes: originalSize,
|
|
58
|
+
toonSizeBytes: toonSize,
|
|
59
|
+
savedBytes,
|
|
60
|
+
savingsPercent: `${savingsPercent}%`,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return [results];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#8e2de2;stop-opacity:1" />
|
|
5
|
+
<stop offset="100%" style="stop-color:#4a00e0;stop-opacity:1" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="100" height="100" rx="20" fill="url(#grad1)" />
|
|
9
|
+
<text x="50" y="70" font-family="Arial, sans-serif" font-size="60" font-weight="bold" fill="white" text-anchor="middle">T</text>
|
|
10
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "toonify-n8n-node",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "n8n node to convert JSON to TOON format for reduced LLM token usage",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": { "name": "Diego" },
|
|
7
|
+
"keywords": [
|
|
8
|
+
"n8n-community-node-package"
|
|
9
|
+
],
|
|
10
|
+
"n8n": {
|
|
11
|
+
"n8nNodesApiVersion": 1,
|
|
12
|
+
"nodes": ["dist/nodes/ToToon/ToToon.node.js"]
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"lint": "eslint . --ext .ts"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@toon-format/toon": "^2.1.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^20.0.0",
|
|
24
|
+
"n8n-workflow": "^1.0.0",
|
|
25
|
+
"typescript": "^5.0.0"
|
|
26
|
+
},
|
|
27
|
+
"overrides": {
|
|
28
|
+
"form-data": "^4.0.5",
|
|
29
|
+
"lodash": "^4.17.23"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ToToon } from '../nodes/ToToon/ToToon.node';
|
|
2
|
+
import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
|
3
|
+
|
|
4
|
+
// Mock the execution context
|
|
5
|
+
const mockExecuteFunctions = {
|
|
6
|
+
getInputData: () => {
|
|
7
|
+
return [
|
|
8
|
+
{
|
|
9
|
+
json: {
|
|
10
|
+
id: 1,
|
|
11
|
+
name: "Alice",
|
|
12
|
+
roles: ["admin", "editor"]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
json: {
|
|
17
|
+
id: 2,
|
|
18
|
+
name: "Bob",
|
|
19
|
+
roles: ["viewer"]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
] as INodeExecutionData[];
|
|
23
|
+
},
|
|
24
|
+
getNodeParameter: (parameterName: string) => {
|
|
25
|
+
if (parameterName === 'outputField') return 'myToonField';
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
} as unknown as IExecuteFunctions;
|
|
29
|
+
|
|
30
|
+
async function runTest() {
|
|
31
|
+
console.log("Starting ToToon Node Test...\n");
|
|
32
|
+
|
|
33
|
+
const node = new ToToon();
|
|
34
|
+
|
|
35
|
+
// Bind the mock context to the execute function
|
|
36
|
+
const execute = node.execute.bind(mockExecuteFunctions);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const results = await execute();
|
|
40
|
+
const outputItems = results[0];
|
|
41
|
+
|
|
42
|
+
outputItems.forEach((item, index) => {
|
|
43
|
+
console.log(`--- Item ${index + 1} ---`);
|
|
44
|
+
console.log("Original JSON:", JSON.stringify(mockExecuteFunctions.getInputData()[index].json));
|
|
45
|
+
console.log("Result:", JSON.stringify(item.json, null, 2));
|
|
46
|
+
console.log("------------------\n");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log("Test execution successful!");
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error("Test failed:", error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
runTest();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2019",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"strict": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": ".",
|
|
10
|
+
"declaration": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["nodes/**/*.ts"],
|
|
13
|
+
"exclude": ["node_modules", "dist"]
|
|
14
|
+
}
|