@kapeta/local-cluster-service 0.46.0 → 0.47.1
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/CHANGELOG.md +14 -0
- package/dist/cjs/src/storm/codegen.js +16 -15
- package/dist/cjs/src/storm/event-parser.d.ts +4 -4
- package/dist/cjs/src/storm/event-parser.js +64 -18
- package/dist/cjs/src/storm/events.d.ts +9 -1
- package/dist/cjs/src/storm/events.js +0 -4
- package/dist/cjs/src/storm/routes.js +20 -7
- package/dist/cjs/src/storm/stormClient.js +3 -0
- package/dist/cjs/src/storm/stream.d.ts +1 -0
- package/dist/cjs/test/storm/event-parser.test.d.ts +5 -0
- package/dist/cjs/test/storm/event-parser.test.js +161 -0
- package/dist/esm/src/storm/codegen.js +16 -15
- package/dist/esm/src/storm/event-parser.d.ts +4 -4
- package/dist/esm/src/storm/event-parser.js +64 -18
- package/dist/esm/src/storm/events.d.ts +9 -1
- package/dist/esm/src/storm/events.js +0 -4
- package/dist/esm/src/storm/routes.js +20 -7
- package/dist/esm/src/storm/stormClient.js +3 -0
- package/dist/esm/src/storm/stream.d.ts +1 -0
- package/dist/esm/test/storm/event-parser.test.d.ts +5 -0
- package/dist/esm/test/storm/event-parser.test.js +161 -0
- package/package.json +3 -1
- package/src/storm/codegen.ts +23 -14
- package/src/storm/event-parser.ts +107 -28
- package/src/storm/events.ts +11 -1
- package/src/storm/routes.ts +33 -19
- package/src/storm/stormClient.ts +4 -0
- package/src/storm/stream.ts +1 -0
- package/test/storm/event-parser.test.ts +182 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.47.1](https://github.com/kapetacom/local-cluster-service/compare/v0.47.0...v0.47.1) (2024-05-30)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Make UUIDs deterministic to allow cleaner updates ([#150](https://github.com/kapetacom/local-cluster-service/issues/150)) ([0e3bbec](https://github.com/kapetacom/local-cluster-service/commit/0e3bbecb00d52e2cc0f5e0d8adb9953f40e6e253))
|
7
|
+
|
8
|
+
# [0.47.0](https://github.com/kapetacom/local-cluster-service/compare/v0.46.0...v0.47.0) (2024-05-29)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* Added tests for event parsing and layouting of nodes ([#149](https://github.com/kapetacom/local-cluster-service/issues/149)) ([d36afa9](https://github.com/kapetacom/local-cluster-service/commit/d36afa905d0c66c4562b4eccf07c067efe6b3c9c))
|
14
|
+
|
1
15
|
# [0.46.0](https://github.com/kapetacom/local-cluster-service/compare/v0.45.0...v0.46.0) (2024-05-28)
|
2
16
|
|
3
17
|
|
@@ -28,15 +28,15 @@ class StormCodegen {
|
|
28
28
|
getStream() {
|
29
29
|
return this.out;
|
30
30
|
}
|
31
|
-
handleTemplateFileOutput(blockUri, template, data) {
|
31
|
+
handleTemplateFileOutput(blockUri, aiName, template, data) {
|
32
32
|
switch (data.type) {
|
33
33
|
case 'FILE':
|
34
34
|
template.filename = data.payload.filename;
|
35
35
|
template.content = data.payload.content;
|
36
|
-
return this.handleFileOutput(blockUri, data);
|
36
|
+
return this.handleFileOutput(blockUri, aiName, data);
|
37
37
|
}
|
38
38
|
}
|
39
|
-
handleUiOutput(blockUri, data) {
|
39
|
+
handleUiOutput(blockUri, aiName, data) {
|
40
40
|
switch (data.type) {
|
41
41
|
case 'SCREEN':
|
42
42
|
this.out.emit('data', {
|
@@ -45,17 +45,17 @@ class StormCodegen {
|
|
45
45
|
created: Date.now(),
|
46
46
|
payload: {
|
47
47
|
...data.payload,
|
48
|
-
blockName:
|
48
|
+
blockName: aiName,
|
49
49
|
},
|
50
50
|
});
|
51
51
|
case 'FILE':
|
52
|
-
return this.handleFileOutput(blockUri, data);
|
52
|
+
return this.handleFileOutput(blockUri, aiName, data);
|
53
53
|
}
|
54
54
|
}
|
55
|
-
handleFileOutput(blockUri, data) {
|
55
|
+
handleFileOutput(blockUri, aiName, data) {
|
56
56
|
switch (data.type) {
|
57
57
|
case 'FILE':
|
58
|
-
this.emitFile(blockUri, data.payload.filename, data.payload.content, data.reason);
|
58
|
+
this.emitFile(blockUri, aiName, data.payload.filename, data.payload.content, data.reason);
|
59
59
|
return {
|
60
60
|
type: 'FILE',
|
61
61
|
created: Date.now(),
|
@@ -77,7 +77,7 @@ class StormCodegen {
|
|
77
77
|
}
|
78
78
|
const allFiles = this.toStormFiles(generatedResult);
|
79
79
|
// Send all the non-ai files to the stream
|
80
|
-
this.emitFiles(block.uri, allFiles);
|
80
|
+
this.emitFiles(block.uri, block.aiName, allFiles);
|
81
81
|
const relevantFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && file.type !== codegen_1.AIFileTypes.WEB_SCREEN);
|
82
82
|
const uiTemplates = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.WEB_SCREEN);
|
83
83
|
if (uiTemplates.length > 0) {
|
@@ -89,7 +89,7 @@ class StormCodegen {
|
|
89
89
|
prompt: this.userPrompt,
|
90
90
|
});
|
91
91
|
uiStream.on('data', (evt) => {
|
92
|
-
this.handleUiOutput(block.uri, evt);
|
92
|
+
this.handleUiOutput(block.uri, block.aiName, evt);
|
93
93
|
});
|
94
94
|
await uiStream.waitForDone();
|
95
95
|
}
|
@@ -98,13 +98,13 @@ class StormCodegen {
|
|
98
98
|
// Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
|
99
99
|
const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
|
100
100
|
if (serviceFiles.length > 0) {
|
101
|
-
await this.processTemplates(block.uri, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
101
|
+
await this.processTemplates(block.uri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
|
102
102
|
}
|
103
103
|
}
|
104
104
|
/**
|
105
105
|
* Emits the text-based files to the stream
|
106
106
|
*/
|
107
|
-
emitFiles(uri, files) {
|
107
|
+
emitFiles(uri, aiName, files) {
|
108
108
|
files.forEach((file) => {
|
109
109
|
if (!file.content || typeof file.content !== 'string') {
|
110
110
|
return;
|
@@ -119,10 +119,10 @@ class StormCodegen {
|
|
119
119
|
// They will need to be implemented by the AI
|
120
120
|
return;
|
121
121
|
}
|
122
|
-
this.emitFile(uri, file.filename, file.content);
|
122
|
+
this.emitFile(uri, aiName, file.filename, file.content);
|
123
123
|
});
|
124
124
|
}
|
125
|
-
emitFile(uri, filename, content, reason = 'File generated') {
|
125
|
+
emitFile(uri, blockName, filename, content, reason = 'File generated') {
|
126
126
|
this.out.emit('data', {
|
127
127
|
type: 'FILE',
|
128
128
|
reason,
|
@@ -130,6 +130,7 @@ class StormCodegen {
|
|
130
130
|
payload: {
|
131
131
|
filename: filename,
|
132
132
|
content: content,
|
133
|
+
blockName,
|
133
134
|
blockRef: uri.toNormalizedString(),
|
134
135
|
},
|
135
136
|
});
|
@@ -137,7 +138,7 @@ class StormCodegen {
|
|
137
138
|
/**
|
138
139
|
* Sends the template to the AI and processes the response
|
139
140
|
*/
|
140
|
-
async processTemplates(blockUri, generator, templates, contextFiles) {
|
141
|
+
async processTemplates(blockUri, aiName, generator, templates, contextFiles) {
|
141
142
|
const promises = templates.map(async (templateFile) => {
|
142
143
|
const stream = await generator({
|
143
144
|
context: contextFiles,
|
@@ -146,7 +147,7 @@ class StormCodegen {
|
|
146
147
|
});
|
147
148
|
const files = [];
|
148
149
|
stream.on('data', (evt) => {
|
149
|
-
const file = this.handleTemplateFileOutput(blockUri, templateFile, evt);
|
150
|
+
const file = this.handleTemplateFileOutput(blockUri, aiName, templateFile, evt);
|
150
151
|
if (file) {
|
151
152
|
files.push(file);
|
152
153
|
}
|
@@ -10,7 +10,7 @@ export interface BlockDefinitionInfo {
|
|
10
10
|
content: BlockDefinition;
|
11
11
|
aiName: string;
|
12
12
|
}
|
13
|
-
export interface
|
13
|
+
export interface StormDefinitions {
|
14
14
|
plan: Plan;
|
15
15
|
blocks: BlockDefinitionInfo[];
|
16
16
|
}
|
@@ -51,12 +51,11 @@ export declare class StormEventParser {
|
|
51
51
|
private options;
|
52
52
|
constructor(options: StormOptions);
|
53
53
|
private reset;
|
54
|
-
addEvent(evt: StormEvent):
|
54
|
+
addEvent(handle: string, evt: StormEvent): StormDefinitions;
|
55
55
|
getEvents(): StormEvent[];
|
56
56
|
isValid(): boolean;
|
57
57
|
getError(): string;
|
58
|
-
|
59
|
-
toResult(handle: string): ParsedResult;
|
58
|
+
toResult(handle: string): StormDefinitions;
|
60
59
|
private toSafeName;
|
61
60
|
private toRef;
|
62
61
|
toBlockDefinitions(handle: string): {
|
@@ -64,6 +63,7 @@ export declare class StormEventParser {
|
|
64
63
|
};
|
65
64
|
private toResourceKind;
|
66
65
|
private toBlockKind;
|
66
|
+
private toConnectionMapping;
|
67
67
|
private toPortType;
|
68
68
|
private toBlockTarget;
|
69
69
|
private toBlockTargetKind;
|
@@ -3,14 +3,11 @@
|
|
3
3
|
* Copyright 2023 Kapeta Inc.
|
4
4
|
* SPDX-License-Identifier: BUSL-1.1
|
5
5
|
*/
|
6
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
7
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
8
|
-
};
|
9
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
10
7
|
exports.StormEventParser = exports.resolveOptions = void 0;
|
11
8
|
const nodejs_utils_1 = require("@kapeta/nodejs-utils");
|
12
9
|
const kaplang_core_1 = require("@kapeta/kaplang-core");
|
13
|
-
const
|
10
|
+
const uuid_1 = require("uuid");
|
14
11
|
const definitionsManager_1 = require("../definitionsManager");
|
15
12
|
async function resolveOptions() {
|
16
13
|
// Predefined types for now - TODO: Allow user to select / change
|
@@ -105,8 +102,9 @@ class StormEventParser {
|
|
105
102
|
this.blocks = {};
|
106
103
|
this.connections = [];
|
107
104
|
}
|
108
|
-
addEvent(evt) {
|
105
|
+
addEvent(handle, evt) {
|
109
106
|
this.events.push(evt);
|
107
|
+
console.log('evt', evt);
|
110
108
|
switch (evt.type) {
|
111
109
|
case 'CREATE_PLAN_PROPERTIES':
|
112
110
|
this.planName = evt.payload.name;
|
@@ -144,26 +142,27 @@ class StormEventParser {
|
|
144
142
|
case 'FILE':
|
145
143
|
break;
|
146
144
|
}
|
145
|
+
return this.toResult(handle);
|
147
146
|
}
|
148
147
|
getEvents() {
|
149
148
|
return this.events;
|
150
149
|
}
|
151
150
|
isValid() {
|
151
|
+
if (!this.planName) {
|
152
|
+
return false;
|
153
|
+
}
|
152
154
|
return !this.failed;
|
153
155
|
}
|
154
156
|
getError() {
|
155
157
|
return this.error;
|
156
158
|
}
|
157
|
-
applyLayoutToBlocks(result) {
|
158
|
-
return result;
|
159
|
-
}
|
160
159
|
toResult(handle) {
|
161
|
-
const planRef = this.toRef(handle, this.planName);
|
160
|
+
const planRef = this.toRef(handle, this.planName ?? 'undefined');
|
162
161
|
const blockDefinitions = this.toBlockDefinitions(handle);
|
163
162
|
const refIdMap = {};
|
164
|
-
const screens = {};
|
165
163
|
const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
|
166
|
-
|
164
|
+
// Create a deterministic uuid
|
165
|
+
const id = (0, uuid_1.v5)(ref, uuid_1.v5.URL);
|
167
166
|
refIdMap[ref] = id;
|
168
167
|
return {
|
169
168
|
id,
|
@@ -174,7 +173,7 @@ class StormEventParser {
|
|
174
173
|
dimensions: {
|
175
174
|
left: 0,
|
176
175
|
top: 0,
|
177
|
-
width:
|
176
|
+
width: 150,
|
178
177
|
height: 200,
|
179
178
|
},
|
180
179
|
};
|
@@ -211,6 +210,28 @@ class StormEventParser {
|
|
211
210
|
console.warn('Client resource not found: %s on %s', apiConnection.toResource, clientConsumerRef.toNormalizedString(), apiConnection);
|
212
211
|
return;
|
213
212
|
}
|
213
|
+
if (apiProviderBlock.content.spec.entities?.source?.value) {
|
214
|
+
if (!clientConsumerBlock.content.spec.entities) {
|
215
|
+
clientConsumerBlock.content.spec.entities = {
|
216
|
+
types: [],
|
217
|
+
source: {
|
218
|
+
type: kaplang_core_1.KAPLANG_ID,
|
219
|
+
version: kaplang_core_1.KAPLANG_VERSION,
|
220
|
+
value: '',
|
221
|
+
},
|
222
|
+
};
|
223
|
+
}
|
224
|
+
const clientTypes = kaplang_core_1.DSLDataTypeParser.parse(clientConsumerBlock.content.spec.entities.source.value);
|
225
|
+
const apiTypes = kaplang_core_1.DSLDataTypeParser.parse(apiProviderBlock.content.spec.entities?.source?.value);
|
226
|
+
apiTypes.forEach((apiType) => {
|
227
|
+
if (clientTypes.some((clientType) => clientType.name === apiType.name)) {
|
228
|
+
// Already exists
|
229
|
+
return;
|
230
|
+
}
|
231
|
+
clientTypes.push(apiType);
|
232
|
+
});
|
233
|
+
clientConsumerBlock.content.spec.entities.source.value = kaplang_core_1.KaplangWriter.write(clientTypes);
|
234
|
+
}
|
214
235
|
clientResource.spec.methods = apiResource.spec.methods;
|
215
236
|
clientResource.spec.source = apiResource.spec.source;
|
216
237
|
});
|
@@ -229,9 +250,7 @@ class StormEventParser {
|
|
229
250
|
blockId: refIdMap[fromRef.toNormalizedString()],
|
230
251
|
resourceName: connection.fromResource,
|
231
252
|
},
|
232
|
-
mapping:
|
233
|
-
//TODO: Add mapping
|
234
|
-
},
|
253
|
+
mapping: this.toConnectionMapping(handle, connection, blockDefinitions),
|
235
254
|
};
|
236
255
|
});
|
237
256
|
const plan = {
|
@@ -246,10 +265,10 @@ class StormEventParser {
|
|
246
265
|
connections,
|
247
266
|
},
|
248
267
|
};
|
249
|
-
return
|
268
|
+
return {
|
250
269
|
plan,
|
251
270
|
blocks: Object.values(blockDefinitions),
|
252
|
-
}
|
271
|
+
};
|
253
272
|
}
|
254
273
|
toSafeName(name) {
|
255
274
|
return name.toLowerCase().replace(/[^0-9a-z-]/gi, '');
|
@@ -377,7 +396,7 @@ class StormEventParser {
|
|
377
396
|
},
|
378
397
|
},
|
379
398
|
};
|
380
|
-
blockSpec.
|
399
|
+
blockSpec.consumers.push(dbResource);
|
381
400
|
break;
|
382
401
|
case 'JWTCONSUMER':
|
383
402
|
case 'WEBFRAGMENT':
|
@@ -460,6 +479,33 @@ class StormEventParser {
|
|
460
479
|
}
|
461
480
|
return '';
|
462
481
|
}
|
482
|
+
toConnectionMapping(handle, connection, blockDefinitions) {
|
483
|
+
if (connection.fromResourceType !== 'API') {
|
484
|
+
return;
|
485
|
+
}
|
486
|
+
const fromRef = this.toRef(handle, connection.fromComponent);
|
487
|
+
const apiProviderBlock = blockDefinitions[fromRef.toNormalizedString()];
|
488
|
+
if (!apiProviderBlock) {
|
489
|
+
console.warn('Provider block not found: %s', connection.fromComponent, connection);
|
490
|
+
return;
|
491
|
+
}
|
492
|
+
const apiResource = apiProviderBlock.content.spec.providers?.find((p) => p.kind === this.options.apiKind && p.metadata.name === connection.fromResource);
|
493
|
+
if (!apiResource) {
|
494
|
+
console.warn('API resource not found: %s on %s', connection.fromResource, fromRef.toNormalizedString(), connection);
|
495
|
+
return;
|
496
|
+
}
|
497
|
+
const apiMethods = kaplang_core_1.DSLConverters.toSchemaMethods(kaplang_core_1.DSLAPIParser.parse(apiResource.spec?.source?.value ?? '', {
|
498
|
+
ignoreSemantics: true,
|
499
|
+
}));
|
500
|
+
const mapping = {};
|
501
|
+
Object.entries(apiMethods).forEach(([methodId, method]) => {
|
502
|
+
mapping[methodId] = {
|
503
|
+
targetId: methodId,
|
504
|
+
type: 'EXACT',
|
505
|
+
};
|
506
|
+
});
|
507
|
+
return mapping;
|
508
|
+
}
|
463
509
|
toPortType(type) {
|
464
510
|
switch (type) {
|
465
511
|
case 'API':
|
@@ -2,6 +2,7 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
+
import { StormDefinitions } from './event-parser';
|
5
6
|
export type StormResourceType = 'API' | 'DATABASE' | 'CLIENT' | 'JWTPROVIDER' | 'JWTCONSUMER' | 'WEBFRAGMENT' | 'WEBPAGE' | 'SMTPCLIENT' | 'EXTERNAL_API' | 'SUBSCRIBER' | 'PUBLISHER' | 'QUEUE' | 'EXCHANGE';
|
6
7
|
export type StormBlockType = 'BACKEND' | 'FRONTEND' | 'GATEWAY' | 'MQ' | 'CLI' | 'DESKTOP';
|
7
8
|
export interface StormBlockInfo {
|
@@ -116,6 +117,7 @@ export interface StormEventFile {
|
|
116
117
|
payload: {
|
117
118
|
filename: string;
|
118
119
|
content: string;
|
120
|
+
blockName: string;
|
119
121
|
blockRef: string;
|
120
122
|
};
|
121
123
|
}
|
@@ -123,4 +125,10 @@ export interface StormEventDone {
|
|
123
125
|
type: 'DONE';
|
124
126
|
created: number;
|
125
127
|
}
|
126
|
-
export
|
128
|
+
export interface StormEventDefinitionChange {
|
129
|
+
type: 'DEFINITION_CHANGE';
|
130
|
+
reason: string;
|
131
|
+
created: number;
|
132
|
+
payload: StormDefinitions;
|
133
|
+
}
|
134
|
+
export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
|
@@ -25,12 +25,13 @@ router.post('/:handle/all', async (req, res) => {
|
|
25
25
|
const metaStream = await stormClient_1.stormClient.createMetadata(aiRequest.prompt, aiRequest.history);
|
26
26
|
res.set('Content-Type', 'application/x-ndjson');
|
27
27
|
metaStream.on('data', (data) => {
|
28
|
-
eventParser.addEvent(data);
|
28
|
+
const result = eventParser.addEvent(req.params.handle, data);
|
29
|
+
sendDefinitions(res, result);
|
29
30
|
});
|
30
31
|
await streamStormPartialResponse(metaStream, res);
|
31
32
|
if (!eventParser.isValid()) {
|
32
33
|
// We can't continue if the meta stream is invalid
|
33
|
-
res
|
34
|
+
sendEvent(res, {
|
34
35
|
type: 'ERROR_INTERNAL',
|
35
36
|
payload: { error: eventParser.getError() },
|
36
37
|
reason: 'Failed to generate system',
|
@@ -40,6 +41,7 @@ router.post('/:handle/all', async (req, res) => {
|
|
40
41
|
return;
|
41
42
|
}
|
42
43
|
const result = eventParser.toResult(handle);
|
44
|
+
sendDefinitions(res, result);
|
43
45
|
const stormCodegen = new codegen_1.StormCodegen(aiRequest.prompt, result.blocks, eventParser.getEvents());
|
44
46
|
const codegenStream = streamStormPartialResponse(stormCodegen.getStream(), res);
|
45
47
|
await stormCodegen.process();
|
@@ -50,22 +52,30 @@ router.post('/:handle/all', async (req, res) => {
|
|
50
52
|
sendError(err, res);
|
51
53
|
}
|
52
54
|
});
|
55
|
+
function sendDefinitions(res, result) {
|
56
|
+
sendEvent(res, {
|
57
|
+
type: 'DEFINITION_CHANGE',
|
58
|
+
payload: result,
|
59
|
+
reason: 'Updates to definition',
|
60
|
+
created: Date.now(),
|
61
|
+
});
|
62
|
+
}
|
53
63
|
function sendDone(res) {
|
54
|
-
res
|
64
|
+
sendEvent(res, {
|
55
65
|
type: 'DONE',
|
56
66
|
created: Date.now(),
|
57
|
-
})
|
67
|
+
});
|
58
68
|
res.end();
|
59
69
|
}
|
60
70
|
function sendError(err, res) {
|
61
71
|
console.error('Failed to send prompt', err);
|
62
72
|
if (res.headersSent) {
|
63
|
-
res
|
73
|
+
sendEvent(res, {
|
64
74
|
type: 'ERROR_INTERNAL',
|
65
75
|
created: Date.now(),
|
66
76
|
payload: { error: err.message },
|
67
77
|
reason: 'Failed while sending prompt',
|
68
|
-
})
|
78
|
+
});
|
69
79
|
}
|
70
80
|
else {
|
71
81
|
res.status(400).send({ error: err.message });
|
@@ -74,7 +84,7 @@ function sendError(err, res) {
|
|
74
84
|
function streamStormPartialResponse(result, res) {
|
75
85
|
return new Promise((resolve, reject) => {
|
76
86
|
result.on('data', (data) => {
|
77
|
-
res
|
87
|
+
sendEvent(res, data);
|
78
88
|
});
|
79
89
|
result.on('error', (err) => {
|
80
90
|
reject(err);
|
@@ -84,4 +94,7 @@ function streamStormPartialResponse(result, res) {
|
|
84
94
|
});
|
85
95
|
});
|
86
96
|
}
|
97
|
+
function sendEvent(res, evt) {
|
98
|
+
res.write(JSON.stringify(evt) + '\n');
|
99
|
+
}
|
87
100
|
exports.default = router;
|
@@ -28,6 +28,9 @@ class StormClient {
|
|
28
28
|
if (api.hasToken()) {
|
29
29
|
//headers['Authorization'] = `Bearer ${api.getAccessToken()}`; //TODO: Enable authentication
|
30
30
|
}
|
31
|
+
if (body.conversationId) {
|
32
|
+
headers['conversationId'] = body.conversationId;
|
33
|
+
}
|
31
34
|
return {
|
32
35
|
url,
|
33
36
|
method: method,
|
@@ -0,0 +1,161 @@
|
|
1
|
+
"use strict";
|
2
|
+
/**
|
3
|
+
* Copyright 2023 Kapeta Inc.
|
4
|
+
* SPDX-License-Identifier: BUSL-1.1
|
5
|
+
*/
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
7
|
+
const event_parser_1 = require("../../src/storm/event-parser");
|
8
|
+
const parserOptions = {
|
9
|
+
serviceKind: 'kapeta/block-service:local',
|
10
|
+
serviceLanguage: 'kapeta/language-target-nodejs-ts:local',
|
11
|
+
frontendKind: 'kapeta/block-type-frontend:local',
|
12
|
+
frontendLanguage: 'kapeta/language-target-react-ts:local',
|
13
|
+
cliKind: 'kapeta/block-type-cli:local',
|
14
|
+
cliLanguage: 'kapeta/language-target-nodejs-ts:local',
|
15
|
+
desktopKind: 'kapeta/block-type-desktop:local',
|
16
|
+
desktopLanguage: 'kapeta/language-target-electron-ts:local',
|
17
|
+
gatewayKind: 'kapeta/block-type-gateway:local',
|
18
|
+
mqKind: 'kapeta/block-type-mq:local',
|
19
|
+
exchangeKind: 'kapeta/resource-type-exchange:local',
|
20
|
+
queueKind: 'kapeta/resource-type-queue:local',
|
21
|
+
publisherKind: 'kapeta/resource-type-publisher:local',
|
22
|
+
subscriberKind: 'kapeta/resource-type-subscriber:local',
|
23
|
+
databaseKind: 'kapeta/block-type-database:local',
|
24
|
+
apiKind: 'kapeta/block-type-api:local',
|
25
|
+
clientKind: 'kapeta/block-type-client:local',
|
26
|
+
webPageKind: 'kapeta/block-type-web-page:local',
|
27
|
+
webFragmentKind: 'kapeta/block-type-web-fragment:local',
|
28
|
+
jwtProviderKind: 'kapeta/resource-type-jwt-provider:local',
|
29
|
+
jwtConsumerKind: 'kapeta/resource-type-jwt-consumer:local',
|
30
|
+
smtpKind: 'kapeta/resource-type-smtp:local',
|
31
|
+
externalApiKind: 'kapeta/resource-type-external-api:local',
|
32
|
+
};
|
33
|
+
const events = [
|
34
|
+
{
|
35
|
+
type: 'CREATE_PLAN_PROPERTIES',
|
36
|
+
created: Date.now(),
|
37
|
+
reason: 'create plan properties',
|
38
|
+
payload: {
|
39
|
+
name: 'my-plan',
|
40
|
+
description: 'my plan description',
|
41
|
+
},
|
42
|
+
},
|
43
|
+
{
|
44
|
+
type: 'CREATE_BLOCK',
|
45
|
+
reason: 'create backend',
|
46
|
+
created: Date.now(),
|
47
|
+
payload: {
|
48
|
+
name: 'service',
|
49
|
+
description: 'A service block',
|
50
|
+
type: 'BACKEND',
|
51
|
+
resources: [
|
52
|
+
{
|
53
|
+
name: 'entities',
|
54
|
+
type: 'DATABASE',
|
55
|
+
description: 'A database resource',
|
56
|
+
},
|
57
|
+
{
|
58
|
+
type: 'API',
|
59
|
+
name: 'entities',
|
60
|
+
description: 'An API resource',
|
61
|
+
},
|
62
|
+
],
|
63
|
+
},
|
64
|
+
},
|
65
|
+
{
|
66
|
+
type: 'CREATE_BLOCK',
|
67
|
+
reason: 'create frontend',
|
68
|
+
created: Date.now(),
|
69
|
+
payload: {
|
70
|
+
name: 'ui',
|
71
|
+
description: 'A frontend block',
|
72
|
+
type: 'FRONTEND',
|
73
|
+
resources: [
|
74
|
+
{
|
75
|
+
name: 'web',
|
76
|
+
type: 'WEBPAGE',
|
77
|
+
description: 'A web page',
|
78
|
+
},
|
79
|
+
{
|
80
|
+
type: 'CLIENT',
|
81
|
+
name: 'entities',
|
82
|
+
description: 'Client for backend',
|
83
|
+
},
|
84
|
+
],
|
85
|
+
},
|
86
|
+
},
|
87
|
+
{
|
88
|
+
type: 'CREATE_CONNECTION',
|
89
|
+
created: Date.now(),
|
90
|
+
reason: 'connect service to ui',
|
91
|
+
payload: {
|
92
|
+
fromComponent: 'service',
|
93
|
+
fromResource: 'entities',
|
94
|
+
fromResourceType: 'API',
|
95
|
+
toComponent: 'ui',
|
96
|
+
toResource: 'entities',
|
97
|
+
toResourceType: 'CLIENT',
|
98
|
+
},
|
99
|
+
},
|
100
|
+
{
|
101
|
+
type: 'CREATE_API',
|
102
|
+
reason: 'create api',
|
103
|
+
created: Date.now(),
|
104
|
+
payload: {
|
105
|
+
blockName: 'service',
|
106
|
+
content: `controller Entities('/entities') {
|
107
|
+
@GET('/')
|
108
|
+
list(): string[]
|
109
|
+
}`,
|
110
|
+
},
|
111
|
+
},
|
112
|
+
{
|
113
|
+
type: 'CREATE_MODEL',
|
114
|
+
created: Date.now(),
|
115
|
+
reason: 'create model',
|
116
|
+
payload: {
|
117
|
+
blockName: 'service',
|
118
|
+
content: `type Entity {
|
119
|
+
@Id
|
120
|
+
id: string
|
121
|
+
|
122
|
+
name: string
|
123
|
+
}`,
|
124
|
+
},
|
125
|
+
},
|
126
|
+
];
|
127
|
+
describe('event-parser', () => {
|
128
|
+
it('it can parse events into a plan and blocks with proper layout', () => {
|
129
|
+
const parser = new event_parser_1.StormEventParser(parserOptions);
|
130
|
+
events.forEach((event) => parser.addEvent('kapeta', event));
|
131
|
+
const result = parser.toResult('kapeta');
|
132
|
+
expect(result.plan.metadata.name).toBe('kapeta/my-plan');
|
133
|
+
expect(result.plan.metadata.description).toBe('my plan description');
|
134
|
+
expect(result.blocks.length).toBe(2);
|
135
|
+
expect(result.blocks[0].content.metadata.name).toBe('kapeta/service');
|
136
|
+
expect(result.blocks[1].content.metadata.name).toBe('kapeta/ui');
|
137
|
+
const dbResource = result.blocks[0].content.spec.consumers?.[0];
|
138
|
+
const apiResource = result.blocks[0].content.spec.providers?.[0];
|
139
|
+
const clientResource = result.blocks[1].content.spec.consumers?.[0];
|
140
|
+
const pageResource = result.blocks[1].content.spec.providers?.[0];
|
141
|
+
expect(apiResource).toBeDefined();
|
142
|
+
expect(clientResource).toBeDefined();
|
143
|
+
expect(dbResource).toBeDefined();
|
144
|
+
expect(pageResource).toBeDefined();
|
145
|
+
expect(apiResource?.kind).toBe(parserOptions.apiKind);
|
146
|
+
expect(clientResource?.kind).toBe(parserOptions.clientKind);
|
147
|
+
expect(dbResource?.kind).toBe(parserOptions.databaseKind);
|
148
|
+
expect(pageResource?.kind).toBe(parserOptions.webPageKind);
|
149
|
+
expect(apiResource?.spec).toEqual(clientResource?.spec);
|
150
|
+
expect(dbResource?.spec.source.value).toContain('type Entity');
|
151
|
+
const serviceBlockInstance = result.plan.spec.blocks[0];
|
152
|
+
expect(serviceBlockInstance.name).toBe('service');
|
153
|
+
const uiBlockInstance = result.plan.spec.blocks[1];
|
154
|
+
expect(uiBlockInstance.name).toBe('ui');
|
155
|
+
expect(result.plan.spec.connections.length).toBe(1);
|
156
|
+
expect(result.plan.spec.connections[0].consumer.blockId).toBe(uiBlockInstance.id);
|
157
|
+
expect(result.plan.spec.connections[0].consumer.resourceName).toBe(clientResource?.metadata.name);
|
158
|
+
expect(result.plan.spec.connections[0].provider.blockId).toBe(serviceBlockInstance.id);
|
159
|
+
expect(result.plan.spec.connections[0].provider.resourceName).toBe(apiResource?.metadata.name);
|
160
|
+
});
|
161
|
+
});
|