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 SQLite database and shared across **all** workflows on this n8n instance
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 SQLite database file on the n8n host at:
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.db
103
+ ${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable-data.json
104
104
  ```
105
105
 
106
- The database and table are **created automatically on first use** — no setup required. Data survives instance restarts and is accessible from any workflow on the same n8n instance. This is ideal for cross-workflow counters, shared feature flags, or any state that multiple workflows need to read and write.
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 database 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.
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 SQLite database.
2
+ * Cross-workflow variable storage backed by a local JSON file.
3
3
  *
4
- * The database is auto-created at:
5
- * ${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable.db
4
+ * The file is auto-created at:
5
+ * ${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable-data.json
6
6
  *
7
- * No configuration is required the database file and table are created
8
- * automatically on first use and shared across all workflows on the instance.
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 SQLite database.
3
+ * Cross-workflow variable storage backed by a local JSON file.
4
4
  *
5
- * The database is auto-created at:
6
- * ${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable.db
5
+ * The file is auto-created at:
6
+ * ${N8N_USER_FOLDER ?? ~/.n8n}/n8n-nodes-variable-data.json
7
7
  *
8
- * No configuration is required the database file and table are created
9
- * automatically on first use and shared across all workflows on the instance.
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 DB_FILENAME = 'n8n-nodes-variable.db';
26
- function getDbPath() {
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 we can't create the n8n folder, fall back to the OS temp directory
35
- return path_1.default.join(os_1.default.tmpdir(), DB_FILENAME);
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, DB_FILENAME);
37
+ return path_1.default.join(userFolder, DATA_FILENAME);
38
38
  }
39
- // Singleton connection — reused across all node executions in the same process
40
- let _db = null;
41
- function getDb() {
42
- if (_db)
43
- return _db;
44
- const dbPath = getDbPath();
45
- _db = new better_sqlite3_1.default(dbPath);
46
- // WAL journal mode allows concurrent reads alongside writes
47
- _db.pragma('journal_mode = WAL');
48
- _db.pragma('foreign_keys = ON');
49
- // Auto-create the table if it doesn't exist
50
- _db.exec(`
51
- CREATE TABLE IF NOT EXISTS variables (
52
- namespace TEXT NOT NULL,
53
- key TEXT NOT NULL,
54
- value TEXT NOT NULL,
55
- type TEXT,
56
- created_at TEXT,
57
- updated_at TEXT,
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 db = getDb();
66
- const row = db
67
- .prepare('SELECT value, type, created_at, updated_at FROM variables WHERE namespace = ? AND key = ?')
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: JSON.parse(row.value),
73
- type: row.type ?? undefined,
74
- createdAt: row.created_at ?? undefined,
75
- updatedAt: row.updated_at ?? undefined,
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 db = getDb();
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
- // Preserve the original created_at on conflict
84
- db.prepare(`INSERT INTO variables (namespace, key, value, type, created_at, updated_at)
85
- VALUES (?, ?, ?, ?, ?, ?)
86
- ON CONFLICT(namespace, key) DO UPDATE SET
87
- value = excluded.value,
88
- type = excluded.type,
89
- updated_at = excluded.updated_at`).run(namespace, key, serialized, typeName, now, now);
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
- db.prepare(`INSERT INTO variables (namespace, key, value, type, created_at, updated_at)
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 db = getDb();
103
- const result = db
104
- .prepare('DELETE FROM variables WHERE namespace = ? AND key = ?')
105
- .run(namespace, key);
106
- return result.changes > 0;
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 db = getDb();
110
- const row = db
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 db = getDb();
117
- const rows = db
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 row of rows) {
122
- result[row.key] = {
123
- value: JSON.parse(row.value),
124
- type: row.type ?? undefined,
125
- createdAt: row.created_at ?? undefined,
126
- updatedAt: row.updated_at ?? undefined,
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 db = getDb();
133
- const result = db
134
- .prepare('DELETE FROM variables WHERE namespace = ?')
135
- .run(namespace);
136
- return result.changes;
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.8",
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",