n8n-nodes-mautic-advanced 1.3.9 → 1.3.10

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.
@@ -4,213 +4,181 @@ exports.runtimeZod = exports.RuntimeDynamicStructuredTool = void 0;
4
4
  // nodes/MauticAdvanced/ai-tools/runtime.ts
5
5
  const module_1 = require("module");
6
6
  /**
7
- * Anchor candidates packages in n8n's dependency tree that can serve as a
8
- * createRequire() anchor to resolve @langchain/core/tools and zod from n8n's
9
- * own module tree (not this package's bundled devDependency copies).
7
+ * Runtime resolution of `zod` and `@langchain/core`'s DynamicStructuredTool.
10
8
  *
11
9
  * IMPORTANT: resolution must NEVER run at module-import time. n8n's
12
10
  * node-directory-loader requires every node file in this package (including
13
11
  * this one) purely to register node metadata, long before any workflow
14
12
  * executes — and it aborts loading the ENTIRE package if any one file throws
15
- * while being required. That used to be exactly what happened here: under
16
- * pnpm-strict-isolated n8n installs (v2.29.x+) this package is installed
17
- * outside n8n's own node_modules tree (e.g. ~/.n8n/nodes/), so neither
18
- * anchor below resolves at all @langchain/classic/@langchain/core are only
19
- * reachable from inside @n8n/n8n-nodes-langchain's own isolated pnpm
20
- * subtree, which no filesystem-based require.resolve() from here can walk
21
- * into. The resulting throw took down the plain, non-AI `mauticAdvanced`
22
- * node and its trigger too, not just this AI-tools node.
13
+ * while being required. Doing filesystem resolution here at import time is
14
+ * exactly what took down the plain, non-AI `mauticAdvanced` node and its
15
+ * trigger under pnpm-strict-isolated installs (issue #2). Resolution is
16
+ * therefore deferred to first actual use inside the Proxy traps below, which
17
+ * only fire when a connected AI tool is invoked at workflow-execution time.
23
18
  *
24
- * Resolution is therefore deferred to first actual use, inside the Proxy
25
- * traps below (which only fire when a connected AI tool is invoked at
26
- * workflow-execution time via supplyData()). By then, n8n's own Agent/MCP
27
- * Trigger machinery has already loaded @langchain/core/tools itself (to run
28
- * its own tool-calling logic), so if the anchors still don't resolve, a
29
- * require.cache scan finds the exact module instance n8n itself loaded —
30
- * which works regardless of install layout.
19
+ * By execution time n8n's own Agent/MCP Trigger machinery has already loaded
20
+ * `@n8n/n8n-nodes-langchain` (and therefore zod and @langchain/core) into the
21
+ * process-global require.cache. We resolve the SAME module identity n8n uses
22
+ * the copy its `normalizeToolSchema` runs `instanceof ZodType` / `instanceof
23
+ * ZodType`-style checks against by two strategies, in order:
24
+ *
25
+ * 1. `require.main` n8n's own entry point. For a correctly-installed
26
+ * (non-isolated) host this lands on n8n's top-level copies directly.
27
+ * Guarded for `require.main` being undefined (ESM launch, worker_threads /
28
+ * queue-mode workers). We do NOT fall back to `__filename`: anchoring at
29
+ * this file would resolve THIS package's own bundled copy, which is the
30
+ * wrong module identity and fails n8n's `instanceof` checks silently.
31
+ *
32
+ * 2. An n8n-owned-tree anchor — a module already resident in require.cache
33
+ * whose path belongs to a package only n8n owns and community nodes never
34
+ * bundle. We `createRequire()` the dependency FROM that module's path, so
35
+ * the resolved copy is tied to n8n's tree by identity — independent of
36
+ * cache iteration order and of pnpm virtual-store path naming.
37
+ *
38
+ * If neither strategy resolves, we FAIL CLEAN (throw a diagnostic). We do NOT
39
+ * scan require.cache for a bare `zod` / `@langchain/core` and guess which copy
40
+ * is n8n's: under pnpm strict-isolated installs the cache key is the flat
41
+ * virtual-store realpath (`.../.pnpm/zod@3.x.x/node_modules/zod/...`) which does
42
+ * not encode the dependent package, so another installed community node's
43
+ * bundled copy can win the scan and yield a wrong-instance `ZodType` that
44
+ * n8n's `instanceof` check drops silently (issue #4). Returning nothing and
45
+ * throwing is safer than returning a possibly-wrong copy.
31
46
  */
