@kapeta/local-cluster-service 0.62.2 → 0.63.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.63.1](https://github.com/kapetacom/local-cluster-service/compare/v0.63.0...v0.63.1) (2024-08-20)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * handle port conflict and hanging response on close ([#219](https://github.com/kapetacom/local-cluster-service/issues/219)) ([e4f3b26](https://github.com/kapetacom/local-cluster-service/commit/e4f3b268fec9958aa315a186a306d2350354452b))
7
+
8
+ # [0.63.0](https://github.com/kapetacom/local-cluster-service/compare/v0.62.2...v0.63.0) (2024-08-15)
9
+
10
+
11
+ ### Features
12
+
13
+ * Add reset endpoint for resetting localStorage in UI server ([#218](https://github.com/kapetacom/local-cluster-service/issues/218)) ([1dd1edc](https://github.com/kapetacom/local-cluster-service/commit/1dd1edcba982f40b853569fd408e6ed561495686))
14
+
1
15
  ## [0.62.2](https://github.com/kapetacom/local-cluster-service/compare/v0.62.1...v0.62.2) (2024-08-14)
2
16
 
3
17
 
@@ -1,4 +1,11 @@
1
+ /// <reference types="node" />
2
+ import { Server } from 'http';
1
3
  import { StormEventPage } from './events';
4
+ declare module 'express-serve-static-core' {
5
+ interface Application {
6
+ listen(port: number, callback?: (err?: Error) => void): Server;
7
+ }
8
+ }
2
9
  export declare class UIServer {
3
10
  private readonly express;
4
11
  private readonly systemId;
@@ -8,4 +15,5 @@ export declare class UIServer {
8
15
  start(): Promise<void>;
9
16
  close(): void;
10
17
  resolveUrl(screenData: StormEventPage): string;
18
+ resolveUrlFromPath(path: string): string;
11
19
  }
@@ -22,11 +22,23 @@ class UIServer {
22
22
  }
23
23
  async start() {
24
24
  this.port = await clusterService_1.clusterService.getNextAvailablePort(this.port);
25
- this.express.all('/*', async (req, res) => {
25
+ this.express.get('/_reset', (req, res) => {
26
+ res.send(`
27
+ <script>
28
+ window.localStorage.clear();
29
+ window.sessionStorage.clear();
30
+ </script>`);
31
+ });
32
+ this.express.all('/*', (req, res) => {
26
33
  (0, page_utils_1.readPageFromDisk)(this.systemId, req.params[0], req.method, res);
27
34
  });
28
- return new Promise((resolve) => {
29
- this.server = this.express.listen(this.port, () => {
35
+ return new Promise((resolve, reject) => {
36
+ this.server = this.express.listen(this.port, (err) => {
37
+ if (err) {
38
+ console.error('Failed to start UI server', err);
39
+ reject(err);
40
+ return;
41
+ }
30
42
  console.log(`UI Server started on port ${this.port}`);
31
43
  resolve();
32
44
  });
@@ -40,8 +52,11 @@ class UIServer {
40
52
  }
41
53
  }
42
54
  resolveUrl(screenData) {
43
- const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
44
- return `http://localhost:${this.port}${path}`;
55
+ return this.resolveUrlFromPath(screenData.payload.path);
56
+ }
57
+ resolveUrlFromPath(path) {
58
+ const resolvedPath = path.startsWith('/') ? path : `/${path}`;
59
+ return `http://localhost:${this.port}${resolvedPath}`;
45
60
  }
46
61
  }
47
62
  exports.UIServer = UIServer;
@@ -375,5 +375,14 @@ export interface StormEventReferenceClassification {
375
375
  created: number;
376
376
  payload: ReferenceClassification;
377
377
  }
378
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase;
378
+ export interface StormEventUIStarted {
379
+ type: 'UI_SERVER_STARTED';
380
+ reason: string;
381
+ created: number;
382
+ payload: {
383
+ conversationId: string;
384
+ resetUrl: string;
385
+ };
386
+ }
387
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted;
379
388
  export {};
@@ -100,6 +100,7 @@ router.delete('/:handle/ui', async (req, res) => {
100
100
  server.close();
101
101
  delete UI_SERVERS[conversationId];
102
102
  }
103
+ res.status(200).json({ status: 'ok' });
103
104
  });
104
105
  router.post('/:handle/ui/iterative', async (req, res) => {
105
106
  const handle = req.params.handle;
@@ -247,6 +248,15 @@ router.post('/:handle/ui', async (req, res) => {
247
248
  await waitForStormStream(shellsStream);
248
249
  UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
249
250
  await UI_SERVERS[outerConversationId].start();
251
+ sendEvent(res, {
252
+ type: 'UI_SERVER_STARTED',
253
+ reason: '',
254
+ payload: {
255
+ conversationId: outerConversationId,
256
+ resetUrl: UI_SERVERS[outerConversationId].resolveUrlFromPath('/_reset'),
257
+ },
258
+ created: Date.now(),
259
+ });
250
260
  // Get the pages (5 at a time)
251
261
  const pagePromises = [];
252
262
  onRequestAborted(req, res, () => {
@@ -1,4 +1,11 @@
1
+ /// <reference types="node" />
2
+ import { Server } from 'http';
1
3
  import { StormEventPage } from './events';
4
+ declare module 'express-serve-static-core' {
5
+ interface Application {
6
+ listen(port: number, callback?: (err?: Error) => void): Server;
7
+ }
8
+ }
2
9
  export declare class UIServer {
3
10
  private readonly express;
4
11
  private readonly systemId;
@@ -8,4 +15,5 @@ export declare class UIServer {
8
15
  start(): Promise<void>;
9
16
  close(): void;
10
17
  resolveUrl(screenData: StormEventPage): string;
18
+ resolveUrlFromPath(path: string): string;
11
19
  }
@@ -22,11 +22,23 @@ class UIServer {
22
22
  }
23
23
  async start() {
24
24
  this.port = await clusterService_1.clusterService.getNextAvailablePort(this.port);
25
- this.express.all('/*', async (req, res) => {
25
+ this.express.get('/_reset', (req, res) => {
26
+ res.send(`
27
+ <script>
28
+ window.localStorage.clear();
29
+ window.sessionStorage.clear();
30
+ </script>`);
31
+ });
32
+ this.express.all('/*', (req, res) => {
26
33
  (0, page_utils_1.readPageFromDisk)(this.systemId, req.params[0], req.method, res);
27
34
  });
28
- return new Promise((resolve) => {
29
- this.server = this.express.listen(this.port, () => {
35
+ return new Promise((resolve, reject) => {
36
+ this.server = this.express.listen(this.port, (err) => {
37
+ if (err) {
38
+ console.error('Failed to start UI server', err);
39
+ reject(err);
40
+ return;
41
+ }
30
42
  console.log(`UI Server started on port ${this.port}`);
31
43
  resolve();
32
44
  });
@@ -40,8 +52,11 @@ class UIServer {
40
52
  }
41
53
  }
42
54
  resolveUrl(screenData) {
43
- const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
44
- return `http://localhost:${this.port}${path}`;
55
+ return this.resolveUrlFromPath(screenData.payload.path);
56
+ }
57
+ resolveUrlFromPath(path) {
58
+ const resolvedPath = path.startsWith('/') ? path : `/${path}`;
59
+ return `http://localhost:${this.port}${resolvedPath}`;
45
60
  }
46
61
  }
47
62
  exports.UIServer = UIServer;
@@ -375,5 +375,14 @@ export interface StormEventReferenceClassification {
375
375
  created: number;
376
376
  payload: ReferenceClassification;
377
377
  }
378
- export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase;
378
+ export interface StormEventUIStarted {
379
+ type: 'UI_SERVER_STARTED';
380
+ reason: string;
381
+ created: number;
382
+ payload: {
383
+ conversationId: string;
384
+ resetUrl: string;
385
+ };
386
+ }
387
+ export type StormEvent = StormEventCreateBlock | StormEventCreateConnection | StormEventCreatePlanProperties | StormEventInvalidResponse | StormEventPlanRetry | StormEventCreateDSL | StormEventCreateDSLResource | StormEventError | StormEventScreen | StormEventScreenCandidate | StormEventFileLogical | StormEventFileState | StormEventFileDone | StormEventFileFailed | StormEventFileChunk | StormEventDone | StormEventDefinitionChange | StormEventErrorClassifier | StormEventCodeFix | StormEventErrorDetails | StormEventBlockReady | StormEventPhases | StormEventBlockStatus | StormEventCreateDSLRetry | StormEventUserJourney | StormEventUIShell | StormEventPage | StormEventPageUrl | StormEventPromptImprove | StormEventLandingPage | StormEventReferenceClassification | StormEventApiBase | StormEventUIStarted;
379
388
  export {};
@@ -100,6 +100,7 @@ router.delete('/:handle/ui', async (req, res) => {
100
100
  server.close();
101
101
  delete UI_SERVERS[conversationId];
102
102
  }
103
+ res.status(200).json({ status: 'ok' });
103
104
  });
104
105
  router.post('/:handle/ui/iterative', async (req, res) => {
105
106
  const handle = req.params.handle;
@@ -247,6 +248,15 @@ router.post('/:handle/ui', async (req, res) => {
247
248
  await waitForStormStream(shellsStream);
248
249
  UI_SERVERS[outerConversationId] = new UIServer_1.UIServer(outerConversationId);
249
250
  await UI_SERVERS[outerConversationId].start();
251
+ sendEvent(res, {
252
+ type: 'UI_SERVER_STARTED',
253
+ reason: '',
254
+ payload: {
255
+ conversationId: outerConversationId,
256
+ resetUrl: UI_SERVERS[outerConversationId].resolveUrlFromPath('/_reset'),
257
+ },
258
+ created: Date.now(),
259
+ });
250
260
  // Get the pages (5 at a time)
251
261
  const pagePromises = [];
252
262
  onRequestAborted(req, res, () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.62.2",
3
+ "version": "0.63.1",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -8,6 +8,15 @@ import { clusterService } from '../clusterService';
8
8
  import { Server } from 'http';
9
9
  import { StormEventPage } from './events';
10
10
 
11
+ declare module 'express-serve-static-core' {
12
+ interface Application {
13
+ // Adds error callback support
14
+ // From the docs:
15
+ // All the forms of Node’s http.Server.listen() method are in fact actually supported.
16
+ listen(port: number, callback?: (err?: Error) => void): Server;
17
+ }
18
+ }
19
+
11
20
  export class UIServer {
12
21
  private readonly express: Express;
13
22
  private readonly systemId: string;
@@ -23,12 +32,27 @@ export class UIServer {
23
32
  public async start() {
24
33
  this.port = await clusterService.getNextAvailablePort(this.port);
25
34
 
26
- this.express.all('/*', async (req: Request, res: Response) => {
35
+ this.express.get('/_reset', (req: Request, res: Response) => {
36
+ res.send(
37
+ `
38
+ <script>
39
+ window.localStorage.clear();
40
+ window.sessionStorage.clear();
41
+ </script>`
42
+ );
43
+ });
44
+
45
+ this.express.all('/*', (req: Request, res: Response) => {
27
46
  readPageFromDisk(this.systemId, req.params[0], req.method, res);
28
47
  });
29
48
 
30
- return new Promise<void>((resolve) => {
31
- this.server = this.express.listen(this.port, () => {
49
+ return new Promise<void>((resolve, reject) => {
50
+ this.server = this.express.listen(this.port, (err) => {
51
+ if (err) {
52
+ console.error('Failed to start UI server', err);
53
+ reject(err);
54
+ return;
55
+ }
32
56
  console.log(`UI Server started on port ${this.port}`);
33
57
  resolve();
34
58
  });
@@ -44,7 +68,11 @@ export class UIServer {
44
68
  }
45
69
 
46
70
  resolveUrl(screenData: StormEventPage) {
47
- const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
48
- return `http://localhost:${this.port}${path}`;
71
+ return this.resolveUrlFromPath(screenData.payload.path);
72
+ }
73
+
74
+ resolveUrlFromPath(path: string) {
75
+ const resolvedPath = path.startsWith('/') ? path : `/${path}`;
76
+ return `http://localhost:${this.port}${resolvedPath}`;
49
77
  }
50
78
  }
@@ -264,7 +264,13 @@ export interface StormEventFileChunk extends StormEventFileBase {
264
264
  }
265
265
 
266
266
  export interface StormEventApiBase {
267
- type: 'API_STREAM_CHUNK' | 'API_STREAM_DONE' | 'API_STREAM_FAILED' | 'API_STREAM_STATE' | 'API_STREAM_START' | 'API_STREAM_CHUNK_RESET';
267
+ type:
268
+ | 'API_STREAM_CHUNK'
269
+ | 'API_STREAM_DONE'
270
+ | 'API_STREAM_FAILED'
271
+ | 'API_STREAM_STATE'
272
+ | 'API_STREAM_START'
273
+ | 'API_STREAM_CHUNK_RESET';
268
274
  payload: StormEventFileBasePayload;
269
275
  }
270
276
 
@@ -445,6 +451,16 @@ export interface StormEventReferenceClassification {
445
451
  payload: ReferenceClassification;
446
452
  }
447
453
 
454
+ export interface StormEventUIStarted {
455
+ type: 'UI_SERVER_STARTED';
456
+ reason: string;
457
+ created: number;
458
+ payload: {
459
+ conversationId: string;
460
+ resetUrl: string;
461
+ };
462
+ }
463
+
448
464
  export type StormEvent =
449
465
  | StormEventCreateBlock
450
466
  | StormEventCreateConnection
@@ -477,4 +493,5 @@ export type StormEvent =
477
493
  | StormEventPromptImprove
478
494
  | StormEventLandingPage
479
495
  | StormEventReferenceClassification
480
- | StormEventApiBase;
496
+ | StormEventApiBase
497
+ | StormEventUIStarted;
@@ -13,15 +13,8 @@ import { stringBody } from '../middleware/stringBody';
13
13
  import { KapetaBodyRequest } from '../types';
14
14
  import { StormCodegenRequest, StormContextRequest, StormCreateBlockRequest, StormStream } from './stream';
15
15
 
16
- import {
17
- ConversationIdHeader,
18
- stormClient,
19
- UIPagePrompt,
20
- UIPageEditPrompt,
21
- UIPageEditRequest,
22
- UIPageSamplePrompt,
23
- } from './stormClient';
24
- import { Page, StormEvent, StormEventPage, StormEventPhaseType, UIShell, UserJourneyScreen } from './events';
16
+ import { ConversationIdHeader, stormClient, UIPagePrompt, UIPageEditPrompt, UIPageEditRequest } from './stormClient';
17
+ import { Page, StormEvent, StormEventPage, StormEventPhaseType, UserJourneyScreen } from './events';
25
18
 
26
19
  import {
27
20
  createPhaseEndEvent,
@@ -33,18 +26,9 @@ import {
33
26
  import { StormCodegen } from './codegen';
34
27
  import { assetManager } from '../assetManager';
35
28
  import uuid from 'node-uuid';
36
- import { PromiseQueue } from './PromiseQueue';
37
- import {
38
- readConversationFromFile,
39
- readPageFromDisk,
40
- readPageFromDiskAsString,
41
- SystemIdHeader,
42
- writeConversationToFile,
43
- writePageToDisk,
44
- } from './page-utils';
29
+ import { readPageFromDisk, readPageFromDiskAsString, SystemIdHeader, writePageToDisk } from './page-utils';
45
30
  import { UIServer } from './UIServer';
46
31
  import { PageQueue } from './PageGenerator';
47
- import FSExtra from 'fs-extra';
48
32
 
49
33
  const UI_SERVERS: { [key: string]: UIServer } = {};
50
34
  const router = Router();
@@ -140,6 +124,7 @@ router.delete('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
140
124
  server.close();
141
125
  delete UI_SERVERS[conversationId];
142
126
  }
127
+ res.status(200).json({ status: 'ok' });
143
128
  });
144
129
 
145
130
  router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Response) => {
@@ -325,6 +310,16 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
325
310
  UI_SERVERS[outerConversationId] = new UIServer(outerConversationId);
326
311
  await UI_SERVERS[outerConversationId].start();
327
312
 
313
+ sendEvent(res, {
314
+ type: 'UI_SERVER_STARTED',
315
+ reason: '',
316
+ payload: {
317
+ conversationId: outerConversationId,
318
+ resetUrl: UI_SERVERS[outerConversationId].resolveUrlFromPath('/_reset'),
319
+ },
320
+ created: Date.now(),
321
+ });
322
+
328
323
  // Get the pages (5 at a time)
329
324
 
330
325
  const pagePromises: Promise<void>[] = [];