n8n-nodes-variable 1.0.6

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,14 @@
1
+ import type { IDataObject } from 'n8n-workflow';
2
+ import type { StoredVariableEntry } from './types';
3
+ export declare function staticGetVariable(staticData: IDataObject, namespace: string, key: string): StoredVariableEntry | undefined;
4
+ export declare function staticSetVariable(staticData: IDataObject, namespace: string, key: string, value: unknown, typeName: string, includeMetadata: boolean): void;
5
+ export declare function staticDeleteVariable(staticData: IDataObject, namespace: string, key: string): boolean;
6
+ export declare function staticHasVariable(staticData: IDataObject, namespace: string, key: string): boolean;
7
+ export declare function staticListVariables(staticData: IDataObject, namespace: string): Record<string, StoredVariableEntry>;
8
+ export declare function staticClearNamespace(staticData: IDataObject, namespace: string): number;
9
+ export declare function localGetVariable(itemJson: IDataObject, storagePath: string, namespace: string, key: string): unknown;
10
+ export declare function localSetVariable(itemJson: IDataObject, storagePath: string, namespace: string, key: string, value: unknown): void;
11
+ export declare function localDeleteVariable(itemJson: IDataObject, storagePath: string, namespace: string, key: string): boolean;
12
+ export declare function localHasVariable(itemJson: IDataObject, storagePath: string, namespace: string, key: string): boolean;
13
+ export declare function localListVariables(itemJson: IDataObject, storagePath: string, namespace: string): Record<string, unknown>;
14
+ export declare function localClearNamespace(itemJson: IDataObject, storagePath: string, namespace: string): number;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.staticGetVariable = staticGetVariable;
4
+ exports.staticSetVariable = staticSetVariable;
5
+ exports.staticDeleteVariable = staticDeleteVariable;
6
+ exports.staticHasVariable = staticHasVariable;
7
+ exports.staticListVariables = staticListVariables;
8
+ exports.staticClearNamespace = staticClearNamespace;
9
+ exports.localGetVariable = localGetVariable;
10
+ exports.localSetVariable = localSetVariable;
11
+ exports.localDeleteVariable = localDeleteVariable;
12
+ exports.localHasVariable = localHasVariable;
13
+ exports.localListVariables = localListVariables;
14
+ exports.localClearNamespace = localClearNamespace;
15
+ const PACKAGE_KEY = 'n8nNodesVariable';
16
+ // ─── Static data store helpers (Workflow Global / Node Local) ────────────────
17
+ function getPackageStore(staticData) {
18
+ if (!staticData[PACKAGE_KEY]) {
19
+ staticData[PACKAGE_KEY] = { namespaces: {} };
20
+ }
21
+ return staticData[PACKAGE_KEY];
22
+ }
23
+ function getNamespaceStore(pkg, namespace) {
24
+ if (!pkg.namespaces[namespace]) {
25
+ pkg.namespaces[namespace] = { variables: {} };
26
+ }
27
+ return pkg.namespaces[namespace];
28
+ }
29
+ function staticGetVariable(staticData, namespace, key) {
30
+ const pkg = getPackageStore(staticData);
31
+ const ns = pkg.namespaces[namespace];
32
+ if (!ns)
33
+ return undefined;
34
+ return ns.variables[key];
35
+ }
36
+ function staticSetVariable(staticData, namespace, key, value, typeName, includeMetadata) {
37
+ const pkg = getPackageStore(staticData);
38
+ const ns = getNamespaceStore(pkg, namespace);
39
+ const now = new Date().toISOString();
40
+ const existing = ns.variables[key];
41
+ const entry = { value };
42
+ if (includeMetadata) {
43
+ entry.type = typeName;
44
+ entry.createdAt = existing?.createdAt ?? now;
45
+ entry.updatedAt = now;
46
+ }
47
+ ns.variables[key] = entry;
48
+ }
49
+ function staticDeleteVariable(staticData, namespace, key) {
50
+ const pkg = getPackageStore(staticData);
51
+ const ns = pkg.namespaces[namespace];
52
+ if (!ns || !(key in ns.variables))
53
+ return false;
54
+ delete ns.variables[key];
55
+ return true;
56
+ }
57
+ function staticHasVariable(staticData, namespace, key) {
58
+ const pkg = getPackageStore(staticData);
59
+ const ns = pkg.namespaces[namespace];
60
+ return !!(ns && key in ns.variables);
61
+ }
62
+ function staticListVariables(staticData, namespace) {
63
+ const pkg = getPackageStore(staticData);
64
+ const ns = pkg.namespaces[namespace];
65
+ if (!ns)
66
+ return {};
67
+ return { ...ns.variables };
68
+ }
69
+ function staticClearNamespace(staticData, namespace) {
70
+ const pkg = getPackageStore(staticData);
71
+ const ns = pkg.namespaces[namespace];
72
+ if (!ns)
73
+ return 0;
74
+ const count = Object.keys(ns.variables).length;
75
+ ns.variables = {};
76
+ return count;
77
+ }
78
+ // ─── Local execution (item JSON) store helpers ───────────────────────────────
79
+ function getLocalNamespaceContainer(itemJson, storagePath, namespace) {
80
+ if (!itemJson[storagePath] || typeof itemJson[storagePath] !== 'object') {
81
+ itemJson[storagePath] = {};
82
+ }
83
+ const root = itemJson[storagePath];
84
+ if (!root[namespace] || typeof root[namespace] !== 'object') {
85
+ root[namespace] = {};
86
+ }
87
+ return root[namespace];
88
+ }
89
+ function localGetVariable(itemJson, storagePath, namespace, key) {
90
+ const container = itemJson[storagePath];
91
+ if (!container)
92
+ return undefined;
93
+ const ns = container[namespace];
94
+ if (!ns)
95
+ return undefined;
96
+ return ns[key];
97
+ }
98
+ function localSetVariable(itemJson, storagePath, namespace, key, value) {
99
+ const ns = getLocalNamespaceContainer(itemJson, storagePath, namespace);
100
+ ns[key] = value;
101
+ }
102
+ function localDeleteVariable(itemJson, storagePath, namespace, key) {
103
+ const container = itemJson[storagePath];
104
+ if (!container)
105
+ return false;
106
+ const ns = container[namespace];
107
+ if (!ns || !(key in ns))
108
+ return false;
109
+ delete ns[key];
110
+ return true;
111
+ }
112
+ function localHasVariable(itemJson, storagePath, namespace, key) {
113
+ const container = itemJson[storagePath];
114
+ if (!container)
115
+ return false;
116
+ const ns = container[namespace];
117
+ return !!(ns && key in ns);
118
+ }
119
+ function localListVariables(itemJson, storagePath, namespace) {
120
+ const container = itemJson[storagePath];
121
+ if (!container)
122
+ return {};
123
+ const ns = container[namespace];
124
+ if (!ns)
125
+ return {};
126
+ return { ...ns };
127
+ }
128
+ function localClearNamespace(itemJson, storagePath, namespace) {
129
+ const container = itemJson[storagePath];
130
+ if (!container)
131
+ return 0;
132
+ const ns = container[namespace];
133
+ if (!ns)
134
+ return 0;
135
+ const count = Object.keys(ns).length;
136
+ container[namespace] = {};
137
+ return count;
138
+ }
@@ -0,0 +1,30 @@
1
+ export type VariableScope = 'localExecution' | 'workflowGlobal' | 'nodeLocal' | 'customNamespace';
2
+ export type VariableOperation = 'set' | 'get' | 'delete' | 'has' | 'list' | 'clear' | 'increment' | 'decrement' | 'appendToArray' | 'mergeObject' | 'toggleBoolean';
3
+ export type ValueType = 'string' | 'number' | 'boolean' | 'json' | 'array' | 'object' | 'auto';
4
+ export type OutputMode = 'preserveAndAdd' | 'resultOnly' | 'addField';
5
+ export interface StoredVariableEntry {
6
+ value: unknown;
7
+ type?: string;
8
+ createdAt?: string;
9
+ updatedAt?: string;
10
+ }
11
+ export interface VariableNamespace {
12
+ variables: Record<string, StoredVariableEntry>;
13
+ }
14
+ export interface PackageStaticStore {
15
+ namespaces: Record<string, VariableNamespace>;
16
+ }
17
+ export interface OperationResult {
18
+ operation: string;
19
+ scope: string;
20
+ namespace: string;
21
+ key?: string;
22
+ value?: unknown;
23
+ previousValue?: unknown;
24
+ exists?: boolean;
25
+ deleted?: boolean;
26
+ keys?: string[];
27
+ variables?: Record<string, unknown>;
28
+ count?: number;
29
+ cleared?: boolean;
30
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,20 @@
1
+ import type { ValueType } from './types';
2
+ /**
3
+ * Validates a variable key or namespace string.
4
+ * Throws a plain Error (to be wrapped by the node as NodeOperationError).
5
+ */
6
+ export declare function validateKey(key: string, label?: string): void;
7
+ export declare function validateNamespace(ns: string): void;
8
+ /**
9
+ * Parse a raw string value into the typed JavaScript value specified by valueType.
10
+ */
11
+ export declare function parseValueByType(rawValue: unknown, valueType: ValueType): unknown;
12
+ /** Parse a JSON string safely, throwing a user-friendly error on failure. */
13
+ export declare function safeJsonParse(str: string): unknown;
14
+ /** Infer the type name from a runtime value. */
15
+ export declare function inferValueType(value: unknown): string;
16
+ /**
17
+ * Deep-merge source object into target object.
18
+ * Only handles plain objects at each level; other values are overwritten.
19
+ */
20
+ export declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateKey = validateKey;
4
+ exports.validateNamespace = validateNamespace;
5
+ exports.parseValueByType = parseValueByType;
6
+ exports.safeJsonParse = safeJsonParse;
7
+ exports.inferValueType = inferValueType;
8
+ exports.deepMerge = deepMerge;
9
+ /** Keys that would cause prototype pollution — always rejected. */
10
+ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
11
+ /**
12
+ * Validates a variable key or namespace string.
13
+ * Throws a plain Error (to be wrapped by the node as NodeOperationError).
14
+ */
15
+ function validateKey(key, label = 'Key') {
16
+ const trimmed = key.trim();
17
+ if (trimmed.length === 0) {
18
+ throw new Error(`${label} must not be empty.`);
19
+ }
20
+ if (DANGEROUS_KEYS.has(trimmed)) {
21
+ throw new Error(`${label} "${trimmed}" is not allowed for security reasons.`);
22
+ }
23
+ }
24
+ function validateNamespace(ns) {
25
+ const trimmed = ns.trim();
26
+ if (trimmed.length === 0) {
27
+ throw new Error('Namespace must not be empty.');
28
+ }
29
+ if (DANGEROUS_KEYS.has(trimmed)) {
30
+ throw new Error(`Namespace "${trimmed}" is not allowed for security reasons.`);
31
+ }
32
+ }
33
+ /**
34
+ * Parse a raw string value into the typed JavaScript value specified by valueType.
35
+ */
36
+ function parseValueByType(rawValue, valueType) {
37
+ if (valueType === 'auto') {
38
+ return rawValue;
39
+ }
40
+ const str = String(rawValue ?? '');
41
+ switch (valueType) {
42
+ case 'string':
43
+ return str;
44
+ case 'number': {
45
+ const n = Number(str);
46
+ if (str.trim() === '' || !Number.isFinite(n)) {
47
+ throw new Error(`Value "${str}" cannot be converted to a valid number. Got: ${n}`);
48
+ }
49
+ return n;
50
+ }
51
+ case 'boolean': {
52
+ const lower = str.toLowerCase().trim();
53
+ if (lower === 'true' || lower === '1' || lower === 'yes')
54
+ return true;
55
+ if (lower === 'false' || lower === '0' || lower === 'no')
56
+ return false;
57
+ // If rawValue is already boolean, use it directly
58
+ if (typeof rawValue === 'boolean')
59
+ return rawValue;
60
+ throw new Error(`Value "${str}" cannot be converted to a boolean. Use true/false/1/0/yes/no.`);
61
+ }
62
+ case 'json': {
63
+ return safeJsonParse(str);
64
+ }
65
+ case 'array': {
66
+ const parsed = safeJsonParse(str);
67
+ if (!Array.isArray(parsed)) {
68
+ throw new Error(`Value must be a JSON array (e.g. [1, 2, 3]). Got: ${typeof parsed}`);
69
+ }
70
+ return parsed;
71
+ }
72
+ case 'object': {
73
+ const parsed = safeJsonParse(str);
74
+ if (typeof parsed !== 'object' ||
75
+ parsed === null ||
76
+ Array.isArray(parsed)) {
77
+ throw new Error(`Value must be a plain JSON object (e.g. {"key": "value"}). Got: ${Array.isArray(parsed) ? 'array' : typeof parsed}`);
78
+ }
79
+ return parsed;
80
+ }
81
+ default:
82
+ return str;
83
+ }
84
+ }
85
+ /** Parse a JSON string safely, throwing a user-friendly error on failure. */
86
+ function safeJsonParse(str) {
87
+ try {
88
+ return JSON.parse(str);
89
+ }
90
+ catch {
91
+ throw new Error(`Invalid JSON: ${str.slice(0, 100)}${str.length > 100 ? '…' : ''}`);
92
+ }
93
+ }
94
+ /** Infer the type name from a runtime value. */
95
+ function inferValueType(value) {
96
+ if (value === null)
97
+ return 'null';
98
+ if (Array.isArray(value))
99
+ return 'array';
100
+ return typeof value;
101
+ }
102
+ /**
103
+ * Deep-merge source object into target object.
104
+ * Only handles plain objects at each level; other values are overwritten.
105
+ */
106
+ function deepMerge(target, source) {
107
+ const result = { ...target };
108
+ for (const key of Object.keys(source)) {
109
+ const srcVal = source[key];
110
+ const tgtVal = target[key];
111
+ if (typeof srcVal === 'object' &&
112
+ srcVal !== null &&
113
+ !Array.isArray(srcVal) &&
114
+ typeof tgtVal === 'object' &&
115
+ tgtVal !== null &&
116
+ !Array.isArray(tgtVal)) {
117
+ result[key] = deepMerge(tgtVal, srcVal);
118
+ }
119
+ else {
120
+ result[key] = srcVal;
121
+ }
122
+ }
123
+ return result;
124
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "n8n-nodes-variable",
3
+ "version": "1.0.6",
4
+ "description": "Local and global scoped variables for n8n workflows",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "n8n",
8
+ "variables",
9
+ "workflow",
10
+ "state",
11
+ "discord",
12
+ "automation"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "Lou",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": ""
19
+ },
20
+ "main": "index.js",
21
+ "scripts": {
22
+ "build": "tsc && copyfiles -u 1 \"nodes/**/*.svg\" dist/ && copyfiles -u 1 \"nodes/**/*.json\" dist/",
23
+ "dev": "tsc --watch",
24
+ "lint": "eslint nodes --ext .ts",
25
+ "lintfix": "eslint nodes --ext .ts --fix",
26
+ "format": "prettier nodes --write",
27
+ "prepublishOnly": "npm run build && npm run lint",
28
+ "test": "jest --testPathPattern=tests/"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "n8n": {
34
+ "n8nNodesApiVersion": 1,
35
+ "credentials": [],
36
+ "nodes": [
37
+ "dist/nodes/Variable/Variable.node.js"
38
+ ]
39
+ },
40
+ "devDependencies": {
41
+ "@types/jest": "^29.5.12",
42
+ "@types/node": "^18.19.50",
43
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
44
+ "@typescript-eslint/parser": "^6.21.0",
45
+ "copyfiles": "^2.4.1",
46
+ "eslint": "^8.57.0",
47
+ "eslint-plugin-n8n-nodes-base": "^1.16.2",
48
+ "jest": "^29.7.0",
49
+ "n8n-workflow": "^1.0.0",
50
+ "prettier": "^3.3.3",
51
+ "ts-jest": "^29.2.5",
52
+ "typescript": "^5.5.4"
53
+ },
54
+ "peerDependencies": {
55
+ "n8n-workflow": "*"
56
+ },
57
+ "jest": {
58
+ "preset": "ts-jest",
59
+ "testEnvironment": "node",
60
+ "moduleFileExtensions": [
61
+ "ts",
62
+ "js"
63
+ ],
64
+ "testMatch": [
65
+ "**/tests/**/*.test.ts"
66
+ ],
67
+ "transform": {
68
+ "^.+\\.ts$": "ts-jest"
69
+ }
70
+ }
71
+ }