@kapeta/local-cluster-service 0.30.1 → 0.32.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 (44) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/index.js +2 -0
  3. package/dist/cjs/src/ai/aiClient.d.ts +20 -0
  4. package/dist/cjs/src/ai/aiClient.js +74 -0
  5. package/dist/cjs/src/ai/routes.d.ts +7 -0
  6. package/dist/cjs/src/ai/routes.js +37 -0
  7. package/dist/cjs/src/ai/transform.d.ts +11 -0
  8. package/dist/cjs/src/ai/transform.js +239 -0
  9. package/dist/cjs/src/ai/types.d.ts +40 -0
  10. package/dist/cjs/src/ai/types.js +2 -0
  11. package/dist/cjs/src/codeGeneratorManager.js +4 -3
  12. package/dist/cjs/src/containerManager.js +7 -1
  13. package/dist/cjs/src/filesystem/routes.js +16 -0
  14. package/dist/cjs/src/filesystemManager.d.ts +4 -0
  15. package/dist/cjs/src/filesystemManager.js +14 -0
  16. package/dist/cjs/src/utils/utils.d.ts +1 -0
  17. package/dist/cjs/src/utils/utils.js +7 -1
  18. package/dist/esm/index.js +2 -0
  19. package/dist/esm/src/ai/aiClient.d.ts +20 -0
  20. package/dist/esm/src/ai/aiClient.js +74 -0
  21. package/dist/esm/src/ai/routes.d.ts +7 -0
  22. package/dist/esm/src/ai/routes.js +37 -0
  23. package/dist/esm/src/ai/transform.d.ts +11 -0
  24. package/dist/esm/src/ai/transform.js +239 -0
  25. package/dist/esm/src/ai/types.d.ts +40 -0
  26. package/dist/esm/src/ai/types.js +2 -0
  27. package/dist/esm/src/codeGeneratorManager.js +4 -3
  28. package/dist/esm/src/containerManager.js +7 -1
  29. package/dist/esm/src/filesystem/routes.js +16 -0
  30. package/dist/esm/src/filesystemManager.d.ts +4 -0
  31. package/dist/esm/src/filesystemManager.js +14 -0
  32. package/dist/esm/src/utils/utils.d.ts +1 -0
  33. package/dist/esm/src/utils/utils.js +7 -1
  34. package/index.ts +2 -0
  35. package/package.json +1 -1
  36. package/src/ai/aiClient.ts +93 -0
  37. package/src/ai/routes.ts +39 -0
  38. package/src/ai/transform.ts +275 -0
  39. package/src/ai/types.ts +45 -0
  40. package/src/codeGeneratorManager.ts +6 -4
  41. package/src/containerManager.ts +7 -1
  42. package/src/filesystem/routes.ts +22 -0
  43. package/src/filesystemManager.ts +18 -0
  44. package/src/utils/utils.ts +6 -0
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+
6
+ import Router from 'express-promise-router';
7
+ import { Response } from 'express';
8
+
9
+ import { corsHandler } from '../middleware/cors';
10
+
11
+ import { stringBody, StringBodyRequest } from '../middleware/stringBody';
12
+ import { aiClient, AIRequest } from './aiClient';
13
+ import { KapetaBodyRequest } from '../types';
14
+ import YAML from 'yaml';
15
+
16
+ const router = Router();
17
+
18
+ router.use('/', corsHandler);
19
+ router.use('/', stringBody);
20
+
21
+ router.post('/prompt/:handle', async (req: KapetaBodyRequest, res: Response) => {
22
+ const handle = req.params.handle;
23
+ try {
24
+ const aiRequest: AIRequest = JSON.parse(req.stringBody ?? '{}');
25
+ const result = await aiClient.sendPrompt(handle, aiRequest);
26
+ if (req.accepts('application/yaml')) {
27
+ res.set('Content-Type', 'application/yaml');
28
+ res.send(YAML.stringify(result));
29
+ } else {
30
+ res.json(result);
31
+ }
32
+ } catch (err: any) {
33
+ console.error('Failed to send prompt', err);
34
+ res.status(400).send({ error: err.message });
35
+ return;
36
+ }
37
+ });
38
+
39
+ export default router;
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ import { BlockDefinition, Plan } from '@kapeta/schemas';
6
+ import { Application } from './types';
7
+ import { definitionsManager } from '../definitionsManager';
8
+ import { normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
9
+ import uuid from 'node-uuid';
10
+
11
+ export type PlanContext = {
12
+ plan: Plan;
13
+ blocks: BlockDefinition[];
14
+ };
15
+
16
+ async function getFreeName(name: string) {
17
+ let currentName = name;
18
+ let iteration = 1;
19
+ do {
20
+ const found = await definitionsManager.getLatestDefinition(currentName);
21
+ if (!found) {
22
+ return currentName;
23
+ }
24
+ currentName = name + '_' + iteration++;
25
+ } while (true);
26
+ }
27
+
28
+ export const transformToPlan = async (handle: string, application: Application): Promise<PlanContext> => {
29
+ const blockTypeService = await definitionsManager.getLatestDefinition('kapeta/block-type-service');
30
+ const blockTypeFrontend = await definitionsManager.getLatestDefinition('kapeta/block-type-frontend');
31
+ const mongoDbResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-mongodb');
32
+ const postgresResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-postgresql');
33
+ const webPageResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-web-page');
34
+ const webFragmentResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-web-fragment');
35
+ const restApiResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-rest-api');
36
+ const restClientResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-rest-client');
37
+ const javaLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-java-spring-boot');
38
+ const nodejsLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-nodejs');
39
+ const reactLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-react-ts');
40
+
41
+ if (
42
+ !blockTypeService ||
43
+ !blockTypeFrontend ||
44
+ !mongoDbResource ||
45
+ !postgresResource ||
46
+ !javaLanguage ||
47
+ !nodejsLanguage ||
48
+ !reactLanguage ||
49
+ !webPageResource ||
50
+ !restApiResource ||
51
+ !restClientResource ||
52
+ !webFragmentResource
53
+ ) {
54
+ throw new Error('Missing definitions');
55
+ }
56
+
57
+ const plan: Plan = {
58
+ kind: 'core/plan',
59
+ metadata: {
60
+ name: await getFreeName(`${handle}/${application.name}`),
61
+ title: application.title,
62
+ description: application.description,
63
+ visibility: 'private',
64
+ },
65
+ spec: {
66
+ blocks: [],
67
+ connections: [],
68
+ },
69
+ };
70
+
71
+ const blocks: BlockDefinition[] = [];
72
+
73
+ const addToPlan = (ref: string, name: string) => {
74
+ const top = 100 + Math.floor(plan.spec.blocks.length / 3) * 300;
75
+ const left = 200 + (plan.spec.blocks.length % 3) * 450;
76
+
77
+ plan.spec.blocks.push({
78
+ block: {
79
+ ref,
80
+ },
81
+ name,
82
+ id: uuid.v4(),
83
+ dimensions: {
84
+ top,
85
+ left,
86
+ width: -1,
87
+ height: -1,
88
+ },
89
+ });
90
+ };
91
+
92
+ const nameMapper = new Map<string, string>();
93
+
94
+ for (const backend of application.backends) {
95
+ const blockName = `${handle}/${backend.name}`;
96
+ const blockRealName = await getFreeName(blockName);
97
+ nameMapper.set(blockName, blockRealName);
98
+
99
+ const language = backend.targetLanguage === 'java' ? javaLanguage : nodejsLanguage;
100
+ const databaseInfo = backend.databases?.[0];
101
+ const database = databaseInfo?.type === 'mongodb' ? mongoDbResource : postgresResource;
102
+
103
+ const blockRef = normalizeKapetaUri(blockRealName + ':local');
104
+ let targetOptions = {};
105
+ if (backend.targetLanguage === 'java') {
106
+ targetOptions = {
107
+ basePackage: `${handle}.${application.name}`.toLowerCase().replace(/-/g, '_'),
108
+ groupId: `${handle}.${application.name}`.toLowerCase().replace(/-/g, '_'),
109
+ artifactId: backend.name.toLowerCase().replace(/-/g, '_'),
110
+ };
111
+ }
112
+ blocks.push({
113
+ kind: normalizeKapetaUri(`${blockTypeService.definition.metadata.name}:${blockTypeService.version}`),
114
+ metadata: {
115
+ name: blockRealName,
116
+ title: backend.title,
117
+ description: backend.description,
118
+ visibility: 'private',
119
+ },
120
+ spec: {
121
+ target: {
122
+ kind: normalizeKapetaUri(`${language.definition.metadata.name}:${language.version}`),
123
+ options: targetOptions,
124
+ },
125
+ consumers: [
126
+ {
127
+ kind: normalizeKapetaUri(`${database.definition.metadata.name}:${database.version}`),
128
+ metadata: {
129
+ name: (databaseInfo?.name ?? 'main').replace(/-/g, ''),
130
+ },
131
+ spec: {
132
+ port: {
133
+ type: database.definition.spec.ports[0].type,
134
+ },
135
+ },
136
+ },
137
+ ],
138
+ providers: [
139
+ {
140
+ kind: normalizeKapetaUri(
141
+ `${restApiResource.definition.metadata.name}:${restApiResource.version}`
142
+ ),
143
+ metadata: {
144
+ name: 'main',
145
+ },
146
+ spec: {
147
+ port: {
148
+ type: restApiResource.definition.spec.ports[0].type,
149
+ },
150
+ },
151
+ },
152
+ ],
153
+ },
154
+ });
155
+
156
+ addToPlan(blockRef, backend.name);
157
+ }
158
+
159
+ for (const frontend of application.frontends) {
160
+ const blockName = `${handle}/${frontend.name}`;
161
+ const blockRealName = await getFreeName(blockName);
162
+ nameMapper.set(blockName, blockRealName);
163
+
164
+ const language = reactLanguage;
165
+
166
+ const blockRef = normalizeKapetaUri(blockRealName + ':local');
167
+ blocks.push({
168
+ kind: normalizeKapetaUri(`${blockTypeFrontend.definition.metadata.name}:${blockTypeFrontend.version}`),
169
+ metadata: {
170
+ name: blockRealName,
171
+ title: frontend.title,
172
+ description: frontend.description,
173
+ visibility: 'private',
174
+ },
175
+ spec: {
176
+ target: {
177
+ kind: normalizeKapetaUri(`${language.definition.metadata.name}:${language.version}`),
178
+ options: {},
179
+ },
180
+ consumers: [],
181
+ providers: [
182
+ {
183
+ kind: normalizeKapetaUri(
184
+ `${webPageResource.definition.metadata.name}:${webPageResource.version}`
185
+ ),
186
+ metadata: {
187
+ name: 'main',
188
+ },
189
+ spec: {
190
+ port: {
191
+ type: webPageResource.definition.spec.ports[0].type,
192
+ },
193
+ },
194
+ },
195
+ ],
196
+ },
197
+ });
198
+
199
+ addToPlan(blockRef, frontend.name);
200
+ }
201
+
202
+ application.connections?.forEach((connection) => {
203
+ const fullProviderName = nameMapper.get(`${handle}/${connection.provider.name}`) as string;
204
+ const fullConsumerName = nameMapper.get(`${handle}/${connection.consumer.name}`) as string;
205
+ const consumerResourceName = connection.provider.name;
206
+ const providerRef = normalizeKapetaUri(`${fullProviderName}:local`);
207
+ const consumerRef = normalizeKapetaUri(`${fullConsumerName}:local`);
208
+
209
+ const instanceProvider = plan.spec.blocks.find((b) => b.block.ref === providerRef)!;
210
+ const instanceConsumer = plan.spec.blocks.find((b) => b.block.ref === consumerRef)!;
211
+ const consumerBlock = blocks.find((block) => block.metadata.name === fullConsumerName);
212
+ const providerBlock = blocks.find((block) => block.metadata.name === fullProviderName);
213
+ if (!consumerBlock) {
214
+ throw new Error('Missing consumer block: ' + fullConsumerName);
215
+ }
216
+
217
+ if (!providerBlock) {
218
+ throw new Error('Missing provider block: ' + fullProviderName);
219
+ }
220
+
221
+ const portType = parseKapetaUri(providerBlock.kind).fullName === 'kapeta/block-type-service' ? 'rest' : 'web';
222
+
223
+ if (portType === 'rest') {
224
+ consumerBlock.spec.consumers!.push({
225
+ kind: normalizeKapetaUri(
226
+ `${restClientResource.definition.metadata.name}:${restClientResource.version}`
227
+ ),
228
+ metadata: {
229
+ name: consumerResourceName,
230
+ },
231
+ spec: {
232
+ port: {
233
+ type: 'rest',
234
+ },
235
+ },
236
+ });
237
+ } else {
238
+ consumerBlock.spec.consumers!.push({
239
+ kind: normalizeKapetaUri(
240
+ `${webFragmentResource.definition.metadata.name}:${webFragmentResource.version}`
241
+ ),
242
+ metadata: {
243
+ name: consumerResourceName,
244
+ },
245
+ spec: {
246
+ port: {
247
+ type: 'web',
248
+ },
249
+ },
250
+ });
251
+ }
252
+
253
+ plan.spec.connections.push({
254
+ provider: {
255
+ blockId: instanceProvider.id,
256
+ resourceName: 'main',
257
+ port: {
258
+ type: portType,
259
+ },
260
+ },
261
+ consumer: {
262
+ blockId: instanceConsumer.id,
263
+ resourceName: consumerResourceName,
264
+ port: {
265
+ type: portType,
266
+ },
267
+ },
268
+ });
269
+ });
270
+
271
+ return {
272
+ plan,
273
+ blocks,
274
+ };
275
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ export interface Database {
6
+ name: string;
7
+ type: 'mongodb' | 'postgres';
8
+ }
9
+
10
+ export interface BackendService {
11
+ name: string;
12
+ title: string;
13
+ description: string;
14
+ targetLanguage: 'java' | 'node';
15
+ databases: Database[] | null;
16
+ }
17
+
18
+ export interface FrontendService {
19
+ name: string;
20
+ title: string;
21
+ description: string;
22
+ targetLanguage: 'react';
23
+ }
24
+
25
+ export interface Endpoint {
26
+ type: 'backend' | 'frontend';
27
+ name: string;
28
+ }
29
+
30
+ export interface Connection {
31
+ provider: Endpoint;
32
+ consumer: Endpoint;
33
+ }
34
+
35
+ export interface Application {
36
+ kind: 'core/plan';
37
+ name: string;
38
+ title: string;
39
+ description: string;
40
+ backends: BackendService[];
41
+ frontends: FrontendService[];
42
+ connections: Connection[];
43
+ explanation: string;
44
+ response: string;
45
+ }
@@ -9,7 +9,7 @@ import { BlockDefinition } from '@kapeta/schemas';
9
9
  import { definitionsManager } from './definitionsManager';
10
10
  import { Definition } from '@kapeta/local-cluster-config';
11
11
  import { assetManager } from './assetManager';
12
- import { normalizeKapetaUri } from '@kapeta/nodejs-utils';
12
+ import { normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
13
13
  import { repositoryManager } from './repositoryManager';
14
14
 
15
15
  const TARGET_KIND = 'core/language-target';
@@ -61,16 +61,18 @@ class CodeGeneratorManager {
61
61
  }
62
62
 
63
63
  async canGenerateCode(yamlContent: Definition): Promise<boolean> {
64
- if (!yamlContent.spec.target?.kind) {
64
+ if (!yamlContent.spec?.target?.kind || !yamlContent.kind) {
65
65
  //Not all block types have targets
66
66
  return false;
67
67
  }
68
68
 
69
+ const kindUri = parseKapetaUri(yamlContent.kind);
70
+
69
71
  const blockTypes = await definitionsManager.getDefinitions(BLOCK_TYPE_KIND);
70
72
  const blockTypeKinds = blockTypes.map(
71
- (blockType) => blockType.definition.metadata.name.toLowerCase() + ':' + blockType.version
73
+ (blockType) => parseKapetaUri(blockType.definition.metadata.name).fullName
72
74
  );
73
- return !!(yamlContent && yamlContent.kind && blockTypeKinds.indexOf(yamlContent.kind.toLowerCase()) > -1);
75
+ return blockTypeKinds.includes(kindUri.fullName);
74
76
  }
75
77
 
76
78
  async generate(yamlFile: string, yamlContent: Definition) {
@@ -166,11 +166,17 @@ class ContainerManager {
166
166
  ];
167
167
  for (const opts of connectOptions) {
168
168
  try {
169
+ const testClient = new Docker({
170
+ ...opts,
171
+ timeout: 1000, // 1 secs should be enough for a ping
172
+ });
173
+ await testClient.ping();
174
+ // If we get here - we have a working connection
175
+ // Now create a client with a longer timeout for all other operations
169
176
  const client = new Docker({
170
177
  ...opts,
171
178
  timeout: 15 * 60 * 1000, //15 minutes should be enough for any operation
172
179
  });
173
- await client.ping();
174
180
  this._docker = client;
175
181
  const versionInfo: any = await client.version();
176
182
  this._version = versionInfo.Server?.Version ?? versionInfo.Version;
@@ -50,6 +50,28 @@ router.post('/release-channel', (req: StringBodyRequest, res: Response) => {
50
50
  res.sendStatus(204);
51
51
  });
52
52
 
53
+ router.get('/show-pixel-grid', (req: Request, res: Response) => {
54
+ res.send(filesystemManager.getShowPixelGrid());
55
+ });
56
+
57
+ router.use('/show-pixel-grid', stringBody);
58
+
59
+ router.post('/show-pixel-grid', (req: StringBodyRequest, res: Response) => {
60
+ filesystemManager.setShowPixelGrid(req?.stringBody === 'true' ?? false);
61
+ res.sendStatus(204);
62
+ });
63
+
64
+ router.get('/snap-to-pixel-grid', (req: Request, res: Response) => {
65
+ res.send(filesystemManager.getSnapToPixelGrid());
66
+ });
67
+
68
+ router.use('/snap-to-pixel-grid', stringBody);
69
+
70
+ router.post('/snap-to-pixel-grid', (req: StringBodyRequest, res: Response) => {
71
+ filesystemManager.setSnapToPixelGrid(req?.stringBody === 'true' ?? false);
72
+ res.sendStatus(204);
73
+ });
74
+
53
75
  router.use('/', (req: Request, res: Response, next: NextFunction) => {
54
76
  if (!req.query.path) {
55
77
  res.status(400).send({ error: 'Missing required query parameter "path"' });
@@ -12,6 +12,8 @@ const SECTION_ID = 'filesystem';
12
12
  const PROJECT_ROOT = 'project_root';
13
13
  const EDITOR = 'editor';
14
14
  const RELEASE_CHANNEL = 'release_channel';
15
+ const SHOW_PIXEL_GRID = 'show_pixel_grid';
16
+ const SNAP_TO_PIXEL_GRID = 'snap_to_pixel_grid';
15
17
 
16
18
  function isFile(path: string) {
17
19
  try {
@@ -110,6 +112,22 @@ class FilesystemManager {
110
112
  setReleaseChannel(channel: string) {
111
113
  storageService.put('app', RELEASE_CHANNEL, channel);
112
114
  }
115
+
116
+ getShowPixelGrid() {
117
+ return storageService.get<boolean>('app', SHOW_PIXEL_GRID, false);
118
+ }
119
+
120
+ setShowPixelGrid(show: boolean) {
121
+ storageService.put('app', SHOW_PIXEL_GRID, show);
122
+ }
123
+
124
+ getSnapToPixelGrid() {
125
+ return storageService.get<boolean>('app', SNAP_TO_PIXEL_GRID, false);
126
+ }
127
+
128
+ setSnapToPixelGrid(snap: boolean) {
129
+ storageService.put('app', SNAP_TO_PIXEL_GRID, snap);
130
+ }
113
131
  }
114
132
 
115
133
  export const filesystemManager = new FilesystemManager();
@@ -10,11 +10,17 @@ import md5 from 'md5';
10
10
  import { EntityList } from '@kapeta/schemas';
11
11
  import _ from 'lodash';
12
12
  import { AnyMap } from '../types';
13
+ import ClusterConfiguration from '@kapeta/local-cluster-config';
13
14
 
14
15
  export function getBlockInstanceContainerName(systemId: string, instanceId: string) {
15
16
  return `kapeta-block-instance-${md5(systemId + instanceId)}`;
16
17
  }
17
18
 
19
+ export function getRemoteUrl(id: string, defautValue: string) {
20
+ const remoteConfig = ClusterConfiguration.getClusterConfig().remote;
21
+ return remoteConfig?.[id] ?? defautValue;
22
+ }
23
+
18
24
  export function readYML(path: string) {
19
25
  const rawYaml = FS.readFileSync(path);
20
26