32
- const ANCHOR_CANDIDATES = [
33
- // primary: @langchain/classic is a direct dep of @n8n/nodes-langchain, stable since n8n 2.4.x.
34
- // Its @langchain/core peerDep resolves to n8n's hoisted @langchain/core.
35
- '@langchain/classic/agents',
36
- // secondary: langchain package is in the n8n catalog and also has @langchain/core as peerDep.
37
- 'langchain/agents',
47
+ // n8n-owned anchor packages, in priority order. `@n8n/n8n-nodes-langchain` is
48
+ // always loaded at execution time and is the code that runs the instanceof
49
+ // checks, so resolving from it guarantees identical module identity.
50
+ const N8N_ANCHOR_PACKAGES = [
51
+ { name: '@n8n/n8n-nodes-langchain', pattern: /[\\/]@n8n[\\/]n8n-nodes-langchain[\\/]/ },
52
+ { name: '@langchain/classic', pattern: /[\\/]@langchain[\\/]classic[\\/]/ },
53
+ { name: 'n8n-workflow', pattern: /[\\/]n8n-workflow[\\/]/ },
54
+ { name: 'n8n-core', pattern: /[\\/]n8n-core[\\/]/ },
38
55
  ];
39
- // Memoized ONLY on success — a failed attempt must not permanently disable
40
- // retries (e.g. the require.cache scan fallback below may only succeed on a
41
- // later call, once n8n has finished loading its own langchain-dependent
42
- // nodes).
43
- let _runtimeReq;
44
56
  let _anchorDiagnostic = null;
45
- function getRuntimeRequire() {
46
- if (_runtimeReq)
47
- return _runtimeReq;
48
- const tried = [];
49
- for (const candidate of ANCHOR_CANDIDATES) {
57
+ /**
58
+ * Yields a createRequire() for EACH n8n-owned anchor package currently resident
59
+ * in Node's process-global module cache, in the priority order of
60
+ * N8N_ANCHOR_PACKAGES. Must be called lazily (not at module load) — n8n requires
61
+ * node files for registration before any workflow runs, i.e. before the anchor
62
+ * packages are necessarily resident in cache.
63
+ *
64
+ * IMPORTANT: this yields ALL available anchors rather than memoizing the first
65
+ * one found. Different anchors resolve different dependencies: `n8n-workflow` /
66
+ * `n8n-core` are imported by this package at registration time, so they are
67
+ * resident FIRST — but they can resolve `zod` and NOT `@langchain/core/tools`.
68
+ * Memoizing `n8n-workflow` as THE anchor would permanently starve
69
+ * DynamicStructuredTool resolution even after `@langchain/*` later loads. So the
70
+ * caller tries each anchor against the actual dependency and memoizes only at
71
+ * the dependency level (on success). Re-scanning the cache each unresolved call
72
+ * is cheap and only happens until the dependency resolves.
73
+ */
74
+ function* iterN8nAnchorRequires() {
75
+ let cache;
76
+ try {
77
+ // require.cache is the documented CommonJS alias for Module._cache, available
78
+ // directly in CJS module scope (this file compiles to CJS via tsc).
79
+ cache = require.cache;
80
+ }
81
+ catch (_e) {
82
+ // best-effort — require.cache introspection is not guaranteed across Node versions
83
+ cache = undefined;
84
+ }
85
+ if (!cache) {
86
+ _anchorDiagnostic = 'require.cache unavailable';
87
+ return;
88
+ }
89
+ const keys = Object.keys(cache);
90
+ const available = [];
91
+ for (const anchor of N8N_ANCHOR_PACKAGES) {
92
+ const key = keys.find((k) => anchor.pattern.test(k));
93
+ if (!key)
94
+ continue;
50
95
  try {
51
- const resolved = require.resolve(candidate);
52
- _runtimeReq = (0, module_1.createRequire)(resolved);
53
- _anchorDiagnostic = `resolved via anchor: ${candidate}`;
54
- return _runtimeReq;
96
+ const req = (0, module_1.createRequire)(key);
97
+ available.push(anchor.name);
98
+ yield { req, source: `anchor ${anchor.name}` };
55
99
  }
56
- catch (e) {
57
- tried.push(`${candidate}: ${e.message}`);
100
+ catch (_e) {
101
+ // createRequire failed for this anchor — try the next
58
102
  }
59
103
  }
60
- _anchorDiagnostic = `Could not resolve LangChain anchor. Tried:\n${tried.join('\n')}`;
61
- return undefined;
104
+ _anchorDiagnostic = available.length
105
+ ? `anchors available: ${available.join(', ')}`
106
+ : `no n8n-owned anchor resident in require.cache (tried ${N8N_ANCHOR_PACKAGES.map((a) => a.name).join(', ')})`;
62
107
  }
63
108
  /**
64
- * Scans Node's process-global module cache (shared across every node_modules
65
- * tree in this process, regardless of who loaded what) for an already-loaded
66
- * module whose resolved path matches `pathPattern`, returning the first
67
- * match whose exports satisfy `validate`. Must be called lazily (not at
68
- * module load) — n8n requires node files for registration before any
69
- * workflow runs, i.e. before langchain/zod are necessarily loaded into cache.
109
+ * Yields candidate requires in resolution priority order: require.main first
110
+ * (guarded for undefined), then EACH n8n-owned-tree anchor. Symmetric for every
111
+ * dependency no bare-cache scan, no self-exclusion. The caller must try the
112
+ * requested dependency against each yielded require and stop at the first that
113
+ * loads it.
70
114
  */
71
- function findCachedExports(pathPattern, validate, excludeKey) {
72
- try {
73
- // require.cache is the public, documented CommonJS alias that points at the exact
74
- // same underlying object as the internal Module._cache. It is available directly in
75
- // CJS module scope (this file compiles to CJS via tsc), so no require('module') needed.
76
- const cache = require.cache;
77
- if (!cache)
78
- return undefined;
79
- for (const key of Object.keys(cache)) {
80
- if (!pathPattern.test(key))
81
- continue;
82
- if (excludeKey && excludeKey(key))
83
- continue;
84
- const entry = cache[key];
85
- if (!entry)
86
- continue;
87
- const result = validate(entry.exports);
88
- if (result !== undefined)
89
- return result;
115
+ function* candidateRequires() {
116
+ if (require.main?.filename) {
117
+ try {
118
+ yield { req: (0, module_1.createRequire)(require.main.filename), source: 'require.main' };
119
+ }
120
+ catch (_e) {
121
+ // require.main resolution unavailable — fall through to anchors
90
122
  }
91
123
  }
92
- catch (_e) {
93
- // best-effort — require.cache introspection is not guaranteed across Node versions
94
- }
95
- return undefined;
124
+ yield* iterN8nAnchorRequires();
96
125
  }
97
126
  let _RuntimeDynamicStructuredTool;
98
127
  let _runtimeZod;
99
128
  let _langchainLoadError = null;
129
+ let _langchainDiagnostic = null;
100
130
  let _zodLoadError = null;
101
131
  let _zodDiagnostic = null;
102
132
  function resolveDynamicStructuredTool() {
103
133
  if (_RuntimeDynamicStructuredTool)
104
134
  return _RuntimeDynamicStructuredTool;
105
- const runtimeReq = getRuntimeRequire();
106
- if (runtimeReq) {
135
+ const attempts = [];
136
+ for (const { req, source } of candidateRequires()) {
107
137
  try {
108
- const coreTools = runtimeReq('@langchain/core/tools');
138
+ const coreTools = req('@langchain/core/tools');
109
139
  if (typeof coreTools?.['DynamicStructuredTool'] === 'function') {
110
140
  _RuntimeDynamicStructuredTool = coreTools['DynamicStructuredTool'];
141
+ _langchainDiagnostic = `resolved DynamicStructuredTool via ${source}`;
142
+ _langchainLoadError = null;
111
143
  return _RuntimeDynamicStructuredTool;
112
144
  }
145
+ attempts.push(`${source}: @langchain/core/tools lacked DynamicStructuredTool`);
113
146
  }
114
147
  catch (e) {
115
- _langchainLoadError = e.message;
148
+ attempts.push(`${source}: ${e.message}`);
116
149
  }
117
150
  }
118
- // Fallback for pnpm-isolated installs where no filesystem anchor reaches
119
- // @langchain/core at all: it is loaded by n8n's own Agent/MCP Trigger
120
- // machinery before our supplyData() ever runs, so grab it straight out of
121
- // require.cache once it is resident.
122
- const cached = findCachedExports(/[\\/]@langchain[\\/]core[\\/]/, (exports) => typeof exports['DynamicStructuredTool'] === 'function'
123
- ? exports['DynamicStructuredTool']
124
- : undefined);
125
- if (cached) {
126
- _RuntimeDynamicStructuredTool = cached;
127
- _langchainLoadError = null;
128
- }
129
- return _RuntimeDynamicStructuredTool;
151
+ _langchainLoadError = attempts.length
152
+ ? attempts.join('; ')
153
+ : 'no n8n-owned resolution source available (require.main undefined, no anchor in cache)';
154
+ return undefined;
130
155
  }
131
156
  function resolveZod() {
132
157
  if (_runtimeZod)
133
158
  return _runtimeZod;
134
- // Three-step resolution, in priority order: require.main → anchor → require.cache scan.
135
- //
136
- // Primary path: resolve zod starting from n8n's own main entry point's module tree
137
- // (require.main). For a correctly-installed (non-isolated) setup this lands on n8n's
138
- // top-level zod — the exact copy n8n's own normalizeToolSchema does `instanceof ZodType`
139
- // against. Even under pnpm-strict isolation this is a DIFFERENT resolution path than this
140
- // package's own local require('zod'), so it is far more likely to reach n8n's real copy
141
- // (or fail cleanly) than to silently return this package's OWN bundled zod. Resolved
142
- // lazily here (NOT at module top level) so node registration never triggers it — that
143
- // laziness is the entire point of this fix (#2).
144
- //
145
- // Only anchor at require.main when it is actually defined. When it is undefined (ESM-
146
- // launched n8n, worker_threads / queue-mode workers — exactly the deployment mode #2 was
147
- // filed against), there is NO useful main to anchor at: falling back to `__filename` would
148
- // anchor at THIS file (mautic's own runtime.js), whose `require.resolve('zod')` lands on
149
- // this package's OWN bundled zod — the copy that fails n8n's `instanceof ZodType` check.
150
- // So resolve-then-check the path and skip it if it points into this package's own tree,
151
- // reusing the same exclusion substring as the cache-scan path below.
152
- if (require.main?.filename) {
159
+ const attempts = [];
160
+ for (const { req, source } of candidateRequires()) {
153
161
  try {
154
- const mainReq = (0, module_1.createRequire)(require.main.filename);
155
- const resolvedPath = mainReq.resolve('zod');
156
- if (!resolvedPath.includes('n8n-nodes-mautic-advanced')) {
157
- _runtimeZod = mainReq('zod');
158
- if (_runtimeZod) {
159
- _zodDiagnostic = 'resolved zod via require.main';
160
- _zodLoadError = null;
161
- return _runtimeZod;
162
- }
162
+ const z = req('zod');
163
+ // Validate the exports look like zod via `ZodType` (the class n8n's
164
+ // normalizeToolSchema does `instanceof` against — the meaningful correctness
165
+ // signal) plus the `object` factory our schema-generator calls.
166
+ if (typeof z?.['ZodType'] === 'function' && typeof z?.['object'] === 'function') {
167
+ _runtimeZod = z;
168
+ _zodDiagnostic = `resolved zod via ${source}`;
169
+ _zodLoadError = null;
170
+ return _runtimeZod;
163
171
  }
172
+ attempts.push(`${source}: module did not look like zod`);
164
173
  }
165
174
  catch (e) {
166
- _zodLoadError = e.message;
175
+ attempts.push(`${source}: ${e.message}`);
167
176
  }
168
177
  }
169
- // Secondary path: the existing @langchain/classic / langchain anchor. Same resolve-then-
170
- // check exclusion for defense-in-depth consistency — very unlikely to ever resolve into
171
- // this package's own tree, but structurally guarded identically to the other two paths.
172
- const runtimeReq = getRuntimeRequire();
173
- if (runtimeReq) {
174
- try {
175
- const resolvedPath = runtimeReq.resolve('zod');
176
- if (!resolvedPath.includes('n8n-nodes-mautic-advanced')) {
177
- _runtimeZod = runtimeReq('zod');
178
- if (_runtimeZod) {
179
- _zodDiagnostic = 'resolved zod via anchor';
180
- _zodLoadError = null;
181
- return _runtimeZod;
182
- }
183
- }
184
- }
185
- catch (e) {
186
- _zodLoadError = e.message;
187
- }
188
- }
189
- // Tertiary path: scan require.cache for an already-resident zod once neither
190
- // filesystem resolution reaches it (pnpm-strict-isolated installs). The path regex is
191
- // narrowed to zod's own entry-point directories (lib/dist/index/v3/v4) so it never
192
- // matches zod-adjacent package names (e.g. zod-to-json-schema). Validate the exports
193
- // look like zod via `ZodType` (the class n8n's normalizeToolSchema does `instanceof`
194
- // against — the meaningful correctness signal) plus the `object` factory our
195
- // schema-generator calls.
196
- //
197
- // Exclusion guard: this package declares zod as a REAL dependency and imports it at
198
- // node-registration time (schema-generator.ts), so this package's OWN bundled zod is
199
- // ALWAYS resident in require.cache — potentially earlier in iteration order than n8n's.
200
- // We specifically want a DIFFERENT copy (n8n's), because a self-bundled copy would fail
201
- // n8n's `instanceof ZodType` class-identity check. So skip any cache key belonging to
202
- // this package's own bundled zod. (The pathPattern has already matched the zod segment,
203
- // so a package-name match on the key is sufficient and robust to both nested
204
- // `.../n8n-nodes-mautic-advanced/node_modules/zod/...` and flatter install layouts.)
205
- const cached = findCachedExports(/[\\/]zod[\\/](lib|dist|index|v3|v4)/, (exports) => typeof exports['ZodType'] === 'function' && typeof exports['object'] === 'function'
206
- ? exports
207
- : undefined, (key) => key.includes('n8n-nodes-mautic-advanced'));
208
- if (cached) {
209
- _runtimeZod = cached;
210
- _zodLoadError = null;
211
- _zodDiagnostic = 'resolved zod via require.cache scan (pnpm-isolated install)';
212
- }
213
- return _runtimeZod;
178
+ _zodLoadError = attempts.length
179
+ ? attempts.join('; ')
180
+ : 'no n8n-owned resolution source available (require.main undefined, no anchor in cache)';
181
+ return undefined;
214
182
  }
215
183
  // IMPORTANT: Proxy target MUST be `function () {}`, not `{}`.
216
184
  // ECMAScript spec §10.5.13: a Proxy only has [[Construct]] if its target does.
@@ -220,9 +188,11 @@ exports.RuntimeDynamicStructuredTool = new Proxy(function () { }, {
220
188
  construct(_target, args) {
221
189
  const ctor = resolveDynamicStructuredTool();
222
190
  if (!ctor) {
223
- throw new Error(`[MauticAdvancedAiTools] Could not resolve LangChain's DynamicStructuredTool. ` +
224
- `Ensure @n8n/nodes-langchain is installed in n8n's node_modules.` +
225
- (_anchorDiagnostic ? ` Diagnostic: ${_anchorDiagnostic}` : '') +
191
+ throw new Error(`[MauticAdvancedAiTools] Could not resolve LangChain's DynamicStructuredTool ` +
192
+ `via require.main or an n8n-owned require.cache anchor. ` +
193
+ `Ensure @n8n/n8n-nodes-langchain is installed in n8n's node_modules.` +
194
+ (_anchorDiagnostic ? ` Anchor: ${_anchorDiagnostic}.` : '') +
195
+ (_langchainDiagnostic ? ` ${_langchainDiagnostic}.` : '') +
226
196
  (_langchainLoadError ? ` Load error: ${_langchainLoadError}` : ''));
227
197
  }
228
198
  return new ctor(...args);
@@ -245,13 +215,10 @@ exports.runtimeZod = new Proxy({}, {
245
215
  const z = resolveZod();
246
216
  if (!z) {
247
217
  throw new Error(`[MauticAdvancedAiTools] Could not resolve zod (accessing .${String(prop)}) ` +
248
- `via require.main, LangChain anchor, or require.cache scan. ` +
249
- `Ensure @n8n/nodes-langchain is installed in n8n's node_modules.` +
250
- (_zodDiagnostic
251
- ? ` Diagnostic: ${_zodDiagnostic}`
252
- : _anchorDiagnostic
253
- ? ` Diagnostic: ${_anchorDiagnostic}`
254
- : '') +
218
+ `via require.main or an n8n-owned require.cache anchor. ` +
219
+ `Ensure @n8n/n8n-nodes-langchain is installed in n8n's node_modules.` +
220
+ (_anchorDiagnostic ? ` Anchor: ${_anchorDiagnostic}.` : '') +
221
+ (_zodDiagnostic ? ` ${_zodDiagnostic}.` : '') +
255
222
  (_zodLoadError ? ` Load error: ${_zodLoadError}` : ''));
256
223
  }
257
224
  return z[prop];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-mautic-advanced",
3
- "version": "1.3.9",
3
+ "version": "1.3.10",
4
4
  "description": "Enhanced n8n node for Mautic with comprehensive API coverage including tags, campaigns, categories, and advanced contact management",
5
5
  "keywords": [
6
6
  "n8n",