@kapeta/local-cluster-service 0.43.3 → 0.45.0

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/index.js +2 -0
  3. package/dist/cjs/src/codeGeneratorManager.d.ts +1 -0
  4. package/dist/cjs/src/codeGeneratorManager.js +12 -6
  5. package/dist/cjs/src/middleware/cors.d.ts +1 -0
  6. package/dist/cjs/src/middleware/kapeta.d.ts +1 -0
  7. package/dist/cjs/src/middleware/stringBody.d.ts +1 -0
  8. package/dist/cjs/src/proxy/routes.js +19 -19
  9. package/dist/cjs/src/proxy/types/rest.d.ts +2 -4
  10. package/dist/cjs/src/proxy/types/rest.js +45 -6
  11. package/dist/cjs/src/proxy/types/web.d.ts +2 -4
  12. package/dist/cjs/src/proxy/types/web.js +2 -2
  13. package/dist/cjs/src/storm/codegen.d.ts +36 -0
  14. package/dist/cjs/src/storm/codegen.js +160 -0
  15. package/dist/cjs/src/storm/event-parser.d.ts +70 -0
  16. package/dist/cjs/src/storm/event-parser.js +543 -0
  17. package/dist/cjs/src/storm/events.d.ts +127 -0
  18. package/dist/cjs/src/storm/events.js +6 -0
  19. package/dist/cjs/src/storm/routes.d.ts +7 -0
  20. package/dist/cjs/src/storm/routes.js +109 -0
  21. package/dist/cjs/src/storm/stormClient.d.ts +13 -0
  22. package/dist/cjs/src/storm/stormClient.js +87 -0
  23. package/dist/cjs/src/storm/stream.d.ts +38 -0
  24. package/dist/cjs/src/storm/stream.js +57 -0
  25. package/dist/cjs/src/types.d.ts +5 -2
  26. package/dist/esm/index.js +2 -0
  27. package/dist/esm/src/codeGeneratorManager.d.ts +1 -0
  28. package/dist/esm/src/codeGeneratorManager.js +12 -6
  29. package/dist/esm/src/middleware/cors.d.ts +1 -0
  30. package/dist/esm/src/middleware/kapeta.d.ts +1 -0
  31. package/dist/esm/src/middleware/stringBody.d.ts +1 -0
  32. package/dist/esm/src/proxy/routes.js +19 -19
  33. package/dist/esm/src/proxy/types/rest.d.ts +2 -4
  34. package/dist/esm/src/proxy/types/rest.js +45 -6
  35. package/dist/esm/src/proxy/types/web.d.ts +2 -4
  36. package/dist/esm/src/proxy/types/web.js +2 -2
  37. package/dist/esm/src/storm/codegen.d.ts +36 -0
  38. package/dist/esm/src/storm/codegen.js +160 -0
  39. package/dist/esm/src/storm/event-parser.d.ts +70 -0
  40. package/dist/esm/src/storm/event-parser.js +543 -0
  41. package/dist/esm/src/storm/events.d.ts +127 -0
  42. package/dist/esm/src/storm/events.js +6 -0
  43. package/dist/esm/src/storm/routes.d.ts +7 -0
  44. package/dist/esm/src/storm/routes.js +109 -0
  45. package/dist/esm/src/storm/stormClient.d.ts +13 -0
  46. package/dist/esm/src/storm/stormClient.js +87 -0
  47. package/dist/esm/src/storm/stream.d.ts +38 -0
  48. package/dist/esm/src/storm/stream.js +57 -0
  49. package/dist/esm/src/types.d.ts +5 -2
  50. package/index.ts +2 -0
  51. package/package.json +6 -3
  52. package/src/codeGeneratorManager.ts +17 -8
  53. package/src/proxy/routes.ts +26 -20
  54. package/src/proxy/types/rest.ts +73 -11
  55. package/src/proxy/types/web.ts +3 -5
  56. package/src/storm/codegen.ts +210 -0
  57. package/src/storm/event-parser.ts +688 -0
  58. package/src/storm/events.ts +169 -0
  59. package/src/storm/routes.ts +143 -0
  60. package/src/storm/stormClient.ts +114 -0
  61. package/src/storm/stream.ts +88 -0
  62. package/src/types.ts +5 -2
  63. package/src/utils/BlockInstanceRunner.ts +4 -2
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # [0.45.0](https://github.com/kapetacom/local-cluster-service/compare/v0.44.0...v0.45.0) (2024-05-24)
2
+
3
+
4
+ ### Features
5
+
6
+ * Introduces Storm API ([#147](https://github.com/kapetacom/local-cluster-service/issues/147)) ([5bdb075](https://github.com/kapetacom/local-cluster-service/commit/5bdb075f838c331e3a517778c55f83c32cc5b812))
7
+
8
+ # [0.44.0](https://github.com/kapetacom/local-cluster-service/compare/v0.43.3...v0.44.0) (2024-05-08)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Remove wrong todo comment ([c300f3c](https://github.com/kapetacom/local-cluster-service/commit/c300f3c8bd2542517317aca0d54825332f6424bc))
14
+ * Safeguard with optional chaining ([ff8af98](https://github.com/kapetacom/local-cluster-service/commit/ff8af98845d3cc6431a919a7d251c5a90e5b88ef))
15
+
16
+
17
+ ### Features
18
+
19
+ * Mask sensitive data in req and res body ([39c0c46](https://github.com/kapetacom/local-cluster-service/commit/39c0c46a9f9c58938b70c4736d1fb8ec651d7d9c))
20
+
1
21
  ## [0.43.3](https://github.com/kapetacom/local-cluster-service/compare/v0.43.2...v0.43.3) (2024-05-08)
2
22
 
3
23
 
package/dist/cjs/index.js CHANGED
@@ -50,6 +50,7 @@ const routes_9 = __importDefault(require("./src/attachments/routes"));
50
50
  const routes_10 = __importDefault(require("./src/tasks/routes"));
51
51
  const api_1 = __importDefault(require("./src/api"));
52
52
  const routes_11 = __importDefault(require("./src/ai/routes"));
53
+ const routes_12 = __importDefault(require("./src/storm/routes"));
53
54
  const utils_1 = require("./src/utils/utils");
54
55
  const request_1 = __importDefault(require("request"));
55
56
  const repositoryManager_1 = require("./src/repositoryManager");
@@ -89,6 +90,7 @@ function createServer() {
89
90
  app.use('/tasks', routes_10.default);
90
91
  app.use('/api', api_1.default);
91
92
  app.use('/ai', routes_11.default);
93
+ app.use('/storm', routes_12.default);
92
94
  app.get('/status', async (req, res) => {
93
95
  res.send({
94
96
  ok: true,
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { Definition } from '@kapeta/local-cluster-config';
6
6
  declare class CodeGeneratorManager {
7
+ ensureTarget(targetKind: string): Promise<boolean>;
7
8
  private ensureLanguageTargetInRegistry;
8
9
  reload(): Promise<void>;
9
10
  initialize(): Promise<void>;
@@ -17,6 +17,17 @@ const repositoryManager_1 = require("./repositoryManager");
17
17
  const TARGET_KIND = 'core/language-target';
18
18
  const BLOCK_TYPE_REGEX = /^core\/block-type.*/;
19
19
  class CodeGeneratorManager {
20
+ async ensureTarget(targetKind) {
21
+ const targetRef = (0, nodejs_utils_1.normalizeKapetaUri)(targetKind);
22
+ // Automatically downloads target if not available
23
+ const targetAsset = await assetManager_1.assetManager.getAsset(targetRef);
24
+ if (!targetAsset) {
25
+ console.error('Language target not found: %s', targetKind);
26
+ return false;
27
+ }
28
+ await this.ensureLanguageTargetInRegistry(targetAsset?.path, targetAsset?.version, targetAsset?.data);
29
+ return true;
30
+ }
20
31
  async ensureLanguageTargetInRegistry(path, version, definition) {
21
32
  const key = `${definition.metadata.name}:${version}`;
22
33
  try {
@@ -72,14 +83,9 @@ class CodeGeneratorManager {
72
83
  //Not all block types have targets
73
84
  return;
74
85
  }
75
- const targetRef = (0, nodejs_utils_1.normalizeKapetaUri)(yamlContent.spec.target?.kind);
76
- // Automatically downloads target if not available
77
- const targetAsset = await assetManager_1.assetManager.getAsset(targetRef);
78
- if (!targetAsset) {
79
- console.error('Language target not found: %s', yamlContent.spec.target?.kind);
86
+ if (!(await this.ensureTarget(yamlContent.spec.target?.kind))) {
80
87
  return;
81
88
  }
82
- await this.ensureLanguageTargetInRegistry(targetAsset?.path, targetAsset?.version, targetAsset?.data);
83
89
  const baseDir = path_1.default.dirname(yamlFile);
84
90
  console.log('Generating code for path: %s', baseDir);
85
91
  const codeGenerator = new codegen_1.BlockCodeGenerator(yamlContent);
@@ -2,5 +2,6 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
+ /// <reference types="cookie-parser" />
5
6
  import { NextFunction, Request, Response } from 'express';
6
7
  export declare function corsHandler(req: Request, res: Response, next: NextFunction): void;
@@ -2,6 +2,7 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
+ /// <reference types="cookie-parser" />
5
6
  import { NextFunction, Request, Response } from 'express';
6
7
  import { EnvironmentType } from '../types';
7
8
  export interface KapetaRequest extends Request {
@@ -2,6 +2,7 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
+ /// <reference types="cookie-parser" />
5
6
  import { NextFunction, Request, Response } from 'express';
6
7
  export type StringBodyRequest = Request<any> & {
7
8
  stringBody?: string;
@@ -49,19 +49,19 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
49
49
  });
50
50
  return;
51
51
  }
52
- const toBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
52
+ const consumerBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
53
53
  return blockInstance.id.toLowerCase() === connection.consumer.blockId.toLowerCase();
54
54
  });
55
- if (!toBlockInstance) {
55
+ if (!consumerBlockInstance) {
56
56
  res.status(401).send({ error: `Block instance not found "${req.params.consumerInstanceId}` });
57
57
  return;
58
58
  }
59
- const toBlockAsset = await assetManager_1.assetManager.getAsset(toBlockInstance.block.ref);
60
- if (!toBlockAsset) {
61
- res.status(401).send({ error: `Block asset not found "${toBlockInstance.block.ref}` });
59
+ const consumerBlockAsset = await assetManager_1.assetManager.getAsset(consumerBlockInstance.block.ref);
60
+ if (!consumerBlockAsset) {
61
+ res.status(401).send({ error: `Block asset not found "${consumerBlockInstance.block.ref}` });
62
62
  return;
63
63
  }
64
- const consumerResource = getResource(toBlockAsset.data.spec.consumers, req.params.consumerResourceName);
64
+ const consumerResource = getResource(consumerBlockAsset.data.spec.consumers, req.params.consumerResourceName);
65
65
  if (!consumerResource) {
66
66
  res.status(401).send({
67
67
  error: `Block resource not found "${req.params.consumerInstanceId}::${req.params.consumerResourceName}`,
@@ -69,19 +69,19 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
69
69
  return;
70
70
  }
71
71
  const basePath = clusterService_1.clusterService.getProxyPath(req.params.systemId, req.params.consumerInstanceId, req.params.consumerResourceName, req.params.type);
72
- const fromBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
72
+ const providerBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
73
73
  return blockInstance.id.toLowerCase() === connection.provider.blockId.toLowerCase();
74
74
  });
75
- if (!fromBlockInstance) {
75
+ if (!providerBlockInstance) {
76
76
  res.status(401).send({ error: `Block instance not found "${connection.provider.blockId}` });
77
77
  return;
78
78
  }
79
- const fromBlockAsset = await assetManager_1.assetManager.getAsset(fromBlockInstance.block.ref);
80
- if (!fromBlockAsset) {
81
- res.status(401).send({ error: `Block asset not found "${fromBlockInstance.block.ref}` });
79
+ const providerBlockAsset = await assetManager_1.assetManager.getAsset(providerBlockInstance.block.ref);
80
+ if (!providerBlockAsset) {
81
+ res.status(401).send({ error: `Block asset not found "${providerBlockInstance.block.ref}` });
82
82
  return;
83
83
  }
84
- const providerResource = getResource(fromBlockAsset.data.spec.providers, connection.provider.resourceName);
84
+ const providerResource = getResource(providerBlockAsset.data.spec.providers, connection.provider.resourceName);
85
85
  if (!providerResource) {
86
86
  res.status(401).send({
87
87
  error: `Block resource not found "${connection.provider.blockId}::${connection.provider.resourceName}`,
@@ -93,18 +93,18 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
93
93
  while (address.endsWith('/')) {
94
94
  address = address.substring(0, address.length - 1);
95
95
  }
96
- /*
97
- Get the path the consumer requested.
98
- Note that this might not match the path the destination is expecting so we need to identify the method
99
- that is being called and identify the destination path from the connection.
100
- */
96
+ // Get the path the consumer requested. Note that this might not match the path the
97
+ // destination is expecting so we need to identify the method that is being called and
98
+ // identify the destination path from the connection.
101
99
  const consumerPath = req.originalUrl.substring(basePath.length - 1);
102
100
  typeHandler(req, res, {
103
- consumerPath,
104
101
  address,
102
+ connection,
103
+ consumerPath,
105
104
  consumerResource,
105
+ consumerBlockAsset,
106
106
  providerResource,
107
- connection,
107
+ providerBlockAsset,
108
108
  });
109
109
  }
110
110
  catch (err) {
@@ -2,9 +2,7 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
- import { Response } from 'express';
6
- import { ProxyRequestInfo } from '../../types';
7
- import { StringBodyRequest } from '../../middleware/stringBody';
5
+ import { ProxyRequestHandler } from '../../types';
8
6
  import { Resource } from '@kapeta/schemas';
9
7
  export declare function getRestMethodId(restResource: Resource, httpMethod: string, httpPath: string): string | undefined;
10
- export declare function proxyRestRequest(req: StringBodyRequest, res: Response, opts: ProxyRequestInfo): void;
8
+ export declare const proxyRestRequest: ProxyRequestHandler;
@@ -16,6 +16,7 @@ const networkManager_1 = require("../../networkManager");
16
16
  const socketManager_1 = require("../../socketManager");
17
17
  const qs_1 = require("qs");
18
18
  const web_1 = require("./web");
19
+ const kaplang_core_1 = require("@kapeta/kaplang-core");
19
20
  function getRestMethodId(restResource, httpMethod, httpPath) {
20
21
  return lodash_1.default.findKey(restResource.spec.methods, (method) => {
21
22
  let methodType = method.method ? method.method.toUpperCase() : 'GET';
@@ -63,12 +64,49 @@ function resolveMethods(req, opts) {
63
64
  providerMethod,
64
65
  };
65
66
  }
66
- function proxyRestRequest(req, res, opts) {
67
+ function resolveEntitiesSource(blockAsset) {
68
+ return blockAsset.data.spec?.entities?.source?.value || '';
69
+ }
70
+ const MASK_STRING = '*******';
71
+ function maskSimpleRequest(req, consumerMethod, entitiesSource) {
72
+ const maskedRequest = lodash_1.default.cloneDeep(req);
73
+ Object.entries(consumerMethod.arguments || {}).forEach(([key, value]) => {
74
+ if (value.transport === kaplang_core_1.HTTPTransport.BODY && typeof maskedRequest.body === 'string') {
75
+ try {
76
+ maskedRequest.body = (0, kaplang_core_1.maskSensitiveData)(maskedRequest.body, kaplang_core_1.DSLConverters.fromSchemaType(value), entitiesSource, MASK_STRING);
77
+ }
78
+ catch (error) {
79
+ // Ignore errors masking the request body
80
+ }
81
+ }
82
+ // TODO: Mask HEADER
83
+ // TODO: Mask QUERY
84
+ });
85
+ return maskedRequest;
86
+ }
87
+ function maskSimpleResponse(res, consumerMethod, entitiesSource) {
88
+ const maskedResponse = lodash_1.default.cloneDeep(res);
89
+ Object.entries(consumerMethod.arguments || {}).forEach(([key, value]) => {
90
+ // TODO: Mask HEADER
91
+ });
92
+ try {
93
+ if (typeof maskedResponse.body === 'string') {
94
+ maskedResponse.body = (0, kaplang_core_1.maskSensitiveData)(maskedResponse.body, kaplang_core_1.DSLConverters.fromSchemaType(consumerMethod.responseType), entitiesSource, MASK_STRING);
95
+ }
96
+ }
97
+ catch (error) {
98
+ // Ignore errors masking the response body
99
+ }
100
+ return maskedResponse;
101
+ }
102
+ const proxyRestRequest = (req, res, opts) => {
67
103
  if (lodash_1.default.isEmpty(opts.consumerResource.spec.methods) && lodash_1.default.isEmpty(opts.providerResource.spec.methods)) {
68
104
  // If there are no methods defined, we assume the user controls the path and we just proxy the raw request
69
105
  return (0, web_1.proxyHttpRequest)(req, res, opts);
70
106
  }
71
- let { consumerMethod, providerMethod } = resolveMethods(req, opts);
107
+ const { consumerMethod, providerMethod } = resolveMethods(req, opts);
108
+ const consumerEntitiesSource = resolveEntitiesSource(opts.consumerBlockAsset);
109
+ const providerEntitiesSource = resolveEntitiesSource(opts.providerBlockAsset);
72
110
  const consumerPathTemplate = (0, pathTemplateParser_1.pathTemplateParser)(consumerMethod.path);
73
111
  const providerPathTemplate = (0, pathTemplateParser_1.pathTemplateParser)(providerMethod.path);
74
112
  const pathVariables = consumerPathTemplate.parse(opts.consumerPath);
@@ -98,7 +136,7 @@ function proxyRestRequest(req, res, opts) {
98
136
  body: req.stringBody,
99
137
  headers: requestHeaders,
100
138
  };
101
- const traffic = networkManager_1.networkManager.addRequest(req.params.systemId, opts.connection, reqOpts, consumerMethod.id, providerMethod.id);
139
+ const traffic = networkManager_1.networkManager.addRequest(req.params.systemId, opts.connection, maskSimpleRequest(reqOpts, consumerMethod, consumerEntitiesSource), consumerMethod.id, providerMethod.id);
102
140
  socketManager_1.socketManager.emit(traffic.connectionId, 'traffic_start', traffic);
103
141
  (0, request_1.default)(reqOpts, function (err, response, responseBody) {
104
142
  if (err) {
@@ -113,16 +151,17 @@ function proxyRestRequest(req, res, opts) {
113
151
  delete responseHeaders['connection'];
114
152
  res.set(responseHeaders);
115
153
  res.status(response.statusCode);
116
- traffic.withResponse({
154
+ const simpleResponse = {
117
155
  code: response.statusCode,
118
156
  headers: response.headers,
119
157
  body: responseBody,
120
- });
158
+ };
159
+ traffic.withResponse(maskSimpleResponse(simpleResponse, consumerMethod, consumerEntitiesSource));
121
160
  socketManager_1.socketManager.emit(traffic.connectionId, 'traffic_end', traffic);
122
161
  if (responseBody) {
123
162
  res.write(responseBody);
124
163
  }
125
164
  res.end();
126
165
  });
127
- }
166
+ };
128
167
  exports.proxyRestRequest = proxyRestRequest;
@@ -2,7 +2,5 @@
2
2
  * Copyright 2023 Kapeta Inc.
3
3
  * SPDX-License-Identifier: BUSL-1.1
4
4
  */
5
- import { Response } from 'express';
6
- import { ProxyRequestInfo } from '../../types';
7
- import { StringBodyRequest } from '../../middleware/stringBody';
8
- export declare function proxyHttpRequest(req: StringBodyRequest, res: Response, opts: ProxyRequestInfo): void;
5
+ import { ProxyRequestHandler } from '../../types';
6
+ export declare const proxyHttpRequest: ProxyRequestHandler;
@@ -13,7 +13,7 @@ const lodash_1 = __importDefault(require("lodash"));
13
13
  const networkManager_1 = require("../../networkManager");
14
14
  const socketManager_1 = require("../../socketManager");
15
15
  const qs_1 = require("qs");
16
- function proxyHttpRequest(req, res, opts) {
16
+ const proxyHttpRequest = (req, res, opts) => {
17
17
  const requestHeaders = lodash_1.default.clone(req.headers);
18
18
  delete requestHeaders['content-length'];
19
19
  delete requestHeaders['content-encoding'];
@@ -57,5 +57,5 @@ function proxyHttpRequest(req, res, opts) {
57
57
  });
58
58
  //We need to pipe the proxy response to the client response to handle sockets and event streams
59
59
  proxyReq.pipe(res);
60
- }
60
+ };
61
61
  exports.proxyHttpRequest = proxyHttpRequest;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ import { BlockDefinitionInfo } from './event-parser';
6
+ import { StormStream } from './stream';
7
+ export declare class StormCodegen {
8
+ private readonly userPrompt;
9
+ private readonly blocks;
10
+ private readonly out;
11
+ constructor(userPrompt: string, blocks: BlockDefinitionInfo[]);
12
+ process(): Promise<void>;
13
+ getStream(): StormStream;
14
+ private handleFileOutput;
15
+ /**
16
+ * Generates the code for a block and sends it to the AI
17
+ */
18
+ private processBlockCode;
19
+ /**
20
+ * Emits the text-based files to the stream
21
+ */
22
+ private emitFiles;
23
+ private emitFile;
24
+ /**
25
+ * Sends the template to the AI and processes the response
26
+ */
27
+ private processTemplates;
28
+ /**
29
+ * Converts the generated files to a format that can be sent to the AI
30
+ */
31
+ private toStormFiles;
32
+ /**
33
+ * Generates the code using codegen for a given block.
34
+ */
35
+ private generateBlock;
36
+ }
@@ -0,0 +1,160 @@
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
+ exports.StormCodegen = void 0;
8
+ const codegen_1 = require("@kapeta/codegen");
9
+ const codeGeneratorManager_1 = require("../codeGeneratorManager");
10
+ const stormClient_1 = require("./stormClient");
11
+ const stream_1 = require("./stream");
12
+ class StormCodegen {
13
+ userPrompt;
14
+ blocks;
15
+ out = new stream_1.StormStream();
16
+ constructor(userPrompt, blocks) {
17
+ this.userPrompt = userPrompt;
18
+ this.blocks = blocks;
19
+ }
20
+ async process() {
21
+ console.log('Processing blocks', this.blocks.length);
22
+ for (const block of this.blocks) {
23
+ await this.processBlockCode(block);
24
+ }
25
+ this.out.end();
26
+ }
27
+ getStream() {
28
+ return this.out;
29
+ }
30
+ handleFileOutput(blockUri, template, data) {
31
+ switch (data.type) {
32
+ case 'FILE':
33
+ template.filename = data.payload.filename;
34
+ template.content = data.payload.content;
35
+ this.emitFile(blockUri, data.payload.filename, data.payload.content, data.reason);
36
+ break;
37
+ }
38
+ }
39
+ /**
40
+ * Generates the code for a block and sends it to the AI
41
+ */
42
+ async processBlockCode(block) {
43
+ // Generate the code for the block using the standard codegen templates
44
+ const generatedResult = await this.generateBlock(block.content, block.screens);
45
+ if (!generatedResult) {
46
+ console.warn('No generated result for block', block.uri);
47
+ return;
48
+ }
49
+ const allFiles = this.toStormFiles(generatedResult);
50
+ // Send all the non-ai files to the stream
51
+ this.emitFiles(block.uri, allFiles);
52
+ const implementFiles = [codegen_1.AIFileTypes.SERVICE, codegen_1.AIFileTypes.WEB_SCREEN];
53
+ // Gather the context files. These will be all be passed to the AI
54
+ const contextFiles = allFiles.filter((file) => file.type !== codegen_1.AIFileTypes.IGNORE && !implementFiles.includes(file.type));
55
+ // Send the service and UI templates to the AI. These will be send one-by-one in addition to the context files
56
+ const serviceFiles = allFiles.filter((file) => file.type === codegen_1.AIFileTypes.SERVICE);
57
+ await this.processTemplates(block.uri, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
58
+ //const uiTemplates: StormFileInfo[] = allFiles.filter((file) => file.type === 'ui');
59
+ //await this.processTemplates(stormClient.createUIImplementation, uiTemplates, contextFiles);
60
+ }
61
+ /**
62
+ * Emits the text-based files to the stream
63
+ */
64
+ emitFiles(uri, files) {
65
+ files.forEach((file) => {
66
+ if (!file.content || typeof file.content !== 'string') {
67
+ return;
68
+ }
69
+ if (file.type === codegen_1.AIFileTypes.SERVICE) {
70
+ // Don't send the service files to the stream yet
71
+ // They will need to be implemented by the AI
72
+ return;
73
+ }
74
+ if (file.type === codegen_1.AIFileTypes.WEB_SCREEN) {
75
+ // Don't send the web screen files to the stream yet
76
+ // They will need to be implemented by the AI
77
+ return;
78
+ }
79
+ this.emitFile(uri, file.filename, file.content);
80
+ });
81
+ }
82
+ emitFile(uri, filename, content, reason = 'File generated') {
83
+ this.out.emit('data', {
84
+ type: 'FILE',
85
+ reason,
86
+ created: Date.now(),
87
+ payload: {
88
+ filename: filename,
89
+ content: content,
90
+ blockRef: uri.toNormalizedString(),
91
+ },
92
+ });
93
+ }
94
+ /**
95
+ * Sends the template to the AI and processes the response
96
+ */
97
+ processTemplates(blockUri, generator, templates, contextFiles) {
98
+ const promises = templates.map(async (templateFile) => {
99
+ const stream = await generator({
100
+ context: contextFiles,
101
+ template: templateFile,
102
+ prompt: this.userPrompt,
103
+ });
104
+ stream.on('data', (evt) => this.handleFileOutput(blockUri, templateFile, evt));
105
+ return stream.waitForDone();
106
+ });
107
+ return Promise.all(promises);
108
+ }
109
+ /**
110
+ * Converts the generated files to a format that can be sent to the AI
111
+ */
112
+ toStormFiles(generatedResult) {
113
+ const allFiles = generatedResult.files.map((file) => {
114
+ if (!file.content) {
115
+ return {
116
+ ...file,
117
+ type: codegen_1.AIFileTypes.IGNORE,
118
+ };
119
+ }
120
+ if (typeof file.content !== 'string') {
121
+ return {
122
+ ...file,
123
+ type: codegen_1.AIFileTypes.IGNORE,
124
+ };
125
+ }
126
+ const rx = /\/\/AI-TYPE:([a-z0-9- _]+)\n/gi;
127
+ const match = rx.exec(file.content);
128
+ if (!match) {
129
+ return {
130
+ ...file,
131
+ type: codegen_1.AIFileTypes.IGNORE,
132
+ };
133
+ }
134
+ const type = match[1].trim();
135
+ file.content = file.content.replace(rx, '');
136
+ return {
137
+ ...file,
138
+ type,
139
+ };
140
+ });
141
+ return allFiles;
142
+ }
143
+ /**
144
+ * Generates the code using codegen for a given block.
145
+ */
146
+ async generateBlock(yamlContent, screens) {
147
+ if (!yamlContent.spec.target?.kind) {
148
+ //Not all block types have targets
149
+ return;
150
+ }
151
+ if (!(await codeGeneratorManager_1.codeGeneratorManager.ensureTarget(yamlContent.spec.target?.kind))) {
152
+ return;
153
+ }
154
+ const codeGenerator = new codegen_1.BlockCodeGenerator(yamlContent);
155
+ codeGenerator.withOption('AIContext', stormClient_1.STORM_ID);
156
+ codeGenerator.withOption('AIScreens', screens ?? []);
157
+ return codeGenerator.generate();
158
+ }
159
+ }
160
+ exports.StormCodegen = StormCodegen;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ import { ScreenTemplate, StormEvent } from './events';
6
+ import { BlockDefinition, Plan } from '@kapeta/schemas';
7
+ import { KapetaURI } from '@kapeta/nodejs-utils';
8
+ export interface BlockDefinitionInfo {
9
+ uri: KapetaURI;
10
+ content: BlockDefinition;
11
+ screens: ScreenTemplate[];
12
+ }
13
+ export interface ParsedResult {
14
+ plan: Plan;
15
+ blocks: BlockDefinitionInfo[];
16
+ }
17
+ export interface StormOptions {
18
+ databaseKind: string;
19
+ apiKind: string;
20
+ clientKind: string;
21
+ webPageKind: string;
22
+ webFragmentKind: string;
23
+ mqKind: string;
24
+ serviceKind: string;
25
+ serviceLanguage: string;
26
+ frontendKind: string;
27
+ frontendLanguage: string;
28
+ exchangeKind: string;
29
+ queueKind: string;
30
+ publisherKind: string;
31
+ subscriberKind: string;
32
+ jwtProviderKind: string;
33
+ jwtConsumerKind: string;
34
+ smtpKind: string;
35
+ externalApiKind: string;
36
+ cliKind: string;
37
+ cliLanguage: string;
38
+ desktopKind: string;
39
+ desktopLanguage: string;
40
+ gatewayKind: string;
41
+ }
42
+ export declare function resolveOptions(): Promise<StormOptions>;
43
+ export declare class StormEventParser {
44
+ private events;
45
+ private planName;
46
+ private planDescription;
47
+ private blocks;
48
+ private connections;
49
+ private failed;
50
+ private error;
51
+ private options;
52
+ constructor(options: StormOptions);
53
+ private reset;
54
+ addEvent(evt: StormEvent): void;
55
+ getEvents(): StormEvent[];
56
+ isValid(): boolean;
57
+ getError(): string;
58
+ private applyLayoutToBlocks;
59
+ toResult(handle: string): ParsedResult;
60
+ private toSafeName;
61
+ private toRef;
62
+ toBlockDefinitions(handle: string): {
63
+ [key: string]: BlockDefinitionInfo;
64
+ };
65
+ private toResourceKind;
66
+ private toBlockKind;
67
+ private toPortType;
68
+ private toBlockTarget;
69
+ private toBlockTargetKind;
70
+ }