@kapeta/local-cluster-service 0.48.0 → 0.48.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 CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.48.2](https://github.com/kapetacom/local-cluster-service/compare/v0.48.1...v0.48.2) (2024-06-03)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Include resource name for api and model ([#157](https://github.com/kapetacom/local-cluster-service/issues/157)) ([b79a704](https://github.com/kapetacom/local-cluster-service/commit/b79a704b74d11cb0ec0fc8346e29ed754b39fb12))
7
+
8
+ ## [0.48.1](https://github.com/kapetacom/local-cluster-service/compare/v0.48.0...v0.48.1) (2024-06-03)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Enrich events with block refs and instance ids ([#156](https://github.com/kapetacom/local-cluster-service/issues/156)) ([c45d5d7](https://github.com/kapetacom/local-cluster-service/commit/c45d5d776760a51fb786582d74cb4d049e27f0db))
14
+
1
15
  # [0.48.0](https://github.com/kapetacom/local-cluster-service/compare/v0.47.4...v0.48.0) (2024-06-03)
2
16
 
3
17
 
@@ -10,12 +10,14 @@ export declare class StormCodegen {
10
10
  private readonly blocks;
11
11
  private readonly out;
12
12
  private readonly events;
13
+ private readonly tmpDir;
13
14
  constructor(userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
14
15
  process(): Promise<void>;
15
16
  getStream(): StormStream;
16
17
  private handleTemplateFileOutput;
17
18
  private handleUiOutput;
18
19
  private handleFileOutput;
20
+ private getBasePath;
19
21
  /**
20
22
  * Generates the code for a block and sends it to the AI
21
23
  */
@@ -43,10 +43,12 @@ class StormCodegen {
43
43
  blocks;
44
44
  out = new stream_1.StormStream();
45
45
  events;
46
+ tmpDir;
46
47
  constructor(userPrompt, blocks, events) {
47
48
  this.userPrompt = userPrompt;
48
49
  this.blocks = blocks;
49
50
  this.events = events;
51
+ this.tmpDir = node_os_1.default.tmpdir();
50
52
  }
51
53
  async process() {
52
54
  for (const block of this.blocks) {
@@ -95,6 +97,9 @@ class StormCodegen {
95
97
  };
96
98
  }
97
99
  }
100
+ getBasePath(blockName) {
101
+ return path_1.default.join(this.tmpDir, blockName);
102
+ }
98
103
  /**
99
104
  * Generates the code for a block and sends it to the AI
100
105
  */
@@ -129,7 +134,7 @@ class StormCodegen {
129
134
  if (serviceFiles.length > 0) {
130
135
  await this.processTemplates(block.uri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
131
136
  }
132
- const basePath = path_1.default.join(node_os_1.default.tmpdir(), block.content.metadata.name);
137
+ const basePath = this.getBasePath(block.content.metadata.name);
133
138
  for (const serviceFile of serviceFiles) {
134
139
  const filePath = (0, path_1.join)(basePath, serviceFile.filename);
135
140
  await (0, promises_1.writeFile)(filePath, serviceFile.content);
@@ -165,12 +170,14 @@ class StormCodegen {
165
170
  });
166
171
  }
167
172
  emitFile(uri, blockName, filename, content, reason = 'File generated') {
173
+ const basePath = this.getBasePath(uri.fullName);
168
174
  this.out.emit('data', {
169
175
  type: 'FILE',
170
176
  reason,
171
177
  created: Date.now(),
172
178
  payload: {
173
179
  filename: filename,
180
+ path: (0, path_1.join)(basePath, filename),
174
181
  content: content,
175
182
  blockName,
176
183
  blockRef: uri.toNormalizedString(),
@@ -245,7 +252,7 @@ class StormCodegen {
245
252
  if (!(await codeGeneratorManager_1.codeGeneratorManager.ensureTarget(yamlContent.spec.target?.kind))) {
246
253
  return;
247
254
  }
248
- const basePath = path_1.default.join(node_os_1.default.tmpdir(), yamlContent.metadata.name);
255
+ const basePath = this.getBasePath(yamlContent.metadata.name);
249
256
  const codeGenerator = new codegen_1.BlockCodeGenerator(yamlContent);
250
257
  codeGenerator.withOption('AIContext', stormClient_1.STORM_ID);
251
258
  const generatedResult = await codeGenerator.generate();
@@ -51,10 +51,15 @@ export declare class StormEventParser {
51
51
  private options;
52
52
  constructor(options: StormOptions);
53
53
  private reset;
54
- addEvent(handle: string, evt: StormEvent): StormDefinitions;
54
+ /**
55
+ * Builds plan and block definitions - and enriches events with relevant refs and ids
56
+ */
57
+ processEvent(handle: string, evt: StormEvent): StormDefinitions;
55
58
  getEvents(): StormEvent[];
56
59
  isValid(): boolean;
57
60
  getError(): string;
61
+ private toInstanceId;
62
+ private toInstanceIdFromRef;
58
63
  toResult(handle: string): StormDefinitions;
59
64
  private toSafeName;
60
65
  private toRef;
@@ -122,9 +122,13 @@ class StormEventParser {
122
122
  this.blocks = {};
123
123
  this.connections = [];
124
124
  }
125
- addEvent(handle, evt) {
125
+ /**
126
+ * Builds plan and block definitions - and enriches events with relevant refs and ids
127
+ */
128
+ processEvent(handle, evt) {
129
+ let blockInfo;
126
130
  this.events.push(evt);
127
- console.log('evt', evt);
131
+ console.log('Processing event: %s', evt.type);
128
132
  switch (evt.type) {
129
133
  case 'CREATE_PLAN_PROPERTIES':
130
134
  this.planName = evt.payload.name;
@@ -137,6 +141,8 @@ class StormEventParser {
137
141
  models: [],
138
142
  types: [],
139
143
  };
144
+ evt.payload.blockRef = this.toRef(handle, evt.payload.name).toNormalizedString();
145
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
140
146
  break;
141
147
  case 'PLAN_RETRY':
142
148
  this.reset();
@@ -146,15 +152,29 @@ class StormEventParser {
146
152
  this.error = evt.payload.error;
147
153
  break;
148
154
  case 'CREATE_API':
149
- this.blocks[evt.payload.blockName].apis.push(prettifyKaplang(evt.payload.content));
155
+ blockInfo = this.blocks[evt.payload.blockName];
156
+ blockInfo.apis.push(prettifyKaplang(evt.payload.content));
157
+ evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
158
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
159
+ const api = blockInfo.resources.find((r) => r.type == 'API');
160
+ evt.payload.resourceName = api?.name;
161
+ break;
162
+ case 'CREATE_MODEL':
163
+ blockInfo = this.blocks[evt.payload.blockName];
164
+ blockInfo.models.push(prettifyKaplang(evt.payload.content));
165
+ evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
166
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
167
+ const database = blockInfo.resources.find((r) => r.type == 'DATABASE');
168
+ evt.payload.resourceName = database?.name;
150
169
  break;
151
170
  case 'CREATE_TYPE':
152
171
  this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
153
- break;
154
- case 'CREATE_MODEL':
155
- this.blocks[evt.payload.blockName].models.push(prettifyKaplang(evt.payload.content));
172
+ evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
173
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
156
174
  break;
157
175
  case 'CREATE_CONNECTION':
176
+ evt.payload.fromBlockId = this.toInstanceId(handle, evt.payload.fromComponent);
177
+ evt.payload.toBlockId = this.toInstanceId(handle, evt.payload.toComponent);
158
178
  this.connections.push(evt.payload);
159
179
  break;
160
180
  default:
@@ -176,13 +196,20 @@ class StormEventParser {
176
196
  getError() {
177
197
  return this.error;
178
198
  }
199
+ toInstanceId(handle, blockName) {
200
+ const ref = this.toRef(handle, blockName);
201
+ return this.toInstanceIdFromRef(ref.toNormalizedString());
202
+ }
203
+ toInstanceIdFromRef(ref) {
204
+ return (0, uuid_1.v5)((0, nodejs_utils_1.normalizeKapetaUri)(ref), uuid_1.v5.URL);
205
+ }
179
206
  toResult(handle) {
180
207
  const planRef = this.toRef(handle, this.planName ?? 'undefined');
181
208
  const blockDefinitions = this.toBlockDefinitions(handle);
182
209
  const refIdMap = {};
183
210
  const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
184
211
  // Create a deterministic uuid
185
- const id = (0, uuid_1.v5)(ref, uuid_1.v5.URL);
212
+ const id = this.toInstanceIdFromRef(ref);
186
213
  refIdMap[ref] = id;
187
214
  return {
188
215
  id,
@@ -14,6 +14,8 @@ export interface StormBlockInfo {
14
14
  name: string;
15
15
  description: string;
16
16
  }[];
17
+ blockRef?: string;
18
+ instanceId?: string;
17
19
  }
18
20
  export interface StormBlockInfoFilled extends StormBlockInfo {
19
21
  apis: string[];
@@ -28,9 +30,11 @@ export interface StormEventCreateBlock {
28
30
  }
29
31
  export interface StormConnection {
30
32
  fromComponent: string;
33
+ fromBlockId?: string;
31
34
  fromResource: string;
32
35
  fromResourceType: StormResourceType;
33
36
  toComponent: string;
37
+ toBlockId?: string;
34
38
  toResource: string;
35
39
  toResourceType: StormResourceType;
36
40
  }
@@ -66,12 +70,26 @@ export interface StormEventPlanRetry {
66
70
  };
67
71
  }
68
72
  export interface StormEventCreateDSL {
69
- type: 'CREATE_API' | 'CREATE_TYPE' | 'CREATE_MODEL';
73
+ type: 'CREATE_TYPE';
70
74
  reason: string;
71
75
  created: number;
72
76
  payload: {
73
77
  blockName: string;
74
78
  content: string;
79
+ blockRef?: string;
80
+ instanceId?: string;
81
+ };
82
+ }
83
+ export interface StormEventCreateDSLResource extends Omit<StormEventCreateDSL, 'type'> {
84
+ type: 'CREATE_API' | 'CREATE_MODEL';
85
+ reason: string;
86
+ created: number;
87
+ payload: {
88
+ blockName: string;
89
+ content: string;
90
+ blockRef?: string;
91
+ instanceId?: string;
92
+ resourceName?: string;
75
93
  };
76
94
  }
77
95
  export interface StormEventError {
@@ -116,6 +134,7 @@ export interface StormEventFile {
116
134
  created: number;
117
135
  payload: {
118
136
  filename: string;
137
+ path: string;
119
138
  content: string;
120
139
  blockName: string;
121
140
  blockRef: string;
@@ -131,4 +150,4 @@ export interface StormEventDefinitionChange {
131
150
  created: number;
132
151
  payload: StormDefinitions;
133
152
  }
134
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
153
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
@@ -27,12 +27,12 @@ router.post('/:handle/all', async (req, res) => {
27
27
  res.set('Content-Type', 'application/x-ndjson');
28
28
  res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
29
29
  res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
30
- console.log('metaStream.getConversationId()', metaStream.getConversationId());
31
30
  metaStream.on('data', (data) => {
32
- const result = eventParser.addEvent(req.params.handle, data);
31
+ const result = eventParser.processEvent(req.params.handle, data);
32
+ sendEvent(res, data);
33
33
  sendDefinitions(res, result);
34
34
  });
35
- await streamStormPartialResponse(metaStream, res);
35
+ await waitForStormStream(metaStream);
36
36
  if (!eventParser.isValid()) {
37
37
  // We can't continue if the meta stream is invalid
38
38
  sendEvent(res, {
@@ -88,6 +88,16 @@ function sendError(err, res) {
88
88
  res.status(400).send({ error: err.message });
89
89
  }
90
90
  }
91
+ function waitForStormStream(result) {
92
+ return new Promise((resolve, reject) => {
93
+ result.on('error', (err) => {
94
+ reject(err);
95
+ });
96
+ result.on('end', () => {
97
+ resolve();
98
+ });
99
+ });
100
+ }
91
101
  function streamStormPartialResponse(result, res) {
92
102
  return new Promise((resolve, reject) => {
93
103
  result.on('data', (data) => {
@@ -127,7 +127,7 @@ const events = [
127
127
  describe('event-parser', () => {
128
128
  it('it can parse events into a plan and blocks with proper layout', () => {
129
129
  const parser = new event_parser_1.StormEventParser(parserOptions);
130
- events.forEach((event) => parser.addEvent('kapeta', event));
130
+ events.forEach((event) => parser.processEvent('kapeta', event));
131
131
  const result = parser.toResult('kapeta');
132
132
  expect(result.plan.metadata.name).toBe('kapeta/my-plan');
133
133
  expect(result.plan.metadata.description).toBe('my plan description');
@@ -10,12 +10,14 @@ export declare class StormCodegen {
10
10
  private readonly blocks;
11
11
  private readonly out;
12
12
  private readonly events;
13
+ private readonly tmpDir;
13
14
  constructor(userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]);
14
15
  process(): Promise<void>;
15
16
  getStream(): StormStream;
16
17
  private handleTemplateFileOutput;
17
18
  private handleUiOutput;
18
19
  private handleFileOutput;
20
+ private getBasePath;
19
21
  /**
20
22
  * Generates the code for a block and sends it to the AI
21
23
  */
@@ -43,10 +43,12 @@ class StormCodegen {
43
43
  blocks;
44
44
  out = new stream_1.StormStream();
45
45
  events;
46
+ tmpDir;
46
47
  constructor(userPrompt, blocks, events) {
47
48
  this.userPrompt = userPrompt;
48
49
  this.blocks = blocks;
49
50
  this.events = events;
51
+ this.tmpDir = node_os_1.default.tmpdir();
50
52
  }
51
53
  async process() {
52
54
  for (const block of this.blocks) {
@@ -95,6 +97,9 @@ class StormCodegen {
95
97
  };
96
98
  }
97
99
  }
100
+ getBasePath(blockName) {
101
+ return path_1.default.join(this.tmpDir, blockName);
102
+ }
98
103
  /**
99
104
  * Generates the code for a block and sends it to the AI
100
105
  */
@@ -129,7 +134,7 @@ class StormCodegen {
129
134
  if (serviceFiles.length > 0) {
130
135
  await this.processTemplates(block.uri, block.aiName, stormClient_1.stormClient.createServiceImplementation.bind(stormClient_1.stormClient), serviceFiles, contextFiles);
131
136
  }
132
- const basePath = path_1.default.join(node_os_1.default.tmpdir(), block.content.metadata.name);
137
+ const basePath = this.getBasePath(block.content.metadata.name);
133
138
  for (const serviceFile of serviceFiles) {
134
139
  const filePath = (0, path_1.join)(basePath, serviceFile.filename);
135
140
  await (0, promises_1.writeFile)(filePath, serviceFile.content);
@@ -165,12 +170,14 @@ class StormCodegen {
165
170
  });
166
171
  }
167
172
  emitFile(uri, blockName, filename, content, reason = 'File generated') {
173
+ const basePath = this.getBasePath(uri.fullName);
168
174
  this.out.emit('data', {
169
175
  type: 'FILE',
170
176
  reason,
171
177
  created: Date.now(),
172
178
  payload: {
173
179
  filename: filename,
180
+ path: (0, path_1.join)(basePath, filename),
174
181
  content: content,
175
182
  blockName,
176
183
  blockRef: uri.toNormalizedString(),
@@ -245,7 +252,7 @@ class StormCodegen {
245
252
  if (!(await codeGeneratorManager_1.codeGeneratorManager.ensureTarget(yamlContent.spec.target?.kind))) {
246
253
  return;
247
254
  }
248
- const basePath = path_1.default.join(node_os_1.default.tmpdir(), yamlContent.metadata.name);
255
+ const basePath = this.getBasePath(yamlContent.metadata.name);
249
256
  const codeGenerator = new codegen_1.BlockCodeGenerator(yamlContent);
250
257
  codeGenerator.withOption('AIContext', stormClient_1.STORM_ID);
251
258
  const generatedResult = await codeGenerator.generate();
@@ -51,10 +51,15 @@ export declare class StormEventParser {
51
51
  private options;
52
52
  constructor(options: StormOptions);
53
53
  private reset;
54
- addEvent(handle: string, evt: StormEvent): StormDefinitions;
54
+ /**
55
+ * Builds plan and block definitions - and enriches events with relevant refs and ids
56
+ */
57
+ processEvent(handle: string, evt: StormEvent): StormDefinitions;
55
58
  getEvents(): StormEvent[];
56
59
  isValid(): boolean;
57
60
  getError(): string;
61
+ private toInstanceId;
62
+ private toInstanceIdFromRef;
58
63
  toResult(handle: string): StormDefinitions;
59
64
  private toSafeName;
60
65
  private toRef;
@@ -122,9 +122,13 @@ class StormEventParser {
122
122
  this.blocks = {};
123
123
  this.connections = [];
124
124
  }
125
- addEvent(handle, evt) {
125
+ /**
126
+ * Builds plan and block definitions - and enriches events with relevant refs and ids
127
+ */
128
+ processEvent(handle, evt) {
129
+ let blockInfo;
126
130
  this.events.push(evt);
127
- console.log('evt', evt);
131
+ console.log('Processing event: %s', evt.type);
128
132
  switch (evt.type) {
129
133
  case 'CREATE_PLAN_PROPERTIES':
130
134
  this.planName = evt.payload.name;
@@ -137,6 +141,8 @@ class StormEventParser {
137
141
  models: [],
138
142
  types: [],
139
143
  };
144
+ evt.payload.blockRef = this.toRef(handle, evt.payload.name).toNormalizedString();
145
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
140
146
  break;
141
147
  case 'PLAN_RETRY':
142
148
  this.reset();
@@ -146,15 +152,29 @@ class StormEventParser {
146
152
  this.error = evt.payload.error;
147
153
  break;
148
154
  case 'CREATE_API':
149
- this.blocks[evt.payload.blockName].apis.push(prettifyKaplang(evt.payload.content));
155
+ blockInfo = this.blocks[evt.payload.blockName];
156
+ blockInfo.apis.push(prettifyKaplang(evt.payload.content));
157
+ evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
158
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
159
+ const api = blockInfo.resources.find((r) => r.type == 'API');
160
+ evt.payload.resourceName = api?.name;
161
+ break;
162
+ case 'CREATE_MODEL':
163
+ blockInfo = this.blocks[evt.payload.blockName];
164
+ blockInfo.models.push(prettifyKaplang(evt.payload.content));
165
+ evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
166
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
167
+ const database = blockInfo.resources.find((r) => r.type == 'DATABASE');
168
+ evt.payload.resourceName = database?.name;
150
169
  break;
151
170
  case 'CREATE_TYPE':
152
171
  this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
153
- break;
154
- case 'CREATE_MODEL':
155
- this.blocks[evt.payload.blockName].models.push(prettifyKaplang(evt.payload.content));
172
+ evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
173
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
156
174
  break;
157
175
  case 'CREATE_CONNECTION':
176
+ evt.payload.fromBlockId = this.toInstanceId(handle, evt.payload.fromComponent);
177
+ evt.payload.toBlockId = this.toInstanceId(handle, evt.payload.toComponent);
158
178
  this.connections.push(evt.payload);
159
179
  break;
160
180
  default:
@@ -176,13 +196,20 @@ class StormEventParser {
176
196
  getError() {
177
197
  return this.error;
178
198
  }
199
+ toInstanceId(handle, blockName) {
200
+ const ref = this.toRef(handle, blockName);
201
+ return this.toInstanceIdFromRef(ref.toNormalizedString());
202
+ }
203
+ toInstanceIdFromRef(ref) {
204
+ return (0, uuid_1.v5)((0, nodejs_utils_1.normalizeKapetaUri)(ref), uuid_1.v5.URL);
205
+ }
179
206
  toResult(handle) {
180
207
  const planRef = this.toRef(handle, this.planName ?? 'undefined');
181
208
  const blockDefinitions = this.toBlockDefinitions(handle);
182
209
  const refIdMap = {};
183
210
  const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
184
211
  // Create a deterministic uuid
185
- const id = (0, uuid_1.v5)(ref, uuid_1.v5.URL);
212
+ const id = this.toInstanceIdFromRef(ref);
186
213
  refIdMap[ref] = id;
187
214
  return {
188
215
  id,
@@ -14,6 +14,8 @@ export interface StormBlockInfo {
14
14
  name: string;
15
15
  description: string;
16
16
  }[];
17
+ blockRef?: string;
18
+ instanceId?: string;
17
19
  }
18
20
  export interface StormBlockInfoFilled extends StormBlockInfo {
19
21
  apis: string[];
@@ -28,9 +30,11 @@ export interface StormEventCreateBlock {
28
30
  }
29
31
  export interface StormConnection {
30
32
  fromComponent: string;
33
+ fromBlockId?: string;
31
34
  fromResource: string;
32
35
  fromResourceType: StormResourceType;
33
36
  toComponent: string;
37
+ toBlockId?: string;
34
38
  toResource: string;
35
39
  toResourceType: StormResourceType;
36
40
  }
@@ -66,12 +70,26 @@ export interface StormEventPlanRetry {
66
70
  };
67
71
  }
68
72
  export interface StormEventCreateDSL {
69
- type: 'CREATE_API' | 'CREATE_TYPE' | 'CREATE_MODEL';
73
+ type: 'CREATE_TYPE';
70
74
  reason: string;
71
75
  created: number;
72
76
  payload: {
73
77
  blockName: string;
74
78
  content: string;
79
+ blockRef?: string;
80
+ instanceId?: string;
81
+ };
82
+ }
83
+ export interface StormEventCreateDSLResource extends Omit<StormEventCreateDSL, 'type'> {
84
+ type: 'CREATE_API' | 'CREATE_MODEL';
85
+ reason: string;
86
+ created: number;
87
+ payload: {
88
+ blockName: string;
89
+ content: string;
90
+ blockRef?: string;
91
+ instanceId?: string;
92
+ resourceName?: string;
75
93
  };
76
94
  }
77
95
  export interface StormEventError {
@@ -116,6 +134,7 @@ export interface StormEventFile {
116
134
  created: number;
117
135
  payload: {
118
136
  filename: string;
137
+ path: string;
119
138
  content: string;
120
139
  blockName: string;
121
140
  blockRef: string;
@@ -131,4 +150,4 @@ export interface StormEventDefinitionChange {
131
150
  created: number;
132
151
  payload: StormDefinitions;
133
152
  }
134
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
153
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFile | StormEventDone | StormEventDefinitionChange;
@@ -27,12 +27,12 @@ router.post('/:handle/all', async (req, res) => {
27
27
  res.set('Content-Type', 'application/x-ndjson');
28
28
  res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
29
29
  res.set(stormClient_1.ConversationIdHeader, metaStream.getConversationId());
30
- console.log('metaStream.getConversationId()', metaStream.getConversationId());
31
30
  metaStream.on('data', (data) => {
32
- const result = eventParser.addEvent(req.params.handle, data);
31
+ const result = eventParser.processEvent(req.params.handle, data);
32
+ sendEvent(res, data);
33
33
  sendDefinitions(res, result);
34
34
  });
35
- await streamStormPartialResponse(metaStream, res);
35
+ await waitForStormStream(metaStream);
36
36
  if (!eventParser.isValid()) {
37
37
  // We can't continue if the meta stream is invalid
38
38
  sendEvent(res, {
@@ -88,6 +88,16 @@ function sendError(err, res) {
88
88
  res.status(400).send({ error: err.message });
89
89
  }
90
90
  }
91
+ function waitForStormStream(result) {
92
+ return new Promise((resolve, reject) => {
93
+ result.on('error', (err) => {
94
+ reject(err);
95
+ });
96
+ result.on('end', () => {
97
+ resolve();
98
+ });
99
+ });
100
+ }
91
101
  function streamStormPartialResponse(result, res) {
92
102
  return new Promise((resolve, reject) => {
93
103
  result.on('data', (data) => {
@@ -127,7 +127,7 @@ const events = [
127
127
  describe('event-parser', () => {
128
128
  it('it can parse events into a plan and blocks with proper layout', () => {
129
129
  const parser = new event_parser_1.StormEventParser(parserOptions);
130
- events.forEach((event) => parser.addEvent('kapeta', event));
130
+ events.forEach((event) => parser.processEvent('kapeta', event));
131
131
  const result = parser.toResult('kapeta');
132
132
  expect(result.plan.metadata.name).toBe('kapeta/my-plan');
133
133
  expect(result.plan.metadata.description).toBe('my plan description');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.48.0",
3
+ "version": "0.48.2",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -23,11 +23,13 @@ export class StormCodegen {
23
23
  private readonly blocks: BlockDefinitionInfo[];
24
24
  private readonly out = new StormStream();
25
25
  private readonly events: StormEvent[];
26
+ private readonly tmpDir: string;
26
27
 
27
28
  constructor(userPrompt: string, blocks: BlockDefinitionInfo[], events: StormEvent[]) {
28
29
  this.userPrompt = userPrompt;
29
30
  this.blocks = blocks;
30
31
  this.events = events;
32
+ this.tmpDir = os.tmpdir();
31
33
  }
32
34
 
33
35
  public async process() {
@@ -83,6 +85,10 @@ export class StormCodegen {
83
85
  }
84
86
  }
85
87
 
88
+ private getBasePath(blockName: string) {
89
+ return path.join(this.tmpDir, blockName);
90
+ }
91
+
86
92
  /**
87
93
  * Generates the code for a block and sends it to the AI
88
94
  */
@@ -135,8 +141,8 @@ export class StormCodegen {
135
141
  );
136
142
  }
137
143
 
138
- const basePath = path.join(os.tmpdir(), block.content.metadata.name);
139
-
144
+ const basePath = this.getBasePath(block.content.metadata.name);
145
+
140
146
  for (const serviceFile of serviceFiles) {
141
147
  const filePath = join(basePath, serviceFile.filename);
142
148
  await writeFile(filePath, serviceFile.content);
@@ -147,7 +153,7 @@ export class StormCodegen {
147
153
  await writeFile(filePath, serviceFile.content);
148
154
  }
149
155
 
150
- for(const uiFile of uiTemplates){
156
+ for (const uiFile of uiTemplates) {
151
157
  const filePath = join(basePath, uiFile.filename);
152
158
  await writeFile(filePath, uiFile.content);
153
159
  }
@@ -185,12 +191,15 @@ export class StormCodegen {
185
191
  content: string,
186
192
  reason: string = 'File generated'
187
193
  ) {
194
+ const basePath = this.getBasePath(uri.fullName);
195
+
188
196
  this.out.emit('data', {
189
197
  type: 'FILE',
190
198
  reason,
191
199
  created: Date.now(),
192
200
  payload: {
193
201
  filename: filename,
202
+ path: join(basePath, filename),
194
203
  content: content,
195
204
  blockName,
196
205
  blockRef: uri.toNormalizedString(),
@@ -283,7 +292,7 @@ export class StormCodegen {
283
292
  if (!(await codeGeneratorManager.ensureTarget(yamlContent.spec.target?.kind))) {
284
293
  return;
285
294
  }
286
- const basePath = path.join(os.tmpdir(), yamlContent.metadata.name);
295
+ const basePath = this.getBasePath(yamlContent.metadata.name);
287
296
 
288
297
  const codeGenerator = new BlockCodeGenerator(yamlContent as BlockDefinition);
289
298
  codeGenerator.withOption('AIContext', STORM_ID);
@@ -221,9 +221,13 @@ export class StormEventParser {
221
221
  this.connections = [];
222
222
  }
223
223
 
224
- public addEvent(handle: string, evt: StormEvent): StormDefinitions {
224
+ /**
225
+ * Builds plan and block definitions - and enriches events with relevant refs and ids
226
+ */
227
+ public processEvent(handle: string, evt: StormEvent): StormDefinitions {
228
+ let blockInfo;
225
229
  this.events.push(evt);
226
- console.log('evt', evt);
230
+ console.log('Processing event: %s', evt.type);
227
231
  switch (evt.type) {
228
232
  case 'CREATE_PLAN_PROPERTIES':
229
233
  this.planName = evt.payload.name;
@@ -236,6 +240,8 @@ export class StormEventParser {
236
240
  models: [],
237
241
  types: [],
238
242
  };
243
+ evt.payload.blockRef = this.toRef(handle, evt.payload.name).toNormalizedString();
244
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
239
245
  break;
240
246
  case 'PLAN_RETRY':
241
247
  this.reset();
@@ -245,15 +251,32 @@ export class StormEventParser {
245
251
  this.error = evt.payload.error;
246
252
  break;
247
253
  case 'CREATE_API':
248
- this.blocks[evt.payload.blockName].apis.push(prettifyKaplang(evt.payload.content));
254
+ blockInfo = this.blocks[evt.payload.blockName];
255
+ blockInfo.apis.push(prettifyKaplang(evt.payload.content));
256
+ evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
257
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
258
+
259
+ const api = blockInfo.resources.find((r) => r.type == 'API');
260
+ evt.payload.resourceName = api?.name;
261
+ break;
262
+ case 'CREATE_MODEL':
263
+ blockInfo = this.blocks[evt.payload.blockName];
264
+ blockInfo.models.push(prettifyKaplang(evt.payload.content));
265
+ evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
266
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
267
+
268
+ const database = blockInfo.resources.find((r) => r.type == 'DATABASE');
269
+ evt.payload.resourceName = database?.name;
249
270
  break;
271
+
250
272
  case 'CREATE_TYPE':
251
273
  this.blocks[evt.payload.blockName].types.push(prettifyKaplang(evt.payload.content));
252
- break;
253
- case 'CREATE_MODEL':
254
- this.blocks[evt.payload.blockName].models.push(prettifyKaplang(evt.payload.content));
274
+ evt.payload.blockRef = this.toRef(handle, evt.payload.blockName).toNormalizedString();
275
+ evt.payload.instanceId = this.toInstanceIdFromRef(evt.payload.blockRef);
255
276
  break;
256
277
  case 'CREATE_CONNECTION':
278
+ evt.payload.fromBlockId = this.toInstanceId(handle, evt.payload.fromComponent);
279
+ evt.payload.toBlockId = this.toInstanceId(handle, evt.payload.toComponent);
257
280
  this.connections.push(evt.payload);
258
281
  break;
259
282
 
@@ -281,13 +304,22 @@ export class StormEventParser {
281
304
  return this.error;
282
305
  }
283
306
 
307
+ private toInstanceId(handle: string, blockName: string) {
308
+ const ref = this.toRef(handle, blockName);
309
+ return this.toInstanceIdFromRef(ref.toNormalizedString());
310
+ }
311
+
312
+ private toInstanceIdFromRef(ref: string) {
313
+ return uuid(normalizeKapetaUri(ref), uuid.URL);
314
+ }
315
+
284
316
  public toResult(handle: string): StormDefinitions {
285
317
  const planRef = this.toRef(handle, this.planName ?? 'undefined');
286
318
  const blockDefinitions = this.toBlockDefinitions(handle);
287
319
  const refIdMap: { [key: string]: string } = {};
288
320
  const blocks = Object.entries(blockDefinitions).map(([ref, block]) => {
289
321
  // Create a deterministic uuid
290
- const id = uuid(ref, uuid.URL);
322
+ const id = this.toInstanceIdFromRef(ref);
291
323
  refIdMap[ref] = id;
292
324
  return {
293
325
  id,
@@ -30,6 +30,8 @@ export interface StormBlockInfo {
30
30
  name: string;
31
31
  description: string;
32
32
  }[];
33
+ blockRef?: string;
34
+ instanceId?: string;
33
35
  }
34
36
 
35
37
  export interface StormBlockInfoFilled extends StormBlockInfo {
@@ -47,9 +49,11 @@ export interface StormEventCreateBlock {
47
49
 
48
50
  export interface StormConnection {
49
51
  fromComponent: string;
52
+ fromBlockId?: string;
50
53
  fromResource: string;
51
54
  fromResourceType: StormResourceType;
52
55
  toComponent: string;
56
+ toBlockId?: string;
53
57
  toResource: string;
54
58
  toResourceType: StormResourceType;
55
59
  }
@@ -90,12 +94,27 @@ export interface StormEventPlanRetry {
90
94
  }
91
95
 
92
96
  export interface StormEventCreateDSL {
93
- type: 'CREATE_API' | 'CREATE_TYPE' | 'CREATE_MODEL';
97
+ type: 'CREATE_TYPE';
94
98
  reason: string;
95
99
  created: number;
96
100
  payload: {
97
101
  blockName: string;
98
102
  content: string;
103
+ blockRef?: string;
104
+ instanceId?: string;
105
+ };
106
+ }
107
+
108
+ export interface StormEventCreateDSLResource extends Omit<StormEventCreateDSL, 'type'> {
109
+ type: 'CREATE_API' | 'CREATE_MODEL';
110
+ reason: string;
111
+ created: number;
112
+ payload: {
113
+ blockName: string;
114
+ content: string;
115
+ blockRef?: string;
116
+ instanceId?: string;
117
+ resourceName?: string;
99
118
  };
100
119
  }
101
120
 
@@ -145,6 +164,7 @@ export interface StormEventFile {
145
164
  created: number;
146
165
  payload: {
147
166
  filename: string;
167
+ path: string;
148
168
  content: string;
149
169
  blockName: string;
150
170
  blockRef: string;
@@ -170,6 +190,7 @@ export type StormEvent =
170
190
  | StormEventInvalidResponse
171
191
  | StormEventPlanRetry
172
192
  | StormEventCreateDSL
193
+ | StormEventCreateDSLResource
173
194
  | StormEventError
174
195
  | StormEventScreen
175
196
  | StormEventScreenCandidate
@@ -35,15 +35,15 @@ router.post('/:handle/all', async (req: KapetaBodyRequest, res: Response) => {
35
35
  res.set('Content-Type', 'application/x-ndjson');
36
36
  res.set('Access-Control-Expose-Headers', ConversationIdHeader);
37
37
  res.set(ConversationIdHeader, metaStream.getConversationId());
38
- console.log('metaStream.getConversationId()', metaStream.getConversationId());
39
38
 
40
39
  metaStream.on('data', (data: StormEvent) => {
41
- const result = eventParser.addEvent(req.params.handle, data);
40
+ const result = eventParser.processEvent(req.params.handle, data);
42
41
 
42
+ sendEvent(res, data);
43
43
  sendDefinitions(res, result);
44
44
  });
45
45
 
46
- await streamStormPartialResponse(metaStream, res);
46
+ await waitForStormStream(metaStream);
47
47
 
48
48
  if (!eventParser.isValid()) {
49
49
  // We can't continue if the meta stream is invalid
@@ -109,6 +109,17 @@ function sendError(err: Error, res: Response) {
109
109
  res.status(400).send({ error: err.message });
110
110
  }
111
111
  }
112
+ function waitForStormStream(result: StormStream) {
113
+ return new Promise<void>((resolve, reject) => {
114
+ result.on('error', (err) => {
115
+ reject(err);
116
+ });
117
+
118
+ result.on('end', () => {
119
+ resolve();
120
+ });
121
+ });
122
+ }
112
123
 
113
124
  function streamStormPartialResponse(result: StormStream, res: Response) {
114
125
  return new Promise<void>((resolve, reject) => {
@@ -139,7 +139,7 @@ const events: StormEvent[] = [
139
139
  describe('event-parser', () => {
140
140
  it('it can parse events into a plan and blocks with proper layout', () => {
141
141
  const parser = new StormEventParser(parserOptions);
142
- events.forEach((event) => parser.addEvent('kapeta', event));
142
+ events.forEach((event) => parser.processEvent('kapeta', event));
143
143
 
144
144
  const result = parser.toResult('kapeta');
145
145