@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.
- package/build/builder/runtime-context.mjs +2 -2
- package/build/cli.mjs +0 -0
- package/build/controller/index.d.mts +3 -2
- package/build/controller/index.mjs +4 -3
- package/build/internal/dispatcher/index.d.mts +1 -4
- package/build/internal/dispatcher/index.mjs +3 -2
- package/package.json +2 -2
- package/src/builder/runtime-context.mts +2 -2
- package/src/controller/index.mts +4 -3
- package/src/internal/dispatcher/index.mts +3 -2
- package/test/dispatcher-ctx-forwarding.test.mjs +161 -0
|
@@ -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.
|
|
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('.')) {
|
package/src/controller/index.mts
CHANGED
|
@@ -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
|
+
}
|