n8n-nodes-variable 1.0.8 → 1.0.9
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
|
@@ -12,7 +12,7 @@ The **Variable** node lets you store, retrieve, update, and delete named variabl
|
|
|
12
12
|
- **Workflow Global** — persists across executions using n8n's workflow static data
|
|
13
13
|
- **Node Local** — persists for the specific node instance
|
|
14
14
|
- **Custom Namespace** — workflow global storage with a fully dynamic namespace string (great for per-user / per-guild data)
|
|
15
|
-
- **Cross-Workflow (Shared)** — variables are stored in a local
|
|
15
|
+
- **Cross-Workflow (Shared)** — variables are stored in a local JSON file and shared across **all** workflows on this n8n instance
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
@@ -97,15 +97,15 @@ guild_{{$json.guild.id}} → per-guild settings
|
|
|
97
97
|
|
|
98
98
|
### Cross-Workflow (Shared)
|
|
99
99
|
|
|
100
|
-
Variables are stored in a
|
|
100
|
+
Variables are stored in a JSON file on the n8n host at:
|
|
101
101
|
|
|
102
102
|
```
|
|
103
|
-
${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable.
|
|
103
|
+
${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable-data.json
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
The
|
|
106
|
+
The file is **created automatically on first use** — no setup or dependencies required. Writes are performed atomically (write to a temporary file, then rename) to prevent corruption. Data survives instance restarts and is accessible from any workflow on the same n8n instance.
|
|
107
107
|
|
|
108
|
-
> **Note:** the
|
|
108
|
+
> **Note:** the file lives on the n8n host machine. If you run n8n in a container or cloud environment, ensure the `.n8n` data directory is persisted to a volume so data is not lost on container restarts.
|
|
109
109
|
|
|
110
110
|
---
|
|
111
111
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cross-workflow variable storage backed by a local
|
|
2
|
+
* Cross-workflow variable storage backed by a local JSON file.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
* ${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable.
|
|
4
|
+
* The file is auto-created at:
|
|
5
|
+
* ${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable-data.json
|
|
6
6
|
*
|
|
7
|
-
* No configuration is required
|
|
8
|
-
*
|
|
7
|
+
* No configuration is required. Writes are performed atomically (write to a
|
|
8
|
+
* temporary file, then rename) to prevent data corruption on unexpected
|
|
9
|
+
* process exit. No native dependencies are needed.
|
|
9
10
|
*/
|
|
10
11
|
import type { StoredVariableEntry } from './types';
|
|
11
12
|
export declare function dbGetVariable(namespace: string, key: string): StoredVariableEntry | undefined;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Cross-workflow variable storage backed by a local
|
|
3
|
+
* Cross-workflow variable storage backed by a local JSON file.
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
6
|
-
* ${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable.
|
|
5
|
+
* The file is auto-created at:
|
|
6
|
+
* ${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable-data.json
|
|
7
7
|
*
|
|
8
|
-
* No configuration is required
|
|
9
|
-
*
|
|
8
|
+
* No configuration is required. Writes are performed atomically (write to a
|
|
9
|
+
* temporary file, then rename) to prevent data corruption on unexpected
|
|
10
|
+
* process exit. No native dependencies are needed.
|
|
10
11
|
*/
|
|
11
12
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
13
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -18,12 +19,11 @@ exports.dbDeleteVariable = dbDeleteVariable;
|
|
|
18
19
|
exports.dbHasVariable = dbHasVariable;
|
|
19
20
|
exports.dbListVariables = dbListVariables;
|
|
20
21
|
exports.dbClearNamespace = dbClearNamespace;
|
|
21
|
-
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
22
22
|
const fs_1 = __importDefault(require("fs"));
|
|
23
23
|
const os_1 = __importDefault(require("os"));
|
|
24
24
|
const path_1 = __importDefault(require("path"));
|
|
25
|
-
const
|
|
26
|
-
function
|
|
25
|
+
const DATA_FILENAME = 'n8n-nodes-variable-data.json';
|
|
26
|
+
function getDataPath() {
|
|
27
27
|
const userFolder = process.env['N8N_USER_FOLDER'] ?? path_1.default.join(os_1.default.homedir(), '.n8n');
|
|
28
28
|
try {
|
|
29
29
|
if (!fs_1.default.existsSync(userFolder)) {
|
|
@@ -31,107 +31,98 @@ function getDbPath() {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
catch {
|
|
34
|
-
// If
|
|
35
|
-
return path_1.default.join(os_1.default.tmpdir(),
|
|
34
|
+
// If the n8n user folder can't be created, fall back to the OS temp dir
|
|
35
|
+
return path_1.default.join(os_1.default.tmpdir(), DATA_FILENAME);
|
|
36
36
|
}
|
|
37
|
-
return path_1.default.join(userFolder,
|
|
37
|
+
return path_1.default.join(userFolder, DATA_FILENAME);
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
PRIMARY KEY (namespace, key)
|
|
59
|
-
)
|
|
60
|
-
`);
|
|
61
|
-
return _db;
|
|
39
|
+
function readData() {
|
|
40
|
+
const dataPath = getDataPath();
|
|
41
|
+
try {
|
|
42
|
+
const content = fs_1.default.readFileSync(dataPath, 'utf-8');
|
|
43
|
+
const parsed = JSON.parse(content);
|
|
44
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// File doesn't exist yet or is corrupt — start fresh
|
|
50
|
+
}
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
function writeData(data) {
|
|
54
|
+
const dataPath = getDataPath();
|
|
55
|
+
const tmpPath = `${dataPath}.tmp`;
|
|
56
|
+
fs_1.default.writeFileSync(tmpPath, JSON.stringify(data), 'utf-8');
|
|
57
|
+
fs_1.default.renameSync(tmpPath, dataPath);
|
|
62
58
|
}
|
|
63
59
|
// ─── CRUD operations ─────────────────────────────────────────────────────────
|
|
64
60
|
function dbGetVariable(namespace, key) {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
.get(namespace, key);
|
|
69
|
-
if (!row)
|
|
61
|
+
const data = readData();
|
|
62
|
+
const entry = data[namespace]?.[key];
|
|
63
|
+
if (!entry)
|
|
70
64
|
return undefined;
|
|
71
65
|
return {
|
|
72
|
-
value:
|
|
73
|
-
type:
|
|
74
|
-
createdAt:
|
|
75
|
-
updatedAt:
|
|
66
|
+
value: entry.value,
|
|
67
|
+
type: entry.type,
|
|
68
|
+
createdAt: entry.created_at,
|
|
69
|
+
updatedAt: entry.updated_at,
|
|
76
70
|
};
|
|
77
71
|
}
|
|
78
72
|
function dbSetVariable(namespace, key, value, typeName, includeMetadata) {
|
|
79
|
-
const
|
|
73
|
+
const data = readData();
|
|
74
|
+
if (!data[namespace]) {
|
|
75
|
+
data[namespace] = {};
|
|
76
|
+
}
|
|
80
77
|
const now = new Date().toISOString();
|
|
81
|
-
const serialized = JSON.stringify(value);
|
|
82
78
|
if (includeMetadata) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
const existing = data[namespace][key];
|
|
80
|
+
data[namespace][key] = {
|
|
81
|
+
value,
|
|
82
|
+
type: typeName,
|
|
83
|
+
// Preserve original created_at on updates
|
|
84
|
+
created_at: existing?.created_at ?? now,
|
|
85
|
+
updated_at: now,
|
|
86
|
+
};
|
|
90
87
|
}
|
|
91
88
|
else {
|
|
92
|
-
|
|
93
|
-
VALUES (?, ?, ?, NULL, NULL, NULL)
|
|
94
|
-
ON CONFLICT(namespace, key) DO UPDATE SET
|
|
95
|
-
value = excluded.value,
|
|
96
|
-
type = NULL,
|
|
97
|
-
created_at = NULL,
|
|
98
|
-
updated_at = NULL`).run(namespace, key, serialized);
|
|
89
|
+
data[namespace][key] = { value };
|
|
99
90
|
}
|
|
91
|
+
writeData(data);
|
|
100
92
|
}
|
|
101
93
|
function dbDeleteVariable(namespace, key) {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
const data = readData();
|
|
95
|
+
if (!data[namespace] || !(key in data[namespace])) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
delete data[namespace][key];
|
|
99
|
+
writeData(data);
|
|
100
|
+
return true;
|
|
107
101
|
}
|
|
108
102
|
function dbHasVariable(namespace, key) {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
.prepare('SELECT 1 FROM variables WHERE namespace = ? AND key = ? LIMIT 1')
|
|
112
|
-
.get(namespace, key);
|
|
113
|
-
return row !== undefined;
|
|
103
|
+
const data = readData();
|
|
104
|
+
return Object.prototype.hasOwnProperty.call(data[namespace] ?? {}, key);
|
|
114
105
|
}
|
|
115
106
|
function dbListVariables(namespace) {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
.prepare('SELECT key, value, type, created_at, updated_at FROM variables WHERE namespace = ? ORDER BY key')
|
|
119
|
-
.all(namespace);
|
|
107
|
+
const data = readData();
|
|
108
|
+
const ns = data[namespace] ?? {};
|
|
120
109
|
const result = {};
|
|
121
|
-
for (const
|
|
122
|
-
result[
|
|
123
|
-
value:
|
|
124
|
-
type:
|
|
125
|
-
createdAt:
|
|
126
|
-
updatedAt:
|
|
110
|
+
for (const [k, entry] of Object.entries(ns)) {
|
|
111
|
+
result[k] = {
|
|
112
|
+
value: entry.value,
|
|
113
|
+
type: entry.type,
|
|
114
|
+
createdAt: entry.created_at,
|
|
115
|
+
updatedAt: entry.updated_at,
|
|
127
116
|
};
|
|
128
117
|
}
|
|
129
118
|
return result;
|
|
130
119
|
}
|
|
131
120
|
function dbClearNamespace(namespace) {
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
121
|
+
const data = readData();
|
|
122
|
+
const count = Object.keys(data[namespace] ?? {}).length;
|
|
123
|
+
if (count > 0) {
|
|
124
|
+
delete data[namespace];
|
|
125
|
+
writeData(data);
|
|
126
|
+
}
|
|
127
|
+
return count;
|
|
137
128
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-variable",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Local and global scoped variables for n8n workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
]
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@types/better-sqlite3": "^7.6.11",
|
|
42
41
|
"@types/jest": "^29.5.12",
|
|
43
42
|
"@types/node": "^18.19.50",
|
|
44
43
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
@@ -55,9 +54,6 @@
|
|
|
55
54
|
"peerDependencies": {
|
|
56
55
|
"n8n-workflow": "*"
|
|
57
56
|
},
|
|
58
|
-
"dependencies": {
|
|
59
|
-
"better-sqlite3": "^9.4.0"
|
|
60
|
-
},
|
|
61
57
|
"jest": {
|
|
62
58
|
"preset": "ts-jest",
|
|
63
59
|
"testEnvironment": "node",
|