n8n-nodes-mautic-advanced 1.3.7 → 1.3.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.
@@ -3,6 +3,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runtimeZod = exports.RuntimeDynamicStructuredTool = void 0;
4
4
  // nodes/MauticAdvanced/ai-tools/runtime.ts
5
5
  const module_1 = require("module");
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).
10
+ *
11
+ * IMPORTANT: resolution must NEVER run at module-import time. n8n's
12
+ * node-directory-loader requires every node file in this package (including
13
+ * this one) purely to register node metadata, long before any workflow
14
+ * 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.
23
+ *
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.
31
+ */
6
32
  const ANCHOR_CANDIDATES = [
7
33
  // primary: @langchain/classic is a direct dep of @n8n/nodes-langchain, stable since n8n 2.4.x.
8
34
  // Its @langchain/core peerDep resolves to n8n's hoisted @langchain/core.
@@ -10,25 +36,224 @@ const ANCHOR_CANDIDATES = [
10
36
  // secondary: langchain package is in the n8n catalog and also has @langchain/core as peerDep.
11
37
  'langchain/agents',
12
38
  ];
13
- // require.resolve() works because n8n loads community nodes within its own module resolution
14
- // context. If future n8n isolates community node resolution, use
15
- // require.resolve(candidate, {paths: [n8nPackagePath]}) option.
16
- let runtimeRequire = null;
17
- const errors = [];
18
- for (const candidate of ANCHOR_CANDIDATES) {
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
+ let _anchorDiagnostic = null;
45
+ function getRuntimeRequire() {
46
+ if (_runtimeReq)
47
+ return _runtimeReq;
48
+ const tried = [];
49
+ for (const candidate of ANCHOR_CANDIDATES) {
50
+ try {
51
+ const resolved = require.resolve(candidate);
52
+ _runtimeReq = (0, module_1.createRequire)(resolved);
53
+ _anchorDiagnostic = `resolved via anchor: ${candidate}`;
54
+ return _runtimeReq;
55
+ }
56
+ catch (e) {
57
+ tried.push(`${candidate}: ${e.message}`);
58
+ }
59
+ }
60
+ _anchorDiagnostic = `Could not resolve LangChain anchor. Tried:\n${tried.join('\n')}`;
61
+ return undefined;
62
+ }
63
+ /**
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.
70
+ */
71
+ function findCachedExports(pathPattern, validate, excludeKey) {
19
72
  try {
20
- const resolved = require.resolve(candidate);
21
- runtimeRequire = (0, module_1.createRequire)(resolved);
22
- break;
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;
90
+ }
91
+ }
92
+ catch (_e) {
93
+ // best-effort — require.cache introspection is not guaranteed across Node versions
94
+ }
95
+ return undefined;
96
+ }
97
+ let _RuntimeDynamicStructuredTool;
98
+ let _runtimeZod;
99
+ let _langchainLoadError = null;
100
+ let _zodLoadError = null;
101
+ let _zodDiagnostic = null;
102
+ function resolveDynamicStructuredTool() {
103
+ if (_RuntimeDynamicStructuredTool)
104
+ return _RuntimeDynamicStructuredTool;
105
+ const runtimeReq = getRuntimeRequire();
106
+ if (runtimeReq) {
107
+ try {
108
+ const coreTools = runtimeReq('@langchain/core/tools');
109
+ if (typeof coreTools?.['DynamicStructuredTool'] === 'function') {
110
+ _RuntimeDynamicStructuredTool = coreTools['DynamicStructuredTool'];
111
+ return _RuntimeDynamicStructuredTool;
112
+ }
113
+ }
114
+ catch (e) {
115
+ _langchainLoadError = e.message;
116
+ }
23
117
  }
24
- catch (e) {
25
- errors.push(`${candidate}: ${e.message}`);
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;
26
128
  }
129
+ return _RuntimeDynamicStructuredTool;
27
130
  }
28
- if (!runtimeRequire) {
29
- throw new Error(`[runtime.ts] Could not resolve LangChain anchor. Tried:\n${errors.join('\n')}\n` +
30
- `Ensure @n8n/nodes-langchain is installed in n8n's node_modules.`);
131
+ function resolveZod() {
132
+ if (_runtimeZod)
133
+ 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) {
153
+ 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
+ }
163
+ }
164
+ }
165
+ catch (e) {
166
+ _zodLoadError = e.message;
167
+ }
168
+ }
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;
31
214
  }
32
- const coreTools = runtimeRequire('@langchain/core/tools');
33
- exports.RuntimeDynamicStructuredTool = coreTools['DynamicStructuredTool'];
34
- exports.runtimeZod = runtimeRequire('zod');
215
+ // IMPORTANT: Proxy target MUST be `function () {}`, not `{}`.
216
+ // ECMAScript spec §10.5.13: a Proxy only has [[Construct]] if its target does.
217
+ // Plain objects lack [[Construct]], so `new Proxy({}, ...)` throws
218
+ // "is not a constructor" before the construct trap ever fires.
219
+ exports.RuntimeDynamicStructuredTool = new Proxy(function () { }, {
220
+ construct(_target, args) {
221
+ const ctor = resolveDynamicStructuredTool();
222
+ 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}` : '') +
226
+ (_langchainLoadError ? ` Load error: ${_langchainLoadError}` : ''));
227
+ }
228
+ return new ctor(...args);
229
+ },
230
+ get(_target, prop) {
231
+ const ctor = resolveDynamicStructuredTool();
232
+ if (ctor) {
233
+ return ctor[prop];
234
+ }
235
+ return undefined;
236
+ },
237
+ });
238
+ exports.runtimeZod = new Proxy({}, {
239
+ get(_target, prop) {
240
+ // Guard: frameworks probe Symbol.toPrimitive, Symbol.toStringTag, .then
241
+ // (Promise thenable), and .constructor. Throwing on these causes
242
+ // misleading errors during structural inspection.
243
+ if (typeof prop === 'symbol' || prop === 'then' || prop === 'constructor')
244
+ return undefined;
245
+ const z = resolveZod();
246
+ if (!z) {
247
+ 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
+ : '') +
255
+ (_zodLoadError ? ` Load error: ${_zodLoadError}` : ''));
256
+ }
257
+ return z[prop];
258
+ },
259
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-mautic-advanced",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
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",
@@ -72,5 +72,13 @@
72
72
  "@types/change-case": "^0.0.30",
73
73
  "change-case": "^5.4.4",
74
74
  "zod": "^3.22.0"
75
+ },
76
+ "peerDependencies": {
77
+ "@langchain/core": "*"
78
+ },
79
+ "peerDependenciesMeta": {
80
+ "@langchain/core": {
81
+ "optional": true
82
+ }
75
83
  }
76
84
  }