@chainlink/cre-sdk 0.0.1-alpha → 0.0.2-alpha
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/dist/generated/capabilities/internal/nodeaction/v1/node_action_pb.d.ts +69 -0
- package/dist/generated/capabilities/internal/nodeaction/v1/node_action_pb.js +29 -0
- package/dist/generated-sdk/capabilities/blockchain/evm/v1alpha/client_sdk_gen.d.ts +13 -18
- package/dist/generated-sdk/capabilities/blockchain/evm/v1alpha/client_sdk_gen.js +71 -260
- package/dist/generated-sdk/capabilities/internal/actionandtrigger/v1/basic_sdk_gen.d.ts +4 -9
- package/dist/generated-sdk/capabilities/internal/actionandtrigger/v1/basic_sdk_gen.js +14 -45
- package/dist/generated-sdk/capabilities/internal/basicaction/v1/basicaction_sdk_gen.d.ts +3 -7
- package/dist/generated-sdk/capabilities/internal/basicaction/v1/basicaction_sdk_gen.js +9 -36
- package/dist/generated-sdk/capabilities/internal/basictrigger/v1/basic_sdk_gen.d.ts +2 -8
- package/dist/generated-sdk/capabilities/internal/basictrigger/v1/basic_sdk_gen.js +8 -21
- package/dist/generated-sdk/capabilities/internal/consensus/v1alpha/consensus_sdk_gen.d.ts +5 -8
- package/dist/generated-sdk/capabilities/internal/consensus/v1alpha/consensus_sdk_gen.js +16 -60
- package/dist/generated-sdk/capabilities/internal/nodeaction/v1/basicaction_sdk_gen.d.ts +19 -0
- package/dist/generated-sdk/capabilities/internal/nodeaction/v1/basicaction_sdk_gen.js +36 -0
- package/dist/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen.d.ts +3 -7
- package/dist/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen.js +9 -36
- package/dist/generated-sdk/capabilities/networking/http/v1alpha/http_sdk_gen.d.ts +2 -8
- package/dist/generated-sdk/capabilities/networking/http/v1alpha/http_sdk_gen.js +8 -21
- package/dist/generated-sdk/capabilities/scheduler/cron/v1/cron_sdk_gen.d.ts +2 -8
- package/dist/generated-sdk/capabilities/scheduler/cron/v1/cron_sdk_gen.js +8 -21
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sdk/cre/index.d.ts +5 -19
- package/dist/sdk/cre/index.js +1 -17
- package/dist/sdk/errors.d.ts +12 -0
- package/dist/sdk/errors.js +21 -0
- package/dist/sdk/impl/runtime-impl.d.ts +38 -0
- package/dist/sdk/impl/runtime-impl.js +190 -0
- package/dist/sdk/impl/runtime-impl.test.js +334 -0
- package/dist/sdk/index.d.ts +3 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/runtime.d.ts +24 -0
- package/dist/sdk/utils/capabilities/capability-error.d.ts +0 -3
- package/dist/sdk/utils/capabilities/capability-error.js +0 -2
- package/dist/sdk/utils/config/configHandler.test.d.ts +1 -0
- package/dist/sdk/utils/config/configHandler.test.js +193 -0
- package/dist/sdk/utils/config/index.d.ts +4 -6
- package/dist/sdk/utils/config/index.js +10 -34
- package/dist/sdk/utils/index.d.ts +0 -3
- package/dist/sdk/utils/index.js +0 -3
- package/dist/sdk/utils/values/consensus_aggregators.d.ts +1 -1
- package/dist/sdk/{runtime → wasm}/host-bindings.d.ts +0 -1
- package/dist/sdk/{runtime → wasm}/host-bindings.js +0 -4
- package/dist/sdk/wasm/index.d.ts +1 -0
- package/dist/sdk/wasm/index.js +1 -0
- package/dist/sdk/wasm/runner.d.ts +14 -0
- package/dist/sdk/wasm/runner.js +114 -0
- package/dist/sdk/wasm/runner.test.d.ts +1 -0
- package/dist/sdk/wasm/runner.test.js +270 -0
- package/dist/sdk/wasm/runtime.d.ts +7 -0
- package/dist/sdk/wasm/runtime.js +55 -0
- package/dist/sdk/workflow.d.ts +13 -15
- package/dist/sdk/workflow.js +1 -21
- package/package.json +2 -2
- package/dist/sdk/engine/execute.d.ts +0 -4
- package/dist/sdk/engine/execute.js +0 -12
- package/dist/sdk/engine/execute.test.js +0 -129
- package/dist/sdk/engine/handleExecutionPhase.d.ts +0 -4
- package/dist/sdk/engine/handleExecutionPhase.js +0 -34
- package/dist/sdk/engine/handleSubscribePhase.d.ts +0 -3
- package/dist/sdk/engine/handleSubscribePhase.js +0 -23
- package/dist/sdk/logger.d.ts +0 -7
- package/dist/sdk/logger.js +0 -18
- package/dist/sdk/runtime/errors.d.ts +0 -23
- package/dist/sdk/runtime/errors.js +0 -30
- package/dist/sdk/runtime/index.d.ts +0 -3
- package/dist/sdk/runtime/index.js +0 -2
- package/dist/sdk/runtime/run-in-node-mode.d.ts +0 -12
- package/dist/sdk/runtime/run-in-node-mode.js +0 -47
- package/dist/sdk/runtime/run-in-node-mode.test.js +0 -116
- package/dist/sdk/runtime/runtime.d.ts +0 -36
- package/dist/sdk/runtime/runtime.js +0 -84
- package/dist/sdk/runtime/runtime.test.d.ts +0 -1
- package/dist/sdk/runtime/runtime.test.js +0 -58
- package/dist/sdk/testhelpers/dangerously-call-capability.d.ts +0 -10
- package/dist/sdk/testhelpers/dangerously-call-capability.js +0 -26
- package/dist/sdk/testhelpers/mock-host-bindings.d.ts +0 -15
- package/dist/sdk/testhelpers/mock-host-bindings.js +0 -25
- package/dist/sdk/testhelpers/mock-runtime.d.ts +0 -2
- package/dist/sdk/testhelpers/mock-runtime.js +0 -16
- package/dist/sdk/utils/await-async-request.d.ts +0 -9
- package/dist/sdk/utils/await-async-request.js +0 -27
- package/dist/sdk/utils/capabilities/call-capability.d.ts +0 -22
- package/dist/sdk/utils/capabilities/call-capability.js +0 -33
- package/dist/sdk/utils/capabilities/callback-id.d.ts +0 -3
- package/dist/sdk/utils/capabilities/callback-id.js +0 -22
- package/dist/sdk/utils/capabilities/http/fetch.d.ts +0 -44
- package/dist/sdk/utils/capabilities/http/fetch.js +0 -63
- package/dist/sdk/utils/do-request-async.d.ts +0 -12
- package/dist/sdk/utils/do-request-async.js +0 -17
- package/dist/sdk/utils/error-boundary.d.ts +0 -2
- package/dist/sdk/utils/error-boundary.js +0 -30
- package/dist/sdk/utils/get-request.d.ts +0 -1
- package/dist/sdk/utils/get-request.js +0 -15
- package/dist/sdk/utils/lazy-promise.d.ts +0 -59
- package/dist/sdk/utils/lazy-promise.js +0 -81
- package/dist/sdk/utils/random/get-rand.d.ts +0 -3
- package/dist/sdk/utils/random/get-rand.js +0 -6
- package/dist/sdk/utils/random/random.d.ts +0 -35
- package/dist/sdk/utils/random/random.js +0 -123
- package/dist/sdk/utils/secrets/await-async-secret.d.ts +0 -1
- package/dist/sdk/utils/secrets/await-async-secret.js +0 -29
- package/dist/sdk/utils/secrets/do-get-secret.d.ts +0 -1
- package/dist/sdk/utils/secrets/do-get-secret.js +0 -14
- package/dist/sdk/utils/secrets/get-secret.d.ts +0 -1
- package/dist/sdk/utils/secrets/get-secret.js +0 -10
- package/dist/sdk/utils/send-error.d.ts +0 -1
- package/dist/sdk/utils/send-error.js +0 -16
- package/dist/sdk/utils/send-response-value.d.ts +0 -2
- package/dist/sdk/utils/send-response-value.js +0 -15
- package/dist/sdk/utils/time/get-time.d.ts +0 -16
- package/dist/sdk/utils/time/get-time.js +0 -21
- /package/dist/sdk/{engine/execute.test.d.ts → impl/runtime-impl.test.d.ts} +0 -0
- /package/dist/sdk/{runtime/run-in-node-mode.test.d.ts → runtime.js} +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { afterEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
import { create } from '@bufbuild/protobuf';
|
|
3
|
+
import { anyPack, anyUnpack } from '@bufbuild/protobuf/wkt';
|
|
4
|
+
import { InputSchema, OutputSchema as OutputSchema, } from '../../generated/capabilities/internal/actionandtrigger/v1/action_and_trigger_pb';
|
|
5
|
+
import { InputsSchema, OutputsSchema, } from '../../generated/capabilities/internal/basicaction/v1/basic_action_pb';
|
|
6
|
+
import { NodeInputsSchema, NodeOutputsSchema, } from '../../generated/capabilities/internal/nodeaction/v1/node_action_pb';
|
|
7
|
+
import { AggregationType, AwaitCapabilitiesResponseSchema, CapabilityResponseSchema, ConsensusDescriptorSchema, FieldsMapSchema, Mode, } from '../../generated/sdk/v1alpha/sdk_pb';
|
|
8
|
+
import { BasicCapability } from '../../generated-sdk/capabilities/internal/actionandtrigger/v1/basic_sdk_gen';
|
|
9
|
+
import { BasicActionCapability } from '../../generated-sdk/capabilities/internal/basicaction/v1/basicaction_sdk_gen';
|
|
10
|
+
import { ConsensusCapability } from '../../generated-sdk/capabilities/internal/consensus/v1alpha/consensus_sdk_gen';
|
|
11
|
+
import { BasicActionCapability as NodeActionCapability } from '../../generated-sdk/capabilities/internal/nodeaction/v1/basicaction_sdk_gen';
|
|
12
|
+
import { ConsensusAggregationByFields, consensusMedianAggregation, median, Value, } from '../utils';
|
|
13
|
+
import { CapabilityError } from '../utils/capabilities/capability-error';
|
|
14
|
+
import { DonModeError, NodeModeError } from '../errors';
|
|
15
|
+
import { RuntimeImpl } from './runtime-impl';
|
|
16
|
+
// Helper function to create a RuntimeHelpers mock with error-throwing defaults
|
|
17
|
+
function createRuntimeHelpersMock(overrides = {}) {
|
|
18
|
+
// Create default implementation that throws errors for all methods
|
|
19
|
+
const defaultMock = {
|
|
20
|
+
call: mock(() => {
|
|
21
|
+
throw new Error('Method not implemented: call');
|
|
22
|
+
}),
|
|
23
|
+
await: mock(() => {
|
|
24
|
+
throw new Error('Method not implemented: await');
|
|
25
|
+
}),
|
|
26
|
+
getSecrets: mock(() => {
|
|
27
|
+
throw new Error('Method not implemented: getSecrets');
|
|
28
|
+
}),
|
|
29
|
+
awaitSecrets: mock(() => {
|
|
30
|
+
throw new Error('Method not implemented: awaitSecrets');
|
|
31
|
+
}),
|
|
32
|
+
// switchModes is used in every test, most will ignore it, so it's safe to default to a no-op.
|
|
33
|
+
switchModes: mock(() => { }),
|
|
34
|
+
now: mock(() => {
|
|
35
|
+
throw new Error('Method not implemented: now');
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
// Return a merged object with overrides taking precedence
|
|
39
|
+
return { ...defaultMock, ...overrides };
|
|
40
|
+
}
|
|
41
|
+
const anyMaxSize = 1024n * 1024n;
|
|
42
|
+
// Store original prototypes for manual restoration
|
|
43
|
+
const originalConsensusSimple = ConsensusCapability.prototype.simple;
|
|
44
|
+
const originalNodeActionPerformAction = NodeActionCapability.prototype.performAction;
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
// Restore all mocks after each test
|
|
47
|
+
mock.restore();
|
|
48
|
+
// Manually restore prototype methods
|
|
49
|
+
ConsensusCapability.prototype.simple = originalConsensusSimple;
|
|
50
|
+
NodeActionCapability.prototype.performAction = originalNodeActionPerformAction;
|
|
51
|
+
});
|
|
52
|
+
describe('test runtime', () => {
|
|
53
|
+
describe('test call capability', () => {
|
|
54
|
+
// TODO:
|
|
55
|
+
test.skip('runs async - proper async implementation in progress', async () => {
|
|
56
|
+
const anyResult1 = 'ok1';
|
|
57
|
+
const anyResult2 = 'ok2';
|
|
58
|
+
var expectedCall = 1;
|
|
59
|
+
var expectedAwait = 2;
|
|
60
|
+
const input1 = create(InputsSchema, { inputThing: true });
|
|
61
|
+
const input2 = create(InputSchema, { name: 'input' });
|
|
62
|
+
const helpers = createRuntimeHelpersMock({
|
|
63
|
+
call: mock((request) => {
|
|
64
|
+
switch (request.callbackId) {
|
|
65
|
+
case 1:
|
|
66
|
+
return expectCapabilityCall(request, input1, InputsSchema, BasicActionCapability.CAPABILITY_ID, expectedCall++);
|
|
67
|
+
case 2:
|
|
68
|
+
return expectCapabilityCall(request, input2, InputSchema, BasicCapability.CAPABILITY_ID, expectedCall++);
|
|
69
|
+
default:
|
|
70
|
+
throw new Error(`Unexpected call with callbackId: ${request.callbackId}`);
|
|
71
|
+
}
|
|
72
|
+
}),
|
|
73
|
+
await: mock((request) => {
|
|
74
|
+
expect(request.ids.length).toEqual(1);
|
|
75
|
+
var payload;
|
|
76
|
+
const id = request.ids[0];
|
|
77
|
+
switch (id) {
|
|
78
|
+
case 1:
|
|
79
|
+
expect(1).toEqual(expectedAwait);
|
|
80
|
+
expectedAwait--;
|
|
81
|
+
payload = anyPack(OutputsSchema, create(OutputsSchema, { adaptedThing: anyResult1 }));
|
|
82
|
+
break;
|
|
83
|
+
case 2:
|
|
84
|
+
expect(2).toEqual(expectedAwait);
|
|
85
|
+
expectedAwait--;
|
|
86
|
+
payload = anyPack(OutputSchema, create(OutputSchema, { welcome: anyResult2 }));
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(`Unexpected await with id: ${request.ids[0]}`);
|
|
90
|
+
}
|
|
91
|
+
return create(AwaitCapabilitiesResponseSchema, {
|
|
92
|
+
responses: {
|
|
93
|
+
[id]: create(CapabilityResponseSchema, {
|
|
94
|
+
response: { case: 'payload', value: payload },
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
101
|
+
const workflowAction1 = new BasicActionCapability();
|
|
102
|
+
const call1 = workflowAction1.performAction(runtime, input1);
|
|
103
|
+
const workflowAction2 = new BasicCapability();
|
|
104
|
+
const call2 = workflowAction2.action(runtime, input2);
|
|
105
|
+
const result2 = await call2.result();
|
|
106
|
+
expect(result2.welcome).toEqual(anyResult2);
|
|
107
|
+
const result1 = await call1.result();
|
|
108
|
+
expect(result1.adaptedThing).toEqual(anyResult1);
|
|
109
|
+
});
|
|
110
|
+
test('call capability errors', async () => {
|
|
111
|
+
const helpers = createRuntimeHelpersMock({
|
|
112
|
+
call: mock((_) => {
|
|
113
|
+
return false;
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
117
|
+
const workflowAction1 = new BasicActionCapability();
|
|
118
|
+
const call1 = workflowAction1
|
|
119
|
+
.performAction(runtime, create(InputsSchema, { inputThing: true }))
|
|
120
|
+
.result();
|
|
121
|
+
expect(call1).rejects.toThrow(new CapabilityError(`Capability not found ${BasicActionCapability.CAPABILITY_ID}`, {
|
|
122
|
+
callbackId: 1,
|
|
123
|
+
capabilityId: BasicActionCapability.CAPABILITY_ID,
|
|
124
|
+
method: 'PerformAction',
|
|
125
|
+
}));
|
|
126
|
+
});
|
|
127
|
+
test('capability errors are returned to the caller', async () => {
|
|
128
|
+
const anyError = 'error';
|
|
129
|
+
const helpers = createRuntimeHelpersMock({
|
|
130
|
+
call: mock((_) => {
|
|
131
|
+
return true;
|
|
132
|
+
}),
|
|
133
|
+
await: mock((request) => {
|
|
134
|
+
expect(request.ids.length).toEqual(1);
|
|
135
|
+
return create(AwaitCapabilitiesResponseSchema, {
|
|
136
|
+
responses: {
|
|
137
|
+
[request.ids[0]]: create(CapabilityResponseSchema, {
|
|
138
|
+
response: { case: 'error', value: anyError },
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
145
|
+
const workflowAction1 = new BasicActionCapability();
|
|
146
|
+
const call1 = workflowAction1
|
|
147
|
+
.performAction(runtime, create(InputsSchema, { inputThing: true }))
|
|
148
|
+
.result();
|
|
149
|
+
expect(call1).rejects.toThrow(new CapabilityError('Error ' + anyError, {
|
|
150
|
+
callbackId: 1,
|
|
151
|
+
capabilityId: BasicActionCapability.CAPABILITY_ID,
|
|
152
|
+
method: 'PerformAction',
|
|
153
|
+
}));
|
|
154
|
+
});
|
|
155
|
+
test('await errors', async () => {
|
|
156
|
+
const anyError = 'error';
|
|
157
|
+
const helpers = createRuntimeHelpersMock({
|
|
158
|
+
call: mock((_) => {
|
|
159
|
+
return true;
|
|
160
|
+
}),
|
|
161
|
+
await: mock((_) => {
|
|
162
|
+
throw new Error(anyError);
|
|
163
|
+
}),
|
|
164
|
+
});
|
|
165
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
166
|
+
const workflowAction1 = new BasicActionCapability();
|
|
167
|
+
const call1 = workflowAction1
|
|
168
|
+
.performAction(runtime, create(InputsSchema, { inputThing: true }))
|
|
169
|
+
.result();
|
|
170
|
+
expect(call1).rejects.toThrow(new CapabilityError(anyError, {
|
|
171
|
+
callbackId: 1,
|
|
172
|
+
capabilityId: BasicActionCapability.CAPABILITY_ID,
|
|
173
|
+
method: 'PerformAction',
|
|
174
|
+
}));
|
|
175
|
+
});
|
|
176
|
+
test('await missing response', async () => {
|
|
177
|
+
const helpers = createRuntimeHelpersMock({
|
|
178
|
+
call: mock((_) => {
|
|
179
|
+
return true;
|
|
180
|
+
}),
|
|
181
|
+
await: mock((_) => {
|
|
182
|
+
return create(AwaitCapabilitiesResponseSchema, { responses: {} });
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
186
|
+
const workflowAction1 = new BasicActionCapability();
|
|
187
|
+
const call1 = workflowAction1
|
|
188
|
+
.performAction(runtime, create(InputsSchema, { inputThing: true }))
|
|
189
|
+
.result();
|
|
190
|
+
expect(call1).rejects.toThrow(new CapabilityError('No response found for callback ID 1', {
|
|
191
|
+
callbackId: 1,
|
|
192
|
+
capabilityId: BasicActionCapability.CAPABILITY_ID,
|
|
193
|
+
method: 'PerformAction',
|
|
194
|
+
}));
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('test now conversts to date', () => {
|
|
199
|
+
test('now converts to date', () => {
|
|
200
|
+
const helpers = createRuntimeHelpersMock({
|
|
201
|
+
now: mock(() => 1716153600000),
|
|
202
|
+
});
|
|
203
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
204
|
+
const now = runtime.now();
|
|
205
|
+
expect(now).toEqual(new Date(1716153600000 / 1000000));
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
describe('test run in node mode', () => {
|
|
209
|
+
test('successful consensus', async () => {
|
|
210
|
+
const anyObservation = 10;
|
|
211
|
+
const anyMedian = 11;
|
|
212
|
+
const modes = [];
|
|
213
|
+
const helpers = createRuntimeHelpersMock({
|
|
214
|
+
switchModes: mock((mode) => {
|
|
215
|
+
modes.push(mode);
|
|
216
|
+
}),
|
|
217
|
+
});
|
|
218
|
+
ConsensusCapability.prototype.simple = mock((_, inputs) => {
|
|
219
|
+
expect(modes).toEqual([Mode.DON, Mode.NODE, Mode.DON]);
|
|
220
|
+
expect(inputs.default).toBeUndefined();
|
|
221
|
+
const consensusDescriptor = create(ConsensusDescriptorSchema, {
|
|
222
|
+
descriptor: {
|
|
223
|
+
case: 'fieldsMap',
|
|
224
|
+
value: create(FieldsMapSchema, {
|
|
225
|
+
fields: {
|
|
226
|
+
outputThing: create(ConsensusDescriptorSchema, {
|
|
227
|
+
descriptor: { case: 'aggregation', value: AggregationType.MEDIAN },
|
|
228
|
+
}),
|
|
229
|
+
},
|
|
230
|
+
}),
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
expect(inputs.descriptors).toEqual(consensusDescriptor);
|
|
234
|
+
expect(inputs.$typeName).not.toBeUndefined();
|
|
235
|
+
const inputsProto = inputs;
|
|
236
|
+
expect(inputsProto.observation.case).toEqual('value');
|
|
237
|
+
expect(Value.wrap(inputsProto.observation.value).unwrapToType({
|
|
238
|
+
factory: () => create(NodeOutputsSchema),
|
|
239
|
+
}).outputThing).toEqual(anyObservation);
|
|
240
|
+
return {
|
|
241
|
+
result: async () => Value.from(create(NodeOutputsSchema, { outputThing: anyMedian })).proto(),
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
NodeActionCapability.prototype.performAction = mock((_, __) => {
|
|
245
|
+
expect(modes).toEqual([Mode.DON, Mode.NODE]);
|
|
246
|
+
return {
|
|
247
|
+
result: async () => create(NodeOutputsSchema, { outputThing: anyObservation }),
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
251
|
+
const result = await runtime.runInNodeMode(async (nodeRuntime) => {
|
|
252
|
+
const capability = new NodeActionCapability();
|
|
253
|
+
return await capability
|
|
254
|
+
.performAction(nodeRuntime, create(NodeInputsSchema, { inputThing: true }))
|
|
255
|
+
.result();
|
|
256
|
+
}, ConsensusAggregationByFields({ outputThing: median }))();
|
|
257
|
+
expect(result.outputThing).toEqual(anyMedian);
|
|
258
|
+
});
|
|
259
|
+
test('failed consensus', async () => {
|
|
260
|
+
const anyError = 'error';
|
|
261
|
+
const helpers = createRuntimeHelpersMock({
|
|
262
|
+
switchModes: mock((_) => { }),
|
|
263
|
+
});
|
|
264
|
+
ConsensusCapability.prototype.simple = mock((_, inputs) => {
|
|
265
|
+
expect(inputs.default).toBeUndefined();
|
|
266
|
+
expect(inputs.descriptors).toEqual(create(ConsensusDescriptorSchema, {
|
|
267
|
+
descriptor: { case: 'aggregation', value: AggregationType.MEDIAN },
|
|
268
|
+
}));
|
|
269
|
+
expect(inputs.$typeName).not.toBeUndefined();
|
|
270
|
+
const inputsProto = inputs;
|
|
271
|
+
expect(inputsProto.observation.case).toEqual('error');
|
|
272
|
+
expect(inputsProto.observation.value).toEqual(anyError);
|
|
273
|
+
return { result: async () => Promise.reject(new Error(anyError)) };
|
|
274
|
+
});
|
|
275
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
276
|
+
const result = runtime.runInNodeMode(async (nodeRuntime) => {
|
|
277
|
+
throw new Error(anyError);
|
|
278
|
+
}, consensusMedianAggregation())();
|
|
279
|
+
expect(result).rejects.toThrow(new Error(anyError));
|
|
280
|
+
});
|
|
281
|
+
test('node runtime in don mode fails', async () => {
|
|
282
|
+
const helpers = createRuntimeHelpersMock({
|
|
283
|
+
switchModes: mock((_) => { }),
|
|
284
|
+
call: mock((_) => {
|
|
285
|
+
expect(false).toBe(true);
|
|
286
|
+
return false;
|
|
287
|
+
}),
|
|
288
|
+
});
|
|
289
|
+
ConsensusCapability.prototype.simple = mock((_, __) => {
|
|
290
|
+
return { result: async () => Promise.resolve(Value.from(0).proto()) };
|
|
291
|
+
});
|
|
292
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
293
|
+
var nrt;
|
|
294
|
+
await runtime.runInNodeMode(async (nodeRuntime) => {
|
|
295
|
+
nrt = nodeRuntime;
|
|
296
|
+
return 0;
|
|
297
|
+
}, consensusMedianAggregation())();
|
|
298
|
+
const capability = new NodeActionCapability();
|
|
299
|
+
expect(nrt).toBeDefined();
|
|
300
|
+
expect(capability.performAction(nrt, create(NodeInputsSchema, { inputThing: true })).result()).rejects.toThrow(new NodeModeError());
|
|
301
|
+
});
|
|
302
|
+
test('don runtime in node mode fails', async () => {
|
|
303
|
+
const helpers = createRuntimeHelpersMock({
|
|
304
|
+
switchModes: mock((_) => { }),
|
|
305
|
+
});
|
|
306
|
+
ConsensusCapability.prototype.simple = mock((_, inputs) => {
|
|
307
|
+
expect(inputs.default).toBeUndefined();
|
|
308
|
+
expect(inputs.descriptors).toEqual(create(ConsensusDescriptorSchema, {
|
|
309
|
+
descriptor: { case: 'aggregation', value: AggregationType.MEDIAN },
|
|
310
|
+
}));
|
|
311
|
+
expect(inputs.$typeName).not.toBeUndefined();
|
|
312
|
+
const inputsProto = inputs;
|
|
313
|
+
expect(inputsProto.observation.case).toEqual('error');
|
|
314
|
+
expect(inputsProto.observation.value).toEqual(new DonModeError().message);
|
|
315
|
+
return { result: async () => Promise.reject(new DonModeError()) };
|
|
316
|
+
});
|
|
317
|
+
const runtime = new RuntimeImpl({}, 1, helpers, anyMaxSize);
|
|
318
|
+
const result = runtime.runInNodeMode(async (_) => {
|
|
319
|
+
const capability = new BasicActionCapability();
|
|
320
|
+
await capability.performAction(runtime, create(InputsSchema, { inputThing: true })).result();
|
|
321
|
+
return 0;
|
|
322
|
+
}, consensusMedianAggregation())();
|
|
323
|
+
expect(result).rejects.toThrow(new DonModeError());
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
function expectCapabilityCall(request, expectedPayload, desc, expectedCapabilityId, expectedCallbackId) {
|
|
327
|
+
expect(request.id).toEqual(expectedCapabilityId);
|
|
328
|
+
expect(request.method).toEqual('PerformAction');
|
|
329
|
+
expect(request.callbackId).toEqual(expectedCallbackId);
|
|
330
|
+
expect(request.payload).toBeDefined();
|
|
331
|
+
const payload = anyUnpack(request.payload, desc);
|
|
332
|
+
expect(payload).toEqual(expectedPayload);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
package/dist/sdk/index.d.ts
CHANGED
package/dist/sdk/index.js
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Message } from '@bufbuild/protobuf';
|
|
2
|
+
import type { GenMessage } from '@bufbuild/protobuf/codegenv2';
|
|
3
|
+
import type { ConsensusAggregation, PrimitiveTypes, UnwrapOptions } from './utils';
|
|
4
|
+
import type { SecretsProvider } from '.';
|
|
5
|
+
export type CallCapabilityParams<I extends Message, O extends Message> = {
|
|
6
|
+
capabilityId: string;
|
|
7
|
+
method: string;
|
|
8
|
+
payload: I;
|
|
9
|
+
inputSchema: GenMessage<I>;
|
|
10
|
+
outputSchema: GenMessage<O>;
|
|
11
|
+
};
|
|
12
|
+
export type BaseRuntime<C> = {
|
|
13
|
+
config: C;
|
|
14
|
+
callCapability<I extends Message, O extends Message>(params: CallCapabilityParams<I, O>): {
|
|
15
|
+
result: () => Promise<O>;
|
|
16
|
+
};
|
|
17
|
+
now(): Date;
|
|
18
|
+
};
|
|
19
|
+
export type Runtime<C> = BaseRuntime<C> & SecretsProvider & {
|
|
20
|
+
runInNodeMode<TArgs extends any[], TOutput>(fn: (nodeRuntime: NodeRuntime<C>, ...args: TArgs) => Promise<TOutput> | TOutput, consensusAggregation: ConsensusAggregation<TOutput, true>, unwrapOptions?: TOutput extends PrimitiveTypes ? never : UnwrapOptions<TOutput>): (...args: TArgs) => Promise<TOutput>;
|
|
21
|
+
};
|
|
22
|
+
export type NodeRuntime<C> = BaseRuntime<C> & {
|
|
23
|
+
readonly _isNodeRuntime: true;
|
|
24
|
+
};
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import type { Mode } from '../../../generated/sdk/v1alpha/sdk_pb';
|
|
2
1
|
export declare class CapabilityError extends Error {
|
|
3
2
|
name: string;
|
|
4
3
|
capabilityId?: string;
|
|
5
4
|
method?: string;
|
|
6
|
-
mode?: Mode;
|
|
7
5
|
callbackId?: number;
|
|
8
6
|
constructor(message: string, options?: {
|
|
9
7
|
capabilityId?: string;
|
|
10
8
|
method?: string;
|
|
11
|
-
mode?: Mode;
|
|
12
9
|
callbackId?: number;
|
|
13
10
|
});
|
|
14
11
|
}
|
|
@@ -2,7 +2,6 @@ export class CapabilityError extends Error {
|
|
|
2
2
|
name;
|
|
3
3
|
capabilityId;
|
|
4
4
|
method;
|
|
5
|
-
mode;
|
|
6
5
|
callbackId;
|
|
7
6
|
constructor(message, options) {
|
|
8
7
|
super(message);
|
|
@@ -10,7 +9,6 @@ export class CapabilityError extends Error {
|
|
|
10
9
|
if (options) {
|
|
11
10
|
this.capabilityId = options.capabilityId;
|
|
12
11
|
this.method = options.method;
|
|
13
|
-
this.mode = options.mode;
|
|
14
12
|
this.callbackId = options.callbackId;
|
|
15
13
|
}
|
|
16
14
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { configHandler } from './index';
|
|
4
|
+
// Mock schema for testing
|
|
5
|
+
const mockSchema = {
|
|
6
|
+
'~standard': {
|
|
7
|
+
version: 1,
|
|
8
|
+
vendor: 'test',
|
|
9
|
+
validate: (input) => {
|
|
10
|
+
if (typeof input !== 'object' || input === null) {
|
|
11
|
+
return { issues: [{ message: 'Expected object' }] };
|
|
12
|
+
}
|
|
13
|
+
return { value: input };
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
// Helper to create mock ExecuteRequest
|
|
18
|
+
const createMockRequest = (configData) => ({
|
|
19
|
+
config: new TextEncoder().encode(configData),
|
|
20
|
+
});
|
|
21
|
+
describe('configHandler', () => {
|
|
22
|
+
it('should work with no params passed in - uses default JSON parser', async () => {
|
|
23
|
+
const configData = '{"name": "test", "value": 42}';
|
|
24
|
+
const request = createMockRequest(configData);
|
|
25
|
+
const result = await configHandler(request);
|
|
26
|
+
expect(result).toEqual({ name: 'test', value: 42 });
|
|
27
|
+
});
|
|
28
|
+
it('should work with only parser passed in - JSON parsing', async () => {
|
|
29
|
+
const configData = '{"schedule": "0 0 * * *", "enabled": true}';
|
|
30
|
+
const request = createMockRequest(configData);
|
|
31
|
+
const customParser = (config) => {
|
|
32
|
+
const json = JSON.parse(Buffer.from(config).toString());
|
|
33
|
+
return { ...json, parsed: true };
|
|
34
|
+
};
|
|
35
|
+
const result = await configHandler(request, { configParser: customParser });
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
schedule: '0 0 * * *',
|
|
38
|
+
enabled: true,
|
|
39
|
+
parsed: true,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
it('should work with only parser passed in - noop parser (keeps Uint8Array)', async () => {
|
|
43
|
+
const configData = 'raw binary data';
|
|
44
|
+
const request = createMockRequest(configData);
|
|
45
|
+
const noopParser = (config) => config;
|
|
46
|
+
const result = await configHandler(request, { configParser: noopParser });
|
|
47
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
48
|
+
expect(new TextDecoder().decode(result)).toBe(configData);
|
|
49
|
+
});
|
|
50
|
+
it('should work with only schema passed in - uses default parser', async () => {
|
|
51
|
+
const configData = '{"valid": "json", "number": 123}';
|
|
52
|
+
const request = createMockRequest(configData);
|
|
53
|
+
const result = await configHandler(request, { configSchema: mockSchema });
|
|
54
|
+
expect(result).toEqual({ valid: 'json', number: 123 });
|
|
55
|
+
});
|
|
56
|
+
it('should work with both parser and schema', async () => {
|
|
57
|
+
const configData = '{"raw": "data", "needs": "processing"}';
|
|
58
|
+
const request = createMockRequest(configData);
|
|
59
|
+
const customParser = (config) => {
|
|
60
|
+
const parsed = JSON.parse(Buffer.from(config).toString());
|
|
61
|
+
return { ...parsed, processed: true };
|
|
62
|
+
};
|
|
63
|
+
const result = await configHandler(request, {
|
|
64
|
+
configParser: customParser,
|
|
65
|
+
configSchema: mockSchema,
|
|
66
|
+
});
|
|
67
|
+
expect(result).toEqual({
|
|
68
|
+
raw: 'data',
|
|
69
|
+
needs: 'processing',
|
|
70
|
+
processed: true,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
it('should throw error when schema validation fails', async () => {
|
|
74
|
+
const configData = 'invalid json';
|
|
75
|
+
const request = createMockRequest(configData);
|
|
76
|
+
expect(configHandler(request, { configSchema: mockSchema })).rejects.toThrow();
|
|
77
|
+
});
|
|
78
|
+
it('should handle empty JSON object', async () => {
|
|
79
|
+
const configData = '{}';
|
|
80
|
+
const request = createMockRequest(configData);
|
|
81
|
+
const result = await configHandler(request);
|
|
82
|
+
expect(result).toEqual({});
|
|
83
|
+
});
|
|
84
|
+
it('should handle complex nested JSON', async () => {
|
|
85
|
+
const configData = JSON.stringify({
|
|
86
|
+
nested: {
|
|
87
|
+
array: [1, 2, 3],
|
|
88
|
+
object: { key: 'value' },
|
|
89
|
+
},
|
|
90
|
+
boolean: true,
|
|
91
|
+
nullValue: null,
|
|
92
|
+
});
|
|
93
|
+
const request = createMockRequest(configData);
|
|
94
|
+
const result = await configHandler(request);
|
|
95
|
+
expect(result).toEqual({
|
|
96
|
+
nested: {
|
|
97
|
+
array: [1, 2, 3],
|
|
98
|
+
object: { key: 'value' },
|
|
99
|
+
},
|
|
100
|
+
boolean: true,
|
|
101
|
+
nullValue: null,
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
it('should work with custom parser that returns different type', async () => {
|
|
105
|
+
const configData = '{"count": 5}';
|
|
106
|
+
const request = createMockRequest(configData);
|
|
107
|
+
const customParser = (config) => {
|
|
108
|
+
const parsed = JSON.parse(Buffer.from(config).toString());
|
|
109
|
+
return parsed.count * 2; // Return number instead of object
|
|
110
|
+
};
|
|
111
|
+
const result = await configHandler(request, { configParser: customParser });
|
|
112
|
+
expect(result).toBe(10);
|
|
113
|
+
});
|
|
114
|
+
it('should work with Zod schema validation', async () => {
|
|
115
|
+
const configData = JSON.stringify({
|
|
116
|
+
schedule: '0 0 * * *',
|
|
117
|
+
apiUrl: 'https://api.example.com',
|
|
118
|
+
timeout: 5000,
|
|
119
|
+
retries: 3,
|
|
120
|
+
});
|
|
121
|
+
const request = createMockRequest(configData);
|
|
122
|
+
// Create Zod schema
|
|
123
|
+
const zodSchema = z.object({
|
|
124
|
+
schedule: z.string(),
|
|
125
|
+
apiUrl: z.string().url(),
|
|
126
|
+
timeout: z.number().min(1000).max(30000),
|
|
127
|
+
retries: z.number().min(0).max(10),
|
|
128
|
+
});
|
|
129
|
+
const result = await configHandler(request, { configSchema: zodSchema });
|
|
130
|
+
expect(result).toEqual({
|
|
131
|
+
schedule: '0 0 * * *',
|
|
132
|
+
apiUrl: 'https://api.example.com',
|
|
133
|
+
timeout: 5000,
|
|
134
|
+
retries: 3,
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
it('should throw error when Zod schema validation fails', async () => {
|
|
138
|
+
const configData = JSON.stringify({
|
|
139
|
+
schedule: 'invalid cron',
|
|
140
|
+
apiUrl: 'not-a-url', // Invalid URL
|
|
141
|
+
timeout: 100, // Too low
|
|
142
|
+
retries: 15, // Too high
|
|
143
|
+
});
|
|
144
|
+
const request = createMockRequest(configData);
|
|
145
|
+
const zodSchema = z.object({
|
|
146
|
+
schedule: z.string(),
|
|
147
|
+
apiUrl: z.string().url(),
|
|
148
|
+
timeout: z.number().min(1000).max(30000),
|
|
149
|
+
retries: z.number().min(0).max(10),
|
|
150
|
+
});
|
|
151
|
+
expect(configHandler(request, { configSchema: zodSchema })).rejects.toThrow();
|
|
152
|
+
});
|
|
153
|
+
it('should work with Zod schema and custom parser', async () => {
|
|
154
|
+
const configData = JSON.stringify({
|
|
155
|
+
rawData: 'base64encoded',
|
|
156
|
+
metadata: { version: '1.0', type: 'config' },
|
|
157
|
+
});
|
|
158
|
+
const request = createMockRequest(configData);
|
|
159
|
+
// Custom parser that processes the data
|
|
160
|
+
const customParser = (config) => {
|
|
161
|
+
const parsed = JSON.parse(Buffer.from(config).toString());
|
|
162
|
+
return {
|
|
163
|
+
schedule: '0 0 * * *', // Extract from rawData
|
|
164
|
+
apiUrl: 'https://api.example.com', // Extract from rawData
|
|
165
|
+
timeout: 5000,
|
|
166
|
+
retries: 3,
|
|
167
|
+
metadata: parsed.metadata,
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
// Zod schema for the processed data
|
|
171
|
+
const zodSchema = z.object({
|
|
172
|
+
schedule: z.string(),
|
|
173
|
+
apiUrl: z.string().url(),
|
|
174
|
+
timeout: z.number().min(1000).max(30000),
|
|
175
|
+
retries: z.number().min(0).max(10),
|
|
176
|
+
metadata: z.object({
|
|
177
|
+
version: z.string(),
|
|
178
|
+
type: z.string(),
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
const result = await configHandler(request, {
|
|
182
|
+
configParser: customParser,
|
|
183
|
+
configSchema: zodSchema,
|
|
184
|
+
});
|
|
185
|
+
expect(result).toEqual({
|
|
186
|
+
schedule: '0 0 * * *',
|
|
187
|
+
apiUrl: 'https://api.example.com',
|
|
188
|
+
timeout: 5000,
|
|
189
|
+
retries: 3,
|
|
190
|
+
metadata: { version: '1.0', type: 'config' },
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { ExecuteRequest } from '../../../generated/sdk/v1alpha/sdk_pb';
|
|
2
2
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
configParser?: (config: unknown) => unknown;
|
|
7
|
-
configSchema?: StandardSchemaV1;
|
|
3
|
+
export type ConfigHandlerParams<TConfig, TIntermediateConfig = TConfig> = {
|
|
4
|
+
configParser?: (config: Uint8Array) => TIntermediateConfig;
|
|
5
|
+
configSchema?: StandardSchemaV1<TIntermediateConfig, TConfig>;
|
|
8
6
|
};
|
|
9
|
-
export declare const configHandler: <TConfig>({ configParser, configSchema
|
|
7
|
+
export declare const configHandler: <TConfig, TIntermediateConfig = TConfig>(request: ExecuteRequest, { configParser, configSchema }?: ConfigHandlerParams<TConfig, TIntermediateConfig>) => Promise<TConfig>;
|
|
@@ -1,26 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export const getConfigFromExecuteRequest = (executeRequest) => {
|
|
3
|
-
const config = executeRequest.config;
|
|
4
|
-
const configString = Buffer.from(config).toString();
|
|
5
|
-
try {
|
|
6
|
-
return JSON.parse(configString);
|
|
7
|
-
}
|
|
8
|
-
catch (e) {
|
|
9
|
-
if (typeof configString === 'string') {
|
|
10
|
-
return configString;
|
|
11
|
-
}
|
|
12
|
-
if (e instanceof Error) {
|
|
13
|
-
console.error(e.message);
|
|
14
|
-
console.error(e.stack);
|
|
15
|
-
}
|
|
16
|
-
throw e;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
export const getConfig = () => {
|
|
20
|
-
const executeRequest = getRequest();
|
|
21
|
-
return getConfigFromExecuteRequest(executeRequest);
|
|
22
|
-
};
|
|
23
|
-
const standardValidate = async (schema, input) => {
|
|
1
|
+
async function standardValidate(schema, input) {
|
|
24
2
|
let result = schema['~standard'].validate(input);
|
|
25
3
|
if (result instanceof Promise)
|
|
26
4
|
result = await result;
|
|
@@ -32,15 +10,13 @@ const standardValidate = async (schema, input) => {
|
|
|
32
10
|
throw new Error(JSON.stringify(result.issues, null, 2));
|
|
33
11
|
}
|
|
34
12
|
return result.value;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
return parsedConfig;
|
|
13
|
+
}
|
|
14
|
+
const defaultJsonParser = (config) => JSON.parse(Buffer.from(config).toString());
|
|
15
|
+
export const configHandler = async (request, { configParser, configSchema } = {}) => {
|
|
16
|
+
const config = request.config;
|
|
17
|
+
const parser = configParser || defaultJsonParser;
|
|
18
|
+
const intermediateConfig = parser(config);
|
|
19
|
+
return configSchema
|
|
20
|
+
? standardValidate(configSchema, intermediateConfig)
|
|
21
|
+
: intermediateConfig;
|
|
46
22
|
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
export * from './error-boundary';
|
|
2
|
-
export { withErrorBoundary } from './error-boundary';
|
|
3
1
|
export * from './hex-utils';
|
|
4
|
-
export * from './send-response-value';
|
|
5
2
|
export * from './values/consensus_aggregators';
|
|
6
3
|
export * from './values/serializer_types';
|
|
7
4
|
export * from './values/value';
|