@aloma.io/integration-sdk 3.8.66 → 3.8.67

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.
@@ -14,10 +14,10 @@ import { Connector } from '../internal/index.mjs';
14
14
  export function buildResolvers(methods, controller) {
15
15
  const resolvers = {};
16
16
  methods.forEach((method) => {
17
- const handler = async (args) => {
17
+ const handler = async (args, ctx) => {
18
18
  if (!methods.includes(method))
19
19
  throw new Error(`${method} not found`);
20
- return controller[method](args);
20
+ return controller[method](args, ctx);
21
21
  };
22
22
  if (method.includes('.')) {
23
23
  // Register nested tree for array-based resolution: ["crm", "contacts", "getPage"]
package/build/cli.mjs CHANGED
File without changes
@@ -44,8 +44,9 @@ export declare abstract class AbstractController {
44
44
  /**
45
45
  * called, when the remote public method is not found on the controller
46
46
  * @param arg
47
+ * @param ctx optional invocation context (taskId, namespace, sticky)
47
48
  */
48
- protected fallback(arg: any): Promise<any>;
49
+ protected fallback(arg: any, ctx?: any): Promise<any>;
49
50
  /**
50
51
  * will be invoked, when the connector has an endpoint enabled
51
52
  * will receive the data and can e.g. create a new task from it
@@ -134,7 +135,7 @@ export declare abstract class AbstractController {
134
135
  /**
135
136
  * @ignore
136
137
  **/
137
- __default(arg: any): Promise<any | null>;
138
+ __default(arg: any, ctx?: any): Promise<any | null>;
138
139
  /**
139
140
  * @ignore
140
141
  **/
@@ -41,8 +41,9 @@ export class AbstractController {
41
41
  /**
42
42
  * called, when the remote public method is not found on the controller
43
43
  * @param arg
44
+ * @param ctx optional invocation context (taskId, namespace, sticky)
44
45
  */
45
- fallback(arg) {
46
+ fallback(arg, ctx) {
46
47
  throw new Error('method not found');
47
48
  }
48
49
  /**
@@ -136,8 +137,8 @@ export class AbstractController {
136
137
  /**
137
138
  * @ignore
138
139
  **/
139
- async __default(arg) {
140
- return this.fallback(arg);
140
+ async __default(arg, ctx) {
141
+ return this.fallback(arg, ctx);
141
142
  }
142
143
  /**
143
144
  * @ignore
@@ -26,10 +26,7 @@ export default class Dispatcher {
26
26
  build(): {
27
27
  introspect: () => any;
28
28
  configSchema: () => any;
29
- execute: ({ query, variables }: {
30
- query: any;
31
- variables: any;
32
- }) => Promise<any>;
29
+ execute: ({ query, variables, taskId, namespace, sticky }: any) => Promise<any>;
33
30
  processPacket: (packet: any) => Promise<any>;
34
31
  start: (arg: any) => Promise<void>;
35
32
  };
@@ -133,7 +133,8 @@ ${arg.configurableClientScope}
133
133
  }
134
134
  return current;
135
135
  };
136
- const execute = async ({ query, variables }) => {
136
+ const execute = async ({ query, variables, taskId, namespace, sticky }) => {
137
+ const ctx = { taskId, namespace, sticky };
137
138
  if (!Array.isArray(query))
138
139
  query = [query];
139
140
  query = query
@@ -143,7 +144,7 @@ ${arg.configurableClientScope}
143
144
  const method = resolveMethod(query);
144
145
  if (!method && !_resolvers.__default)
145
146
  throw new Error(`${originalQuery} not found`);
146
- return method ? method(variables) : _resolvers.__default(variables ? { ...variables, __method: originalQuery } : variables);
147
+ return method ? method(variables, ctx) : _resolvers.__default(variables ? { ...variables, __method: originalQuery } : variables, ctx);
147
148
  };
148
149
  const introspect = () => local._types;
149
150
  const configSchema = () => local._config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aloma.io/integration-sdk",
3
- "version": "3.8.66",
3
+ "version": "3.8.67",
4
4
  "description": "",
5
5
  "author": "aloma.io",
6
6
  "license": "Apache-2.0",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "repository": {
61
61
  "type": "git",
62
- "url": "https://github.com/aloma-io/integration.git",
62
+ "url": "git+https://github.com/aloma-io/integration.git",
63
63
  "directory": "integration-sdk"
64
64
  },
65
65
  "publishConfig": {
@@ -16,9 +16,9 @@ export function buildResolvers(methods: string[], controller: any): any {
16
16
  const resolvers: any = {};
17
17
 
18
18
  methods.forEach((method) => {
19
- const handler = async (args: any) => {
19
+ const handler = async (args: any, ctx?: any) => {
20
20
  if (!methods.includes(method)) throw new Error(`${method} not found`);
21
- return controller[method](args);
21
+ return controller[method](args, ctx);
22
22
  };
23
23
 
24
24
  if (method.includes('.')) {
@@ -51,8 +51,9 @@ export abstract class AbstractController {
51
51
  /**
52
52
  * called, when the remote public method is not found on the controller
53
53
  * @param arg
54
+ * @param ctx optional invocation context (taskId, namespace, sticky)
54
55
  */
55
- protected fallback(arg: any): Promise<any> {
56
+ protected fallback(arg: any, ctx?: any): Promise<any> {
56
57
  throw new Error('method not found');
57
58
  }
58
59
 
@@ -186,8 +187,8 @@ export abstract class AbstractController {
186
187
  /**
187
188
  * @ignore
188
189
  **/
189
- async __default(arg: any): Promise<any | null> {
190
- return this.fallback(arg);
190
+ async __default(arg: any, ctx?: any): Promise<any | null> {
191
+ return this.fallback(arg, ctx);
191
192
  }
192
193
 
193
194
  /**
@@ -163,7 +163,8 @@ ${arg.configurableClientScope}
163
163
  return current;
164
164
  };
165
165
 
166
- const execute = async ({query, variables}) => {
166
+ const execute = async ({query, variables, taskId, namespace, sticky}: any) => {
167
+ const ctx = {taskId, namespace, sticky};
167
168
  if (!Array.isArray(query)) query = [query];
168
169
 
169
170
  query = query
@@ -177,7 +178,7 @@ ${arg.configurableClientScope}
177
178
  const method = resolveMethod(query);
178
179
  if (!method && !_resolvers.__default) throw new Error(`${originalQuery} not found`);
179
180
 
180
- return method ? method(variables) : _resolvers.__default(variables ? {...variables, __method: originalQuery} : variables);
181
+ return method ? method(variables, ctx) : _resolvers.__default(variables ? {...variables, __method: originalQuery} : variables, ctx);
181
182
  };
182
183
 
183
184
  const introspect = () => local._types;
@@ -0,0 +1,161 @@
1
+ import { Dispatcher } from '../build/internal/dispatcher/index.mjs';
2
+ import { buildResolvers } from '../build/builder/runtime-context.mjs';
3
+
4
+ // Simple test framework matching project convention
5
+ const colors = {
6
+ green: '\x1b[32m',
7
+ red: '\x1b[31m',
8
+ yellow: '\x1b[33m',
9
+ reset: '\x1b[0m',
10
+ cyan: '\x1b[36m'
11
+ };
12
+
13
+ let passed = 0;
14
+ let failed = 0;
15
+
16
+ async function test(name, fn) {
17
+ try {
18
+ console.log(`${colors.cyan}Running: ${name}${colors.reset}`);
19
+ await fn();
20
+ console.log(`${colors.green}✓ PASS: ${name}${colors.reset}\n`);
21
+ passed++;
22
+ } catch (error) {
23
+ console.log(`${colors.red}✗ FAIL: ${name}${colors.reset}`);
24
+ console.log(`${colors.red} Error: ${error.message}${colors.reset}\n`);
25
+ failed++;
26
+ }
27
+ }
28
+
29
+ function assert(condition, message) {
30
+ if (!condition) throw new Error(message || 'Assertion failed');
31
+ }
32
+
33
+ function assertEqual(actual, expected, message) {
34
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
35
+ throw new Error(message || `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
36
+ }
37
+ }
38
+
39
+ // Helper: build a dispatcher with given resolvers and execute a query+envelope.
40
+ // IMPORTANT: this test passes taskId/namespace/sticky in the envelope to verify the
41
+ // dispatcher destructures them and forwards them as a ctx object to handlers.
42
+ async function buildAndExecute(resolvers, envelope) {
43
+ const dispatcher = new Dispatcher();
44
+ dispatcher.types({ fields: {} });
45
+ dispatcher.resolvers(resolvers);
46
+ dispatcher.main(async () => {});
47
+ const built = dispatcher.build();
48
+ return built.execute(envelope);
49
+ }
50
+
51
+ // --- RED TESTS for ctx forwarding (SDK v3.8.67 additive change) ---
52
+
53
+ // Test 1: dispatcher.execute forwards ctx (taskId, namespace, sticky) to a NAMED METHOD handler
54
+ // as the second argument. Currently the dispatcher only passes `variables` — so the second
55
+ // arg is undefined and this test must FAIL (red) until the SDK is patched.
56
+ await test('dispatcher forwards ctx as second arg to named-method handler', async () => {
57
+ let receivedArgs;
58
+ let receivedCtx;
59
+
60
+ // Use buildResolvers so the wrapper is exercised exactly as it is in production.
61
+ const mockController = {
62
+ myMethod: async (args, ctx) => {
63
+ receivedArgs = args;
64
+ receivedCtx = ctx;
65
+ return { ok: true };
66
+ },
67
+ '__autocomplete': async () => ({}),
68
+ '__endpoint': async () => ({}),
69
+ '__default': async () => ({}),
70
+ };
71
+ const methods = ['myMethod', '__autocomplete', '__endpoint', '__default'];
72
+ const resolvers = buildResolvers(methods, mockController);
73
+
74
+ await buildAndExecute(resolvers, {
75
+ query: ['myMethod'],
76
+ variables: { foo: 'bar' },
77
+ taskId: 'task-123',
78
+ namespace: 'testing',
79
+ sticky: 'sticky-abc',
80
+ });
81
+
82
+ assertEqual(receivedArgs, { foo: 'bar' }, 'args should be forwarded unchanged');
83
+ assert(receivedCtx !== undefined, 'ctx should be defined as second argument');
84
+ assertEqual(receivedCtx.taskId, 'task-123', `ctx.taskId should be "task-123", got ${JSON.stringify(receivedCtx)}`);
85
+ assertEqual(receivedCtx.namespace, 'testing', `ctx.namespace should be "testing", got ${JSON.stringify(receivedCtx)}`);
86
+ assertEqual(receivedCtx.sticky, 'sticky-abc', `ctx.sticky should be "sticky-abc", got ${JSON.stringify(receivedCtx)}`);
87
+ });
88
+
89
+ // Test 2: dispatcher.execute forwards ctx to the __default (fallback) handler as second arg
90
+ // when the named method is not found. Currently the __default invocation only receives the
91
+ // merged variables — so ctx is undefined and this test must FAIL (red) until patched.
92
+ await test('dispatcher forwards ctx as second arg to __default fallback handler', async () => {
93
+ let receivedArg;
94
+ let receivedCtx;
95
+
96
+ // Resolvers map with ONLY __default — no named methods, so dispatcher falls back.
97
+ const resolvers = {
98
+ __default: async (arg, ctx) => {
99
+ receivedArg = arg;
100
+ receivedCtx = ctx;
101
+ return { fallback: true };
102
+ },
103
+ };
104
+
105
+ await buildAndExecute(resolvers, {
106
+ query: ['someUnknownMethod'],
107
+ variables: { hello: 'world' },
108
+ taskId: 'task-456',
109
+ namespace: 'testing',
110
+ sticky: 'sticky-def',
111
+ });
112
+
113
+ assert(receivedArg !== undefined, '__default should receive arg');
114
+ assertEqual(receivedArg.hello, 'world', 'variables should be merged into arg');
115
+ assertEqual(receivedArg.__method, ['someUnknownMethod'], '__method should be in arg');
116
+ assert(receivedCtx !== undefined, 'ctx should be defined as second argument to __default');
117
+ assertEqual(receivedCtx.taskId, 'task-456', `ctx.taskId should be "task-456", got ${JSON.stringify(receivedCtx)}`);
118
+ assertEqual(receivedCtx.namespace, 'testing', `ctx.namespace should be "testing"`);
119
+ assertEqual(receivedCtx.sticky, 'sticky-def', `ctx.sticky should be "sticky-def"`);
120
+ });
121
+
122
+ // Test 3: BACKWARD COMPATIBILITY. A controller method with the old 1-arg signature
123
+ // `async myMethod(args)` continues to work when called via the new dispatcher path.
124
+ // The second argument exists but is ignored. This test MUST PASS even in the red phase —
125
+ // if it fails red, the test setup is broken (not the implementation).
126
+ await test('one-arg handler still works (backward compatibility)', async () => {
127
+ let receivedArgs;
128
+
129
+ const mockController = {
130
+ // Note: only one parameter. Second arg, if any, must be silently ignored.
131
+ legacyMethod: async (args) => {
132
+ receivedArgs = args;
133
+ return { legacy: true, ...args };
134
+ },
135
+ '__autocomplete': async () => ({}),
136
+ '__endpoint': async () => ({}),
137
+ '__default': async () => ({}),
138
+ };
139
+ const methods = ['legacyMethod', '__autocomplete', '__endpoint', '__default'];
140
+ const resolvers = buildResolvers(methods, mockController);
141
+
142
+ const result = await buildAndExecute(resolvers, {
143
+ query: ['legacyMethod'],
144
+ variables: { id: 7 },
145
+ taskId: 'task-789',
146
+ namespace: 'testing',
147
+ sticky: 'sticky-ghi',
148
+ });
149
+
150
+ assertEqual(receivedArgs, { id: 7 }, 'legacy handler should receive args unchanged');
151
+ assertEqual(result, { legacy: true, id: 7 }, 'legacy handler should return correct result');
152
+ });
153
+
154
+ // --- Results ---
155
+ console.log(`\n${colors.yellow}--- Dispatcher ctx forwarding test results ---${colors.reset}`);
156
+ console.log(`${colors.green}Passed: ${passed}${colors.reset}`);
157
+ console.log(`${colors.red}Failed: ${failed}${colors.reset}`);
158
+
159
+ if (failed > 0) {
160
+ process.exit(1);
161
+ }