@kapeta/local-cluster-service 0.47.0 → 0.47.2
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 +1 -1
- package/dist/cjs/src/storm/event-parser.js +83 -55
- package/dist/cjs/src/storm/events.d.ts +2 -1
- 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.js +0 -8
- package/dist/esm/src/storm/codegen.js +16 -15
- package/dist/esm/src/storm/event-parser.d.ts +1 -1
- package/dist/esm/src/storm/event-parser.js +83 -55
- package/dist/esm/src/storm/events.d.ts +2 -1
- 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.js +0 -8
- package/package.json +4 -4
- package/src/storm/codegen.ts +23 -14
- package/src/storm/event-parser.ts +126 -65
- package/src/storm/events.ts +2 -1
- package/src/storm/routes.ts +12 -13
- package/src/storm/stormClient.ts +4 -0
- package/src/storm/stream.ts +1 -0
- package/test/storm/event-parser.test.ts +0 -8
@@ -14,11 +14,19 @@ import {
|
|
14
14
|
SourceCode,
|
15
15
|
} from '@kapeta/schemas';
|
16
16
|
import { KapetaURI, normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
|
17
|
-
import {
|
18
|
-
|
17
|
+
import {
|
18
|
+
DSLAPIParser,
|
19
|
+
DSLController,
|
20
|
+
DSLConverters,
|
21
|
+
DSLDataTypeParser,
|
22
|
+
DSLMethod,
|
23
|
+
DSLParser,
|
24
|
+
KAPLANG_ID,
|
25
|
+
KAPLANG_VERSION,
|
26
|
+
KaplangWriter,
|
27
|
+
} from '@kapeta/kaplang-core';
|
28
|
+
import { v5 as uuid } from 'uuid';
|
19
29
|
import { definitionsManager } from '../definitionsManager';
|
20
|
-
import createGraph from 'ngraph.graph';
|
21
|
-
import createLayout from 'ngraph.forcelayout';
|
22
30
|
|
23
31
|
export interface BlockDefinitionInfo {
|
24
32
|
uri: KapetaURI;
|
@@ -57,6 +65,28 @@ export interface StormOptions {
|
|
57
65
|
gatewayKind: string;
|
58
66
|
}
|
59
67
|
|
68
|
+
function prettifyKaplang(source: string) {
|
69
|
+
if (!source || !source.trim()) {
|
70
|
+
return '';
|
71
|
+
}
|
72
|
+
|
73
|
+
try {
|
74
|
+
const ast = DSLParser.parse(source, {
|
75
|
+
ignoreSemantics: true,
|
76
|
+
types: true,
|
77
|
+
methods: true,
|
78
|
+
rest: true,
|
79
|
+
extends: true,
|
80
|
+
generics: true,
|
81
|
+
});
|
82
|
+
|
83
|
+
return KaplangWriter.write(ast.entities ?? []);
|
84
|
+
} catch (e) {
|
85
|
+
console.warn('Failed to prettify source:\n%s', source);
|
86
|
+
return source;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
60
90
|
export async function resolveOptions(): Promise<StormOptions> {
|
61
91
|
// Predefined types for now - TODO: Allow user to select / change
|
62
92
|
|
@@ -170,8 +200,6 @@ export async function resolveOptions(): Promise<StormOptions> {
|
|
170
200
|
};
|
171
201
|
}
|
172
202
|
|
173
|
-
const LAYOUT_MARGIN = 50;
|
174
|
-
|
175
203
|
export class StormEventParser {
|
176
204
|
private events: StormEvent[] = [];
|
177
205
|
private planName: string = '';
|
@@ -193,8 +221,9 @@ export class StormEventParser {
|
|
193
221
|
this.connections = [];
|
194
222
|
}
|
195
223
|
|
196
|
-
public addEvent(handle:string, evt: StormEvent): StormDefinitions {
|
224
|
+
public addEvent(handle: string, evt: StormEvent): StormDefinitions {
|
197
225
|
this.events.push(evt);
|
226
|
+
console.log('evt', evt);
|
198
227
|
switch (evt.type) {
|
199
228
|
case 'CREATE_PLAN_PROPERTIES':
|
200
229
|
this.planName = evt.payload.name;
|
@@ -216,13 +245,13 @@ export class StormEventParser {
|
|
216
245
|
this.error = evt.payload.error;
|
217
246
|
break;
|
218
247
|
case 'CREATE_API':
|
219
|
-
this.blocks[evt.payload.blockName].apis.push(evt.payload.content);
|
248
|
+
this.blocks[evt.payload.blockName].apis.push(prettifyKaplang(evt.payload.content));
|
220
249
|
break;
|
221
250
|
case 'CREATE_TYPE':
|
222
|
-
this.blocks[evt.payload.blockName].types.push(evt.payload.content);
|
251
|
+
this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
|
223
252
|
break;
|
224
253
|
case 'CREATE_MODEL':
|
225
|
-
this.blocks[evt.payload.blockName].models.push(evt.payload.content);
|
254
|
+
this.blocks[evt.payload.blockName].models.push(prettifyKaplang(evt.payload.content));
|
226
255
|
break;
|
227
256
|
case 'CREATE_CONNECTION':
|
228
257
|
this.connections.push(evt.payload);
|
@@ -242,6 +271,9 @@ export class StormEventParser {
|
|
242
271
|
}
|
243
272
|
|
244
273
|
public isValid(): boolean {
|
274
|
+
if (!this.planName) {
|
275
|
+
return false;
|
276
|
+
}
|
245
277
|
return !this.failed;
|
246
278
|
}
|
247
279
|
|
@@ -249,59 +281,13 @@ export class StormEventParser {
|
|
249
281
|
return this.error;
|
250
282
|
}
|
251
283
|
|
252
|
-
private applyLayoutToBlocks(result: StormDefinitions): StormDefinitions {
|
253
|
-
const graph = createGraph();
|
254
|
-
const blockInstances: { [key: string]: BlockInstance } = {};
|
255
|
-
|
256
|
-
result.plan.spec.blocks.forEach((block, index) => {
|
257
|
-
graph.addNode(block.id, block);
|
258
|
-
blockInstances[block.id] = block;
|
259
|
-
});
|
260
|
-
|
261
|
-
result.plan.spec.connections.forEach((connection) => {
|
262
|
-
graph.addLink(connection.provider.blockId, connection.consumer.blockId);
|
263
|
-
});
|
264
|
-
|
265
|
-
const layout = createLayout(graph, {
|
266
|
-
springLength: 150,
|
267
|
-
debug: true,
|
268
|
-
dimensions: 2,
|
269
|
-
gravity: 2,
|
270
|
-
springCoefficient: 0.0008,
|
271
|
-
});
|
272
|
-
|
273
|
-
for (let i = 0; i < 100; ++i) {
|
274
|
-
layout.step();
|
275
|
-
}
|
276
|
-
|
277
|
-
// Layout might place things in negative space. We move everything to positive space
|
278
|
-
const graphBox = layout.getGraphRect();
|
279
|
-
let yAdjust = 0;
|
280
|
-
let xAdjust = 0;
|
281
|
-
if (graphBox.y1 < 0) {
|
282
|
-
yAdjust = -graphBox.y1;
|
283
|
-
}
|
284
|
-
if (graphBox.x1 < 0) {
|
285
|
-
xAdjust = -graphBox.x1;
|
286
|
-
}
|
287
|
-
|
288
|
-
graph.forEachNode((node) => {
|
289
|
-
const position = layout.getNodePosition(node.id);
|
290
|
-
blockInstances[node.id].dimensions.left = LAYOUT_MARGIN + Math.round(position.x + xAdjust);
|
291
|
-
blockInstances[node.id].dimensions.top = LAYOUT_MARGIN + Math.round(position.y + yAdjust);
|
292
|
-
});
|
293
|
-
|
294
|
-
layout.dispose();
|
295
|
-
|
296
|
-
return result;
|
297
|
-
}
|
298
|
-
|
299
284
|
public toResult(handle: string): StormDefinitions {
|
300
|
-
const planRef = this.toRef(handle, this.planName);
|
285
|
+
const planRef = this.toRef(handle, this.planName ?? 'undefined');
|
301
286
|
const blockDefinitions = this.toBlockDefinitions(handle);
|
302
287
|
const refIdMap: { [key: string]: string } = {};
|
303
288
|
const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
|
304
|
-
|
289
|
+
// Create a deterministic uuid
|
290
|
+
const id = uuid(ref, uuid.URL);
|
305
291
|
refIdMap[ref] = id;
|
306
292
|
return {
|
307
293
|
id,
|
@@ -368,13 +354,41 @@ export class StormEventParser {
|
|
368
354
|
return;
|
369
355
|
}
|
370
356
|
|
357
|
+
if (apiProviderBlock.content.spec.entities?.source?.value) {
|
358
|
+
if (!clientConsumerBlock.content.spec.entities) {
|
359
|
+
clientConsumerBlock.content.spec.entities = {
|
360
|
+
types: [],
|
361
|
+
source: {
|
362
|
+
type: KAPLANG_ID,
|
363
|
+
version: KAPLANG_VERSION,
|
364
|
+
value: '',
|
365
|
+
},
|
366
|
+
};
|
367
|
+
}
|
368
|
+
|
369
|
+
const clientTypes = DSLDataTypeParser.parse(
|
370
|
+
clientConsumerBlock.content.spec.entities.source!.value
|
371
|
+
);
|
372
|
+
const apiTypes = DSLDataTypeParser.parse(apiProviderBlock.content.spec.entities?.source?.value);
|
373
|
+
|
374
|
+
apiTypes.forEach((apiType) => {
|
375
|
+
if (clientTypes.some((clientType) => clientType.name === apiType.name)) {
|
376
|
+
// Already exists
|
377
|
+
return;
|
378
|
+
}
|
379
|
+
clientTypes.push(apiType);
|
380
|
+
});
|
381
|
+
|
382
|
+
clientConsumerBlock.content.spec.entities.source!.value = KaplangWriter.write(clientTypes);
|
383
|
+
}
|
371
384
|
clientResource.spec.methods = apiResource.spec.methods;
|
372
385
|
clientResource.spec.source = apiResource.spec.source;
|
373
386
|
});
|
374
387
|
|
375
|
-
const connections = this.connections.map((connection) => {
|
388
|
+
const connections: Connection[] = this.connections.map((connection) => {
|
376
389
|
const fromRef = this.toRef(handle, connection.fromComponent);
|
377
390
|
const toRef = this.toRef(handle, connection.toComponent);
|
391
|
+
|
378
392
|
return {
|
379
393
|
port: {
|
380
394
|
type: this.toPortType(connection.fromResourceType),
|
@@ -387,9 +401,7 @@ export class StormEventParser {
|
|
387
401
|
blockId: refIdMap[fromRef.toNormalizedString()],
|
388
402
|
resourceName: connection.fromResource,
|
389
403
|
},
|
390
|
-
mapping:
|
391
|
-
//TODO: Add mapping
|
392
|
-
},
|
404
|
+
mapping: this.toConnectionMapping(handle, connection, blockDefinitions),
|
393
405
|
} satisfies Connection;
|
394
406
|
});
|
395
407
|
|
@@ -406,10 +418,10 @@ export class StormEventParser {
|
|
406
418
|
},
|
407
419
|
};
|
408
420
|
|
409
|
-
return
|
421
|
+
return {
|
410
422
|
plan,
|
411
423
|
blocks: Object.values(blockDefinitions),
|
412
|
-
}
|
424
|
+
};
|
413
425
|
}
|
414
426
|
|
415
427
|
private toSafeName(name: string): string {
|
@@ -637,6 +649,55 @@ export class StormEventParser {
|
|
637
649
|
return '';
|
638
650
|
}
|
639
651
|
|
652
|
+
private toConnectionMapping(
|
653
|
+
handle: string,
|
654
|
+
connection: StormConnection,
|
655
|
+
blockDefinitions: { [key: string]: BlockDefinitionInfo }
|
656
|
+
): any {
|
657
|
+
if (connection.fromResourceType !== 'API') {
|
658
|
+
return;
|
659
|
+
}
|
660
|
+
|
661
|
+
const fromRef = this.toRef(handle, connection.fromComponent);
|
662
|
+
|
663
|
+
const apiProviderBlock = blockDefinitions[fromRef.toNormalizedString()];
|
664
|
+
if (!apiProviderBlock) {
|
665
|
+
console.warn('Provider block not found: %s', connection.fromComponent, connection);
|
666
|
+
return;
|
667
|
+
}
|
668
|
+
|
669
|
+
const apiResource = apiProviderBlock.content.spec.providers?.find(
|
670
|
+
(p) => p.kind === this.options.apiKind && p.metadata.name === connection.fromResource
|
671
|
+
);
|
672
|
+
|
673
|
+
if (!apiResource) {
|
674
|
+
console.warn(
|
675
|
+
'API resource not found: %s on %s',
|
676
|
+
connection.fromResource,
|
677
|
+
fromRef.toNormalizedString(),
|
678
|
+
connection
|
679
|
+
);
|
680
|
+
return;
|
681
|
+
}
|
682
|
+
|
683
|
+
const apiMethods = DSLConverters.toSchemaMethods(
|
684
|
+
DSLAPIParser.parse(apiResource.spec?.source?.value ?? '', {
|
685
|
+
ignoreSemantics: true,
|
686
|
+
}) as (DSLMethod | DSLController)[]
|
687
|
+
);
|
688
|
+
|
689
|
+
const mapping: any = {};
|
690
|
+
|
691
|
+
Object.entries(apiMethods).forEach(([methodId, method]) => {
|
692
|
+
mapping[methodId] = {
|
693
|
+
targetId: methodId,
|
694
|
+
type: 'EXACT',
|
695
|
+
};
|
696
|
+
});
|
697
|
+
|
698
|
+
return mapping;
|
699
|
+
}
|
700
|
+
|
640
701
|
private toPortType(type: StormResourceType) {
|
641
702
|
switch (type) {
|
642
703
|
case 'API':
|
package/src/storm/events.ts
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import {StormDefinitions} from
|
5
|
+
import { StormDefinitions } from './event-parser';
|
6
6
|
|
7
7
|
export type StormResourceType =
|
8
8
|
| 'API'
|
@@ -146,6 +146,7 @@ export interface StormEventFile {
|
|
146
146
|
payload: {
|
147
147
|
filename: string;
|
148
148
|
content: string;
|
149
|
+
blockName: string;
|
149
150
|
blockRef: string;
|
150
151
|
};
|
151
152
|
}
|
package/src/storm/routes.ts
CHANGED
@@ -4,15 +4,15 @@
|
|
4
4
|
*/
|
5
5
|
|
6
6
|
import Router from 'express-promise-router';
|
7
|
-
import {Response} from 'express';
|
8
|
-
import {corsHandler} from '../middleware/cors';
|
9
|
-
import {stringBody} from '../middleware/stringBody';
|
10
|
-
import {KapetaBodyRequest} from '../types';
|
11
|
-
import {StormContextRequest, StormFileImplementationPrompt, StormFileInfo, StormStream} from './stream';
|
12
|
-
import {stormClient} from './stormClient';
|
13
|
-
import {StormEvent} from './events';
|
14
|
-
import {resolveOptions, StormDefinitions, StormEventParser} from './event-parser';
|
15
|
-
import {StormCodegen} from './codegen';
|
7
|
+
import { Response } from 'express';
|
8
|
+
import { corsHandler } from '../middleware/cors';
|
9
|
+
import { stringBody } from '../middleware/stringBody';
|
10
|
+
import { KapetaBodyRequest } from '../types';
|
11
|
+
import { StormContextRequest, StormFileImplementationPrompt, StormFileInfo, StormStream } from './stream';
|
12
|
+
import { stormClient } from './stormClient';
|
13
|
+
import { StormEvent } from './events';
|
14
|
+
import { resolveOptions, StormDefinitions, StormEventParser } from './event-parser';
|
15
|
+
import { StormCodegen } from './codegen';
|
16
16
|
|
17
17
|
const router = Router();
|
18
18
|
|
@@ -44,7 +44,7 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
|
|
44
44
|
// We can't continue if the meta stream is invalid
|
45
45
|
sendEvent(res, {
|
46
46
|
type: 'ERROR_INTERNAL',
|
47
|
-
payload: {error: eventParser.getError()},
|
47
|
+
payload: { error: eventParser.getError() },
|
48
48
|
reason: 'Failed to generate system',
|
49
49
|
created: Date.now(),
|
50
50
|
});
|
@@ -94,15 +94,14 @@ function sendError(err: Error, res: Response) {
|
|
94
94
|
sendEvent(res, {
|
95
95
|
type: 'ERROR_INTERNAL',
|
96
96
|
created: Date.now(),
|
97
|
-
payload: {error: err.message},
|
97
|
+
payload: { error: err.message },
|
98
98
|
reason: 'Failed while sending prompt',
|
99
99
|
});
|
100
100
|
} else {
|
101
|
-
res.status(400).send({error: err.message});
|
101
|
+
res.status(400).send({ error: err.message });
|
102
102
|
}
|
103
103
|
}
|
104
104
|
|
105
|
-
|
106
105
|
function streamStormPartialResponse(result: StormStream, res: Response) {
|
107
106
|
return new Promise<void>((resolve, reject) => {
|
108
107
|
result.on('data', (data) => {
|
package/src/storm/stormClient.ts
CHANGED
package/src/storm/stream.ts
CHANGED
@@ -169,17 +169,9 @@ describe('event-parser', () => {
|
|
169
169
|
|
170
170
|
const serviceBlockInstance = result.plan.spec.blocks[0];
|
171
171
|
expect(serviceBlockInstance.name).toBe('service');
|
172
|
-
expect(serviceBlockInstance.dimensions.width).toBe(150);
|
173
|
-
expect(serviceBlockInstance.dimensions.height).toBe(200);
|
174
|
-
expect(serviceBlockInstance.dimensions.top).toBe(3);
|
175
|
-
expect(serviceBlockInstance.dimensions.left).toBe(6);
|
176
172
|
|
177
173
|
const uiBlockInstance = result.plan.spec.blocks[1];
|
178
174
|
expect(uiBlockInstance.name).toBe('ui');
|
179
|
-
expect(uiBlockInstance.dimensions.width).toBe(150);
|
180
|
-
expect(uiBlockInstance.dimensions.height).toBe(200);
|
181
|
-
expect(uiBlockInstance.dimensions.top).toBe(107);
|
182
|
-
expect(uiBlockInstance.dimensions.left).toBe(112);
|
183
175
|
|
184
176
|
expect(result.plan.spec.connections.length).toBe(1);
|
185
177
|
expect(result.plan.spec.connections[0].consumer.blockId).toBe(uiBlockInstance.id);
|