@kapeta/local-cluster-service 0.58.2 → 0.58.4

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.58.4](https://github.com/kapetacom/local-cluster-service/compare/v0.58.3...v0.58.4) (2024-07-29)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * fix provider matching when replacing versions in archetypes ([d0554b2](https://github.com/kapetacom/local-cluster-service/commit/d0554b284a57cd4a436a7f00cbbd698a39ffe071))
7
+
8
+ ## [0.58.3](https://github.com/kapetacom/local-cluster-service/compare/v0.58.2...v0.58.3) (2024-07-26)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Ensure that we can control concurrency in page generation ([#203](https://github.com/kapetacom/local-cluster-service/issues/203)) ([4a919e0](https://github.com/kapetacom/local-cluster-service/commit/4a919e013b20f10d4199927970fbe38091a2b858))
14
+
1
15
  ## [0.58.2](https://github.com/kapetacom/local-cluster-service/compare/v0.58.1...v0.58.2) (2024-07-25)
2
16
 
3
17
 
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ export type Future<T> = () => Promise<T>;
6
+ export declare class PromiseQueue {
7
+ private readonly queue;
8
+ private readonly active;
9
+ private readonly maxConcurrency;
10
+ constructor(maxConcurrency?: number);
11
+ private toInternal;
12
+ add<T>(future: Future<T>): Promise<T>;
13
+ private next;
14
+ cancel(): void;
15
+ wait(): Promise<void>;
16
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PromiseQueue = void 0;
4
+ class PromiseQueue {
5
+ queue = [];
6
+ active = [];
7
+ // Maximum number of concurrent promises
8
+ maxConcurrency;
9
+ constructor(maxConcurrency = 5) {
10
+ this.maxConcurrency = maxConcurrency;
11
+ }
12
+ toInternal(future) {
13
+ let resolve = () => { };
14
+ let reject = () => { };
15
+ const promise = new Promise((res, rej) => {
16
+ resolve = res;
17
+ reject = rej;
18
+ });
19
+ return {
20
+ execute: future,
21
+ promise,
22
+ resolve,
23
+ reject,
24
+ };
25
+ }
26
+ add(future) {
27
+ const internal = this.toInternal(future);
28
+ this.queue.push(internal);
29
+ this.next();
30
+ return internal.promise;
31
+ }
32
+ next() {
33
+ if (this.active.length >= this.maxConcurrency) {
34
+ return false;
35
+ }
36
+ if (this.queue.length === 0) {
37
+ return false;
38
+ }
39
+ const future = this.queue.shift();
40
+ if (!future) {
41
+ return false;
42
+ }
43
+ const promise = future
44
+ .execute()
45
+ .then(future.resolve)
46
+ .catch(future.reject)
47
+ .finally(() => {
48
+ this.active.splice(this.active.indexOf(promise), 1);
49
+ this.next();
50
+ });
51
+ this.active.push(promise);
52
+ return this.next();
53
+ }
54
+ cancel() {
55
+ this.queue.splice(0, this.queue.length);
56
+ this.active.splice(0, this.active.length);
57
+ }
58
+ async wait() {
59
+ while (this.queue.length > 0 || this.active.length > 0) {
60
+ await Promise.all(this.active);
61
+ }
62
+ }
63
+ }
64
+ exports.PromiseQueue = PromiseQueue;
@@ -710,7 +710,7 @@ class StormEventParser {
710
710
  for (const prop in options) {
711
711
  if (options.hasOwnProperty(prop)) {
712
712
  const value = options[prop];
713
- if (value.startsWith(kind)) {
713
+ if (value.indexOf(kind) > 0) {
714
714
  return value;
715
715
  }
716
716
  }
@@ -19,6 +19,7 @@ const event_parser_1 = require("./event-parser");
19
19
  const codegen_1 = require("./codegen");
20
20
  const assetManager_1 = require("../assetManager");
21
21
  const node_uuid_1 = __importDefault(require("node-uuid"));
22
+ const PromiseQueue_1 = require("./PromiseQueue");
22
23
  const router = (0, express_promise_router_1.default)();
23
24
  router.use('/', cors_1.corsHandler);
24
25
  router.use('/', stringBody_1.stringBody);
@@ -61,6 +62,10 @@ router.post('/:handle/ui', async (req, res) => {
61
62
  res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
62
63
  res.set(stormClient_1.ConversationIdHeader, userJourneysStream.getConversationId());
63
64
  const promises = {};
65
+ const queue = new PromiseQueue_1.PromiseQueue(10);
66
+ onRequestAborted(req, res, () => {
67
+ queue.cancel();
68
+ });
64
69
  userJourneysStream.on('data', async (data) => {
65
70
  try {
66
71
  console.log('Processing user journey event', data);
@@ -68,11 +73,14 @@ router.post('/:handle/ui', async (req, res) => {
68
73
  if (data.type !== 'USER_JOURNEY') {
69
74
  return;
70
75
  }
76
+ if (userJourneysStream.isAborted()) {
77
+ return;
78
+ }
71
79
  data.payload.screens.forEach((screen) => {
72
80
  if (screen.name in promises) {
73
81
  return;
74
82
  }
75
- promises[screen.name] = new Promise(async (resolve, reject) => {
83
+ promises[screen.name] = queue.add(() => new Promise(async (resolve, reject) => {
76
84
  try {
77
85
  const innerConversationId = node_uuid_1.default.v4();
78
86
  const screenStream = await stormClient_1.stormClient.createUIPage({
@@ -88,7 +96,6 @@ router.post('/:handle/ui', async (req, res) => {
88
96
  if (screenData.type === 'PAGE') {
89
97
  screenData.payload.conversationId = innerConversationId;
90
98
  }
91
- console.log('Processing screen event', screenData);
92
99
  sendEvent(res, screenData);
93
100
  });
94
101
  screenStream.on('end', () => {
@@ -96,9 +103,10 @@ router.post('/:handle/ui', async (req, res) => {
96
103
  });
97
104
  }
98
105
  catch (e) {
106
+ console.error('Failed to process screen', e);
99
107
  reject(e);
100
108
  }
101
- });
109
+ }));
102
110
  });
103
111
  }
104
112
  catch (e) {
@@ -106,10 +114,10 @@ router.post('/:handle/ui', async (req, res) => {
106
114
  }
107
115
  });
108
116
  await waitForStormStream(userJourneysStream);
117
+ await queue.wait();
109
118
  if (userJourneysStream.isAborted()) {
110
119
  return;
111
120
  }
112
- await Promise.all(Object.values(promises));
113
121
  sendDone(res);
114
122
  }
115
123
  catch (err) {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ export type Future<T> = () => Promise<T>;
6
+ export declare class PromiseQueue {
7
+ private readonly queue;
8
+ private readonly active;
9
+ private readonly maxConcurrency;
10
+ constructor(maxConcurrency?: number);
11
+ private toInternal;
12
+ add<T>(future: Future<T>): Promise<T>;
13
+ private next;
14
+ cancel(): void;
15
+ wait(): Promise<void>;
16
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PromiseQueue = void 0;
4
+ class PromiseQueue {
5
+ queue = [];
6
+ active = [];
7
+ // Maximum number of concurrent promises
8
+ maxConcurrency;
9
+ constructor(maxConcurrency = 5) {
10
+ this.maxConcurrency = maxConcurrency;
11
+ }
12
+ toInternal(future) {
13
+ let resolve = () => { };
14
+ let reject = () => { };
15
+ const promise = new Promise((res, rej) => {
16
+ resolve = res;
17
+ reject = rej;
18
+ });
19
+ return {
20
+ execute: future,
21
+ promise,
22
+ resolve,
23
+ reject,
24
+ };
25
+ }
26
+ add(future) {
27
+ const internal = this.toInternal(future);
28
+ this.queue.push(internal);
29
+ this.next();
30
+ return internal.promise;
31
+ }
32
+ next() {
33
+ if (this.active.length >= this.maxConcurrency) {
34
+ return false;
35
+ }
36
+ if (this.queue.length === 0) {
37
+ return false;
38
+ }
39
+ const future = this.queue.shift();
40
+ if (!future) {
41
+ return false;
42
+ }
43
+ const promise = future
44
+ .execute()
45
+ .then(future.resolve)
46
+ .catch(future.reject)
47
+ .finally(() => {
48
+ this.active.splice(this.active.indexOf(promise), 1);
49
+ this.next();
50
+ });
51
+ this.active.push(promise);
52
+ return this.next();
53
+ }
54
+ cancel() {
55
+ this.queue.splice(0, this.queue.length);
56
+ this.active.splice(0, this.active.length);
57
+ }
58
+ async wait() {
59
+ while (this.queue.length > 0 || this.active.length > 0) {
60
+ await Promise.all(this.active);
61
+ }
62
+ }
63
+ }
64
+ exports.PromiseQueue = PromiseQueue;
@@ -710,7 +710,7 @@ class StormEventParser {
710
710
  for (const prop in options) {
711
711
  if (options.hasOwnProperty(prop)) {
712
712
  const value = options[prop];
713
- if (value.startsWith(kind)) {
713
+ if (value.indexOf(kind) > 0) {
714
714
  return value;
715
715
  }
716
716
  }
@@ -19,6 +19,7 @@ const event_parser_1 = require("./event-parser");
19
19
  const codegen_1 = require("./codegen");
20
20
  const assetManager_1 = require("../assetManager");
21
21
  const node_uuid_1 = __importDefault(require("node-uuid"));
22
+ const PromiseQueue_1 = require("./PromiseQueue");
22
23
  const router = (0, express_promise_router_1.default)();
23
24
  router.use('/', cors_1.corsHandler);
24
25
  router.use('/', stringBody_1.stringBody);
@@ -61,6 +62,10 @@ router.post('/:handle/ui', async (req, res) => {
61
62
  res.set('Access-Control-Expose-Headers', stormClient_1.ConversationIdHeader);
62
63
  res.set(stormClient_1.ConversationIdHeader, userJourneysStream.getConversationId());
63
64
  const promises = {};
65
+ const queue = new PromiseQueue_1.PromiseQueue(10);
66
+ onRequestAborted(req, res, () => {
67
+ queue.cancel();
68
+ });
64
69
  userJourneysStream.on('data', async (data) => {
65
70
  try {
66
71
  console.log('Processing user journey event', data);
@@ -68,11 +73,14 @@ router.post('/:handle/ui', async (req, res) => {
68
73
  if (data.type !== 'USER_JOURNEY') {
69
74
  return;
70
75
  }
76
+ if (userJourneysStream.isAborted()) {
77
+ return;
78
+ }
71
79
  data.payload.screens.forEach((screen) => {
72
80
  if (screen.name in promises) {
73
81
  return;
74
82
  }
75
- promises[screen.name] = new Promise(async (resolve, reject) => {
83
+ promises[screen.name] = queue.add(() => new Promise(async (resolve, reject) => {
76
84
  try {
77
85
  const innerConversationId = node_uuid_1.default.v4();
78
86
  const screenStream = await stormClient_1.stormClient.createUIPage({
@@ -88,7 +96,6 @@ router.post('/:handle/ui', async (req, res) => {
88
96
  if (screenData.type === 'PAGE') {
89
97
  screenData.payload.conversationId = innerConversationId;
90
98
  }
91
- console.log('Processing screen event', screenData);
92
99
  sendEvent(res, screenData);
93
100
  });
94
101
  screenStream.on('end', () => {
@@ -96,9 +103,10 @@ router.post('/:handle/ui', async (req, res) => {
96
103
  });
97
104
  }
98
105
  catch (e) {
106
+ console.error('Failed to process screen', e);
99
107
  reject(e);
100
108
  }
101
- });
109
+ }));
102
110
  });
103
111
  }
104
112
  catch (e) {
@@ -106,10 +114,10 @@ router.post('/:handle/ui', async (req, res) => {
106
114
  }
107
115
  });
108
116
  await waitForStormStream(userJourneysStream);
117
+ await queue.wait();
109
118
  if (userJourneysStream.isAborted()) {
110
119
  return;
111
120
  }
112
- await Promise.all(Object.values(promises));
113
121
  sendDone(res);
114
122
  }
115
123
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.58.2",
3
+ "version": "0.58.4",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ export type Future<T> = () => Promise<T>;
6
+
7
+ type InternalFuture<T> = {
8
+ execute: Future<T>;
9
+ promise: Promise<T>;
10
+ resolve: (value: T) => void;
11
+ reject: (reason: any) => void;
12
+ };
13
+
14
+ export class PromiseQueue {
15
+ private readonly queue: InternalFuture<any>[] = [];
16
+ private readonly active: Promise<any>[] = [];
17
+
18
+ // Maximum number of concurrent promises
19
+ private readonly maxConcurrency: number;
20
+
21
+ constructor(maxConcurrency: number = 5) {
22
+ this.maxConcurrency = maxConcurrency;
23
+ }
24
+
25
+ private toInternal<T>(future: Future<T>): InternalFuture<T> {
26
+ let resolve: (value: T) => void = () => {};
27
+ let reject: (reason: any) => void = () => {};
28
+
29
+ const promise = new Promise<T>((res, rej) => {
30
+ resolve = res;
31
+ reject = rej;
32
+ });
33
+ return {
34
+ execute: future,
35
+ promise,
36
+ resolve,
37
+ reject,
38
+ };
39
+ }
40
+
41
+ public add<T>(future: Future<T>) {
42
+ const internal = this.toInternal<T>(future);
43
+ this.queue.push(internal);
44
+ this.next();
45
+
46
+ return internal.promise;
47
+ }
48
+
49
+ private next(): boolean {
50
+ if (this.active.length >= this.maxConcurrency) {
51
+ return false;
52
+ }
53
+ if (this.queue.length === 0) {
54
+ return false;
55
+ }
56
+
57
+ const future = this.queue.shift();
58
+ if (!future) {
59
+ return false;
60
+ }
61
+
62
+ const promise = future
63
+ .execute()
64
+ .then(future.resolve)
65
+ .catch(future.reject)
66
+ .finally(() => {
67
+ this.active.splice(this.active.indexOf(promise), 1);
68
+ this.next();
69
+ });
70
+
71
+ this.active.push(promise);
72
+
73
+ return this.next();
74
+ }
75
+
76
+ public cancel() {
77
+ this.queue.splice(0, this.queue.length);
78
+ this.active.splice(0, this.active.length);
79
+ }
80
+
81
+ public async wait() {
82
+ while (this.queue.length > 0 || this.active.length > 0) {
83
+ await Promise.all(this.active);
84
+ }
85
+ }
86
+ }
@@ -925,7 +925,7 @@ export class StormEventParser {
925
925
  for (const prop in options) {
926
926
  if (options.hasOwnProperty(prop)) {
927
927
  const value = options[prop];
928
- if (value.startsWith(kind)) {
928
+ if (value.indexOf(kind) > 0) {
929
929
  return value;
930
930
  }
931
931
  }
@@ -24,6 +24,7 @@ import {
24
24
  import { StormCodegen } from './codegen';
25
25
  import { assetManager } from '../assetManager';
26
26
  import uuid from 'node-uuid';
27
+ import { PromiseQueue } from './PromiseQueue';
27
28
 
28
29
  const router = Router();
29
30
 
@@ -82,6 +83,11 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
82
83
 
83
84
  const promises: { [key: string]: Promise<void> } = {};
84
85
 
86
+ const queue = new PromiseQueue(10);
87
+ onRequestAborted(req, res, () => {
88
+ queue.cancel();
89
+ });
90
+
85
91
  userJourneysStream.on('data', async (data: StormEvent) => {
86
92
  try {
87
93
  console.log('Processing user journey event', data);
@@ -90,39 +96,47 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
90
96
  return;
91
97
  }
92
98
 
99
+ if (userJourneysStream.isAborted()) {
100
+ return;
101
+ }
102
+
93
103
  data.payload.screens.forEach((screen) => {
94
104
  if (screen.name in promises) {
95
105
  return;
96
106
  }
97
- promises[screen.name] = new Promise(async (resolve, reject) => {
98
- try {
99
- const innerConversationId = uuid.v4();
100
- const screenStream = await stormClient.createUIPage(
101
- {
102
- prompt: screen.requirements,
103
- method: screen.method,
104
- path: screen.path,
105
- description: screen.requirements,
106
- name: screen.name,
107
- title: screen.title,
108
- filename: screen.filename,
109
- },
110
- innerConversationId
111
- );
112
- screenStream.on('data', (screenData: StormEvent) => {
113
- if (screenData.type === 'PAGE') {
114
- screenData.payload.conversationId = innerConversationId;
107
+ promises[screen.name] = queue.add(
108
+ () =>
109
+ new Promise(async (resolve, reject) => {
110
+ try {
111
+ const innerConversationId = uuid.v4();
112
+ const screenStream = await stormClient.createUIPage(
113
+ {
114
+ prompt: screen.requirements,
115
+ method: screen.method,
116
+ path: screen.path,
117
+ description: screen.requirements,
118
+ name: screen.name,
119
+ title: screen.title,
120
+ filename: screen.filename,
121
+ },
122
+ innerConversationId
123
+ );
124
+ screenStream.on('data', (screenData: StormEvent) => {
125
+ if (screenData.type === 'PAGE') {
126
+ screenData.payload.conversationId = innerConversationId;
127
+ }
128
+
129
+ sendEvent(res, screenData);
130
+ });
131
+ screenStream.on('end', () => {
132
+ resolve();
133
+ });
134
+ } catch (e: any) {
135
+ console.error('Failed to process screen', e);
136
+ reject(e);
115
137
  }
116
- console.log('Processing screen event', screenData);
117
- sendEvent(res, screenData);
118
- });
119
- screenStream.on('end', () => {
120
- resolve();
121
- });
122
- } catch (e: any) {
123
- reject(e);
124
- }
125
- });
138
+ })
139
+ );
126
140
  });
127
141
  } catch (e) {
128
142
  console.error('Failed to process event', e);
@@ -130,13 +144,12 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
130
144
  });
131
145
 
132
146
  await waitForStormStream(userJourneysStream);
147
+ await queue.wait();
133
148
 
134
149
  if (userJourneysStream.isAborted()) {
135
150
  return;
136
151
  }
137
152
 
138
- await Promise.all(Object.values(promises));
139
-
140
153
  sendDone(res);
141
154
  } catch (err: any) {
142
155
  sendError(err, res);