@kapeta/local-cluster-service 0.70.2 → 0.70.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 +18 -0
- package/dist/cjs/src/storm/PageGenerator.d.ts +3 -20
- package/dist/cjs/src/storm/PageGenerator.js +95 -126
- package/dist/cjs/src/storm/page-utils.d.ts +1 -0
- package/dist/cjs/src/storm/page-utils.js +6 -4
- package/dist/cjs/src/storm/routes.js +2 -5
- package/dist/cjs/src/storm/utils.d.ts +5 -0
- package/dist/cjs/src/storm/utils.js +15 -1
- package/dist/esm/src/storm/PageGenerator.d.ts +3 -20
- package/dist/esm/src/storm/PageGenerator.js +95 -126
- package/dist/esm/src/storm/page-utils.d.ts +1 -0
- package/dist/esm/src/storm/page-utils.js +6 -4
- package/dist/esm/src/storm/routes.js +2 -5
- package/dist/esm/src/storm/utils.d.ts +5 -0
- package/dist/esm/src/storm/utils.js +15 -1
- package/package.json +2 -1
- package/src/storm/PageGenerator.ts +106 -148
- package/src/storm/page-utils.ts +5 -4
- package/src/storm/routes.ts +2 -5
- package/src/storm/utils.ts +19 -1
- package/dist/cjs/src/storm/PromiseQueue.d.ts +0 -25
- package/dist/cjs/src/storm/PromiseQueue.js +0 -97
- package/dist/esm/src/storm/PromiseQueue.d.ts +0 -25
- package/dist/esm/src/storm/PromiseQueue.js +0 -97
- package/src/storm/PromiseQueue.ts +0 -129
@@ -7,8 +7,9 @@ import uuid from 'node-uuid';
|
|
7
7
|
import { stormClient, UIPagePrompt } from './stormClient';
|
8
8
|
import { ReferenceClassification, StormEvent, StormEventPage, StormImage, UIShell } from './events';
|
9
9
|
import { EventEmitter } from 'node:events';
|
10
|
-
import
|
11
|
-
|
10
|
+
import PQueue from 'p-queue';
|
11
|
+
|
12
|
+
import { hasPageOnDisk, normalizePath } from './page-utils';
|
12
13
|
|
13
14
|
export interface ImagePrompt {
|
14
15
|
name: string;
|
@@ -23,11 +24,11 @@ export interface ImagePrompt {
|
|
23
24
|
type InitialPrompt = Omit<UIPagePrompt, 'shell_page'> & { shellType?: 'public' | 'admin' | 'user' };
|
24
25
|
|
25
26
|
export class PageQueue extends EventEmitter {
|
26
|
-
private readonly queue:
|
27
|
-
private readonly eventQueue:
|
27
|
+
private readonly queue: PQueue;
|
28
|
+
private readonly eventQueue: PQueue;
|
28
29
|
private readonly systemId: string;
|
29
30
|
private readonly systemPrompt: string;
|
30
|
-
private readonly references: Map<string,
|
31
|
+
private readonly references: Map<string, boolean> = new Map();
|
31
32
|
private readonly pages: Map<string, string> = new Map();
|
32
33
|
private readonly images: Map<string, string> = new Map();
|
33
34
|
private uiShells: UIShell[] = [];
|
@@ -37,8 +38,8 @@ export class PageQueue extends EventEmitter {
|
|
37
38
|
super();
|
38
39
|
this.systemId = systemId;
|
39
40
|
this.systemPrompt = systemPrompt;
|
40
|
-
this.queue = new
|
41
|
-
this.eventQueue = new
|
41
|
+
this.queue = new PQueue({ concurrency });
|
42
|
+
this.eventQueue = new PQueue({ concurrency: Number.MAX_VALUE });
|
42
43
|
}
|
43
44
|
|
44
45
|
on(event: 'event', listener: (data: StormEvent) => void | Promise<void>): this;
|
@@ -94,11 +95,10 @@ export class PageQueue extends EventEmitter {
|
|
94
95
|
theme: this.theme,
|
95
96
|
};
|
96
97
|
|
97
|
-
|
98
|
-
this.references.set(prompt.path, generator);
|
98
|
+
this.references.set(prompt.path, true);
|
99
99
|
this.pages.set(prompt.path, prompt.description);
|
100
100
|
|
101
|
-
return this.
|
101
|
+
return this.queue.add<void>(() => this.generate(prompt, conversationId));
|
102
102
|
}
|
103
103
|
private getPrefix(): string {
|
104
104
|
let promptPrefix = '';
|
@@ -132,111 +132,110 @@ export class PageQueue extends EventEmitter {
|
|
132
132
|
return promptPrefix + prompt + promptPostfix;
|
133
133
|
}
|
134
134
|
|
135
|
-
private async
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
)
|
150
|
-
|
151
|
-
|
135
|
+
private async processPageEventWithReferences(event: StormEventPage) {
|
136
|
+
try {
|
137
|
+
console.log('Processing page event', event.payload.path);
|
138
|
+
const references = await this.resolveReferences(event.payload.content);
|
139
|
+
const matchesExistingPages = (url: string) => {
|
140
|
+
return [...this.pages.keys()].some((path) =>
|
141
|
+
new RegExp(`^${path.replaceAll('/*', '/[^/]+')}$`).test(url)
|
142
|
+
);
|
143
|
+
};
|
144
|
+
const initialPrompts: InitialPrompt[] = [];
|
145
|
+
const resourcePromises = references.map(async (reference) => {
|
146
|
+
if (
|
147
|
+
reference.url.startsWith('#') ||
|
148
|
+
reference.url.startsWith('javascript:') ||
|
149
|
+
reference.url.startsWith('http://') ||
|
150
|
+
reference.url.startsWith('https://') ||
|
151
|
+
reference.url.startsWith('data:') ||
|
152
|
+
reference.url.startsWith('blob:') ||
|
153
|
+
reference.url.startsWith('mailto:')
|
154
|
+
) {
|
155
|
+
return;
|
156
|
+
}
|
152
157
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
break;
|
168
|
-
}
|
169
|
-
this.pages.set(reference.url, reference.description);
|
170
|
-
|
171
|
-
initialPrompts.push({
|
172
|
-
name: reference.name,
|
173
|
-
title: reference.title,
|
174
|
-
path: reference.url,
|
175
|
-
method: 'GET',
|
176
|
-
storage_prefix: this.systemId + '_',
|
177
|
-
prompt:
|
178
|
-
`Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
|
179
|
-
`The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
|
180
|
-
description: reference.description,
|
181
|
-
// Only used for matching
|
182
|
-
filename: reference.name + '.ref.html',
|
183
|
-
theme: this.theme,
|
184
|
-
});
|
158
|
+
switch (reference.type) {
|
159
|
+
case 'image':
|
160
|
+
await this.addImagePrompt({
|
161
|
+
...reference,
|
162
|
+
content: event.payload.content,
|
163
|
+
});
|
164
|
+
break;
|
165
|
+
case 'css':
|
166
|
+
case 'javascript':
|
167
|
+
//console.log('Ignoring reference', reference);
|
168
|
+
break;
|
169
|
+
case 'html': {
|
170
|
+
const normalizedPath = normalizePath(reference.url);
|
171
|
+
if (matchesExistingPages(normalizedPath)) {
|
185
172
|
break;
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
title: prompt.title,
|
203
|
-
filename: prompt.filename,
|
204
|
-
method: 'GET',
|
205
|
-
path: prompt.path,
|
206
|
-
prompt: prompt.description,
|
207
|
-
conversationId: '',
|
208
|
-
content: '',
|
209
|
-
description: prompt.description,
|
210
|
-
},
|
173
|
+
}
|
174
|
+
this.pages.set(normalizedPath, reference.description);
|
175
|
+
|
176
|
+
initialPrompts.push({
|
177
|
+
name: reference.name,
|
178
|
+
title: reference.title,
|
179
|
+
path: normalizedPath,
|
180
|
+
method: 'GET',
|
181
|
+
storage_prefix: this.systemId + '_',
|
182
|
+
prompt:
|
183
|
+
`Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
|
184
|
+
`The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
|
185
|
+
description: reference.description,
|
186
|
+
// Only used for matching
|
187
|
+
filename: reference.name + '.ref.html',
|
188
|
+
theme: this.theme,
|
211
189
|
});
|
190
|
+
break;
|
212
191
|
}
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
192
|
+
}
|
193
|
+
});
|
194
|
+
|
195
|
+
// Wait for resources to be generated
|
196
|
+
await Promise.allSettled(resourcePromises);
|
197
|
+
this.emit('page', event);
|
198
|
+
|
199
|
+
// Emit any new pages after the current page to increase responsiveness
|
200
|
+
void initialPrompts.map((prompt) => {
|
201
|
+
if (!this.hasPrompt(prompt.path)) {
|
202
|
+
this.emit('page', {
|
203
|
+
type: 'PAGE',
|
204
|
+
reason: 'reference',
|
205
|
+
created: Date.now(),
|
206
|
+
payload: {
|
207
|
+
name: prompt.name,
|
208
|
+
title: prompt.title,
|
209
|
+
filename: prompt.filename,
|
210
|
+
method: 'GET',
|
211
|
+
path: prompt.path,
|
212
|
+
prompt: prompt.description,
|
213
|
+
conversationId: '',
|
214
|
+
content: '',
|
215
|
+
description: prompt.description,
|
216
|
+
},
|
217
|
+
});
|
218
|
+
}
|
219
|
+
return this.addPrompt(prompt);
|
220
|
+
});
|
221
|
+
} catch (e) {
|
222
|
+
console.error('Failed to process event', e);
|
223
|
+
throw e;
|
224
|
+
}
|
222
225
|
}
|
223
226
|
|
224
227
|
public cancel() {
|
225
|
-
this.queue.
|
226
|
-
this.eventQueue.
|
228
|
+
this.queue.clear();
|
229
|
+
this.eventQueue.clear();
|
227
230
|
}
|
228
231
|
|
229
232
|
public async wait() {
|
230
|
-
while (
|
231
|
-
await this.eventQueue.
|
232
|
-
await this.queue.
|
233
|
+
while (this.eventQueue.size || this.eventQueue.pending || this.queue.size || this.queue.pending) {
|
234
|
+
await this.eventQueue.onIdle();
|
235
|
+
await this.queue.onIdle();
|
233
236
|
}
|
234
237
|
}
|
235
238
|
|
236
|
-
public get length() {
|
237
|
-
return this.queue.length + this.eventQueue.length;
|
238
|
-
}
|
239
|
-
|
240
239
|
private async addImagePrompt(prompt: ImagePrompt) {
|
241
240
|
if (this.images.has(prompt.url)) {
|
242
241
|
//console.log('Ignoring duplicate image prompt', prompt);
|
@@ -257,56 +256,16 @@ export class PageQueue extends EventEmitter {
|
|
257
256
|
|
258
257
|
await result.waitForDone();
|
259
258
|
}
|
260
|
-
}
|
261
|
-
|
262
|
-
export class PageGenerator extends EventEmitter {
|
263
|
-
private readonly eventQueue: PromiseQueue;
|
264
|
-
private readonly conversationId: string;
|
265
|
-
public readonly prompt: UIPagePrompt;
|
266
|
-
|
267
|
-
constructor(prompt: UIPagePrompt, conversationId: string = uuid.v4()) {
|
268
|
-
super();
|
269
|
-
this.conversationId = conversationId;
|
270
|
-
this.prompt = prompt;
|
271
|
-
this.eventQueue = new PromiseQueue(Number.MAX_VALUE);
|
272
|
-
}
|
273
259
|
|
274
|
-
|
275
|
-
on(
|
276
|
-
event: 'page_refs',
|
277
|
-
listener: (data: { event: StormEventPage; references: ReferenceClassification[] }) => Promise<void>
|
278
|
-
): this;
|
279
|
-
|
280
|
-
on(event: string, listener: (...args: any[]) => void): this {
|
281
|
-
return super.on(event, (...args) => {
|
282
|
-
void this.eventQueue.add(async () => listener(...args));
|
283
|
-
});
|
284
|
-
}
|
285
|
-
|
286
|
-
emit(type: 'event', event: StormEvent): boolean;
|
287
|
-
emit(type: 'page_refs', event: { event: StormEventPage; references: ReferenceClassification[] }): boolean;
|
288
|
-
emit(eventName: string | symbol, ...args: any[]): boolean {
|
289
|
-
return super.emit(eventName, ...args);
|
290
|
-
}
|
291
|
-
|
292
|
-
public async generate() {
|
260
|
+
public async generate(prompt: UIPagePrompt, conversationId: string) {
|
293
261
|
const promises: Promise<void>[] = [];
|
294
|
-
const screenStream = await stormClient.createUIPage(
|
262
|
+
const screenStream = await stormClient.createUIPage(prompt, conversationId);
|
295
263
|
|
296
264
|
screenStream.on('data', (event: StormEvent) => {
|
297
265
|
if (event.type === 'PAGE') {
|
298
|
-
event.payload.conversationId =
|
299
|
-
|
300
|
-
promises.push(
|
301
|
-
(async () => {
|
302
|
-
const references = await this.resolveReferences(event.payload.content);
|
303
|
-
//console.log('Resolved references for page', references, event.payload);
|
304
|
-
this.emit('page_refs', {
|
305
|
-
event,
|
306
|
-
references,
|
307
|
-
});
|
308
|
-
})()
|
309
|
-
);
|
266
|
+
event.payload.conversationId = conversationId;
|
267
|
+
|
268
|
+
promises.push(this.processPageEventWithReferences(event));
|
310
269
|
return;
|
311
270
|
}
|
312
271
|
|
@@ -315,7 +274,6 @@ export class PageGenerator extends EventEmitter {
|
|
315
274
|
|
316
275
|
await screenStream.waitForDone();
|
317
276
|
await Promise.all(promises);
|
318
|
-
await this.eventQueue.wait();
|
319
277
|
}
|
320
278
|
|
321
279
|
private async resolveReferences(content: string) {
|
package/src/storm/page-utils.ts
CHANGED
@@ -15,7 +15,7 @@ import { ImagePrompt } from './PageGenerator';
|
|
15
15
|
|
16
16
|
export const SystemIdHeader = 'System-Id';
|
17
17
|
|
18
|
-
function normalizePath(path: string) {
|
18
|
+
export function normalizePath(path: string) {
|
19
19
|
return path
|
20
20
|
.replace(/\?.*$/gi, '')
|
21
21
|
.replace(/:[a-z][a-z_]*\b/gi, '*')
|
@@ -78,7 +78,8 @@ export function getSystemBaseDir(systemId: string) {
|
|
78
78
|
}
|
79
79
|
|
80
80
|
function getFilePath(method: string) {
|
81
|
-
|
81
|
+
// For HEAD requests, we assume we're serving looking for a GET resource
|
82
|
+
return Path.join(method === 'HEAD' ? 'get' : method.toLowerCase(), 'index.html');
|
82
83
|
}
|
83
84
|
|
84
85
|
export function resolveReadPath(systemId: string, path: string, method: string) {
|
@@ -105,7 +106,7 @@ export function resolveReadPath(systemId: string, path: string, method: string)
|
|
105
106
|
return fullPath;
|
106
107
|
}
|
107
108
|
|
108
|
-
const parts = path.split(
|
109
|
+
const parts = path.split('/');
|
109
110
|
|
110
111
|
let currentPath = '';
|
111
112
|
|
@@ -214,7 +215,7 @@ function getFallbackHtml(path: string, method: string): string {
|
|
214
215
|
<script>
|
215
216
|
const checkInterval = 3000;
|
216
217
|
function checkPageReady() {
|
217
|
-
fetch('
|
218
|
+
fetch('/${path}', { method: 'HEAD' })
|
218
219
|
.then(response => {
|
219
220
|
if (response.status === 200) {
|
220
221
|
// The page is ready, reload to fetch it
|
package/src/storm/routes.ts
CHANGED
@@ -47,8 +47,7 @@ import {
|
|
47
47
|
import { UIServer } from './UIServer';
|
48
48
|
import { randomUUID } from 'crypto';
|
49
49
|
import { ImagePrompt, PageQueue } from './PageGenerator';
|
50
|
-
import { createFuture } from './
|
51
|
-
import { copyDirectory } from './utils';
|
50
|
+
import { copyDirectory, createFuture } from './utils';
|
52
51
|
|
53
52
|
const UI_SERVERS: { [key: string]: UIServer } = {};
|
54
53
|
const router = Router();
|
@@ -250,9 +249,7 @@ router.post('/:handle/ui/iterative', async (req: KapetaBodyRequest, res: Respons
|
|
250
249
|
UI_SERVERS[systemId] = new UIServer(systemId);
|
251
250
|
await UI_SERVERS[systemId].start();
|
252
251
|
waitForStormStream(landingPagesStream).then(() => {
|
253
|
-
|
254
|
-
systemPrompt.resolve(aiRequest.prompt);
|
255
|
-
}
|
252
|
+
systemPrompt.resolve(aiRequest.prompt);
|
256
253
|
});
|
257
254
|
|
258
255
|
const pageQueue = new PageQueue(systemId, await systemPrompt.promise, 5);
|
package/src/storm/utils.ts
CHANGED
@@ -5,7 +5,11 @@
|
|
5
5
|
import FS from 'fs-extra';
|
6
6
|
import Path from 'path';
|
7
7
|
|
8
|
-
export async function copyDirectory(
|
8
|
+
export async function copyDirectory(
|
9
|
+
src: string,
|
10
|
+
dest: string,
|
11
|
+
modifyHtml: (fileName: string, content: string) => Promise<string>
|
12
|
+
): Promise<void> {
|
9
13
|
await FS.promises.mkdir(dest, { recursive: true });
|
10
14
|
const entries = await FS.promises.readdir(src, { withFileTypes: true });
|
11
15
|
|
@@ -26,3 +30,17 @@ export async function copyDirectory(src: string, dest: string, modifyHtml: (file
|
|
26
30
|
}
|
27
31
|
}
|
28
32
|
}
|
33
|
+
|
34
|
+
export function createFuture<T = void>() {
|
35
|
+
let resolve: (value: T | PromiseLike<T>) => void = () => {};
|
36
|
+
let reject: (reason?: any) => void = () => {};
|
37
|
+
const promise = new Promise<T>((res, rej) => {
|
38
|
+
resolve = res;
|
39
|
+
reject = rej;
|
40
|
+
});
|
41
|
+
return {
|
42
|
+
promise,
|
43
|
+
resolve,
|
44
|
+
reject,
|
45
|
+
};
|
46
|
+
}
|
@@ -1,25 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Copyright 2023 Kapeta Inc.
|
3
|
-
* SPDX-License-Identifier: BUSL-1.1
|
4
|
-
*/
|
5
|
-
export type Future<T> = () => Promise<T>;
|
6
|
-
export type FuturePromise<T> = {
|
7
|
-
promise: Promise<T>;
|
8
|
-
resolve: (value: T) => void;
|
9
|
-
reject: (reason: any) => void;
|
10
|
-
isResolved: () => boolean;
|
11
|
-
};
|
12
|
-
export declare function createFuture<T = void>(): FuturePromise<T>;
|
13
|
-
export declare class PromiseQueue {
|
14
|
-
private readonly queue;
|
15
|
-
private readonly active;
|
16
|
-
private readonly maxConcurrency;
|
17
|
-
constructor(maxConcurrency?: number);
|
18
|
-
private toInternal;
|
19
|
-
add<T>(future: Future<T>): Promise<T>;
|
20
|
-
private next;
|
21
|
-
cancel(): void;
|
22
|
-
wait(): Promise<void>;
|
23
|
-
get empty(): boolean;
|
24
|
-
get length(): number;
|
25
|
-
}
|
@@ -1,97 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.PromiseQueue = exports.createFuture = void 0;
|
4
|
-
function createFuture() {
|
5
|
-
let resolved = false;
|
6
|
-
let resolve = () => {
|
7
|
-
resolved = true;
|
8
|
-
};
|
9
|
-
let reject = () => {
|
10
|
-
resolved = true;
|
11
|
-
};
|
12
|
-
const promise = new Promise((res, rej) => {
|
13
|
-
resolve = (value) => {
|
14
|
-
resolved = true;
|
15
|
-
res(value);
|
16
|
-
};
|
17
|
-
reject = (reason) => {
|
18
|
-
resolved = true;
|
19
|
-
rej(reason);
|
20
|
-
};
|
21
|
-
});
|
22
|
-
return {
|
23
|
-
promise,
|
24
|
-
resolve,
|
25
|
-
reject,
|
26
|
-
isResolved: () => resolved,
|
27
|
-
};
|
28
|
-
}
|
29
|
-
exports.createFuture = createFuture;
|
30
|
-
class PromiseQueue {
|
31
|
-
queue = [];
|
32
|
-
active = [];
|
33
|
-
// Maximum number of concurrent promises
|
34
|
-
maxConcurrency;
|
35
|
-
constructor(maxConcurrency = 5) {
|
36
|
-
this.maxConcurrency = maxConcurrency;
|
37
|
-
}
|
38
|
-
toInternal(future) {
|
39
|
-
let resolve = () => { };
|
40
|
-
let reject = () => { };
|
41
|
-
const promise = new Promise((res, rej) => {
|
42
|
-
resolve = res;
|
43
|
-
reject = rej;
|
44
|
-
});
|
45
|
-
return {
|
46
|
-
execute: future,
|
47
|
-
promise,
|
48
|
-
resolve,
|
49
|
-
reject,
|
50
|
-
};
|
51
|
-
}
|
52
|
-
add(future) {
|
53
|
-
const internal = this.toInternal(future);
|
54
|
-
this.queue.push(internal);
|
55
|
-
this.next();
|
56
|
-
return internal.promise;
|
57
|
-
}
|
58
|
-
next() {
|
59
|
-
if (this.active.length >= this.maxConcurrency) {
|
60
|
-
return false;
|
61
|
-
}
|
62
|
-
if (this.queue.length === 0) {
|
63
|
-
return false;
|
64
|
-
}
|
65
|
-
const future = this.queue.shift();
|
66
|
-
if (!future) {
|
67
|
-
return false;
|
68
|
-
}
|
69
|
-
const promise = future
|
70
|
-
.execute()
|
71
|
-
.then(future.resolve)
|
72
|
-
.catch(future.reject)
|
73
|
-
.finally(() => {
|
74
|
-
this.active.splice(this.active.indexOf(promise), 1);
|
75
|
-
this.next();
|
76
|
-
});
|
77
|
-
this.active.push(promise);
|
78
|
-
return this.next();
|
79
|
-
}
|
80
|
-
cancel() {
|
81
|
-
this.queue.splice(0, this.queue.length);
|
82
|
-
this.active.splice(0, this.active.length);
|
83
|
-
}
|
84
|
-
async wait() {
|
85
|
-
while (this.queue.length > 0 || this.active.length > 0) {
|
86
|
-
await Promise.allSettled(this.active);
|
87
|
-
this.next();
|
88
|
-
}
|
89
|
-
}
|
90
|
-
get empty() {
|
91
|
-
return this.queue.length === 0 && this.active.length === 0;
|
92
|
-
}
|
93
|
-
get length() {
|
94
|
-
return this.queue.length + this.active.length;
|
95
|
-
}
|
96
|
-
}
|
97
|
-
exports.PromiseQueue = PromiseQueue;
|
@@ -1,25 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Copyright 2023 Kapeta Inc.
|
3
|
-
* SPDX-License-Identifier: BUSL-1.1
|
4
|
-
*/
|
5
|
-
export type Future<T> = () => Promise<T>;
|
6
|
-
export type FuturePromise<T> = {
|
7
|
-
promise: Promise<T>;
|
8
|
-
resolve: (value: T) => void;
|
9
|
-
reject: (reason: any) => void;
|
10
|
-
isResolved: () => boolean;
|
11
|
-
};
|
12
|
-
export declare function createFuture<T = void>(): FuturePromise<T>;
|
13
|
-
export declare class PromiseQueue {
|
14
|
-
private readonly queue;
|
15
|
-
private readonly active;
|
16
|
-
private readonly maxConcurrency;
|
17
|
-
constructor(maxConcurrency?: number);
|
18
|
-
private toInternal;
|
19
|
-
add<T>(future: Future<T>): Promise<T>;
|
20
|
-
private next;
|
21
|
-
cancel(): void;
|
22
|
-
wait(): Promise<void>;
|
23
|
-
get empty(): boolean;
|
24
|
-
get length(): number;
|
25
|
-
}
|
@@ -1,97 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.PromiseQueue = exports.createFuture = void 0;
|
4
|
-
function createFuture() {
|
5
|
-
let resolved = false;
|
6
|
-
let resolve = () => {
|
7
|
-
resolved = true;
|
8
|
-
};
|
9
|
-
let reject = () => {
|
10
|
-
resolved = true;
|
11
|
-
};
|
12
|
-
const promise = new Promise((res, rej) => {
|
13
|
-
resolve = (value) => {
|
14
|
-
resolved = true;
|
15
|
-
res(value);
|
16
|
-
};
|
17
|
-
reject = (reason) => {
|
18
|
-
resolved = true;
|
19
|
-
rej(reason);
|
20
|
-
};
|
21
|
-
});
|
22
|
-
return {
|
23
|
-
promise,
|
24
|
-
resolve,
|
25
|
-
reject,
|
26
|
-
isResolved: () => resolved,
|
27
|
-
};
|
28
|
-
}
|
29
|
-
exports.createFuture = createFuture;
|
30
|
-
class PromiseQueue {
|
31
|
-
queue = [];
|
32
|
-
active = [];
|
33
|
-
// Maximum number of concurrent promises
|
34
|
-
maxConcurrency;
|
35
|
-
constructor(maxConcurrency = 5) {
|
36
|
-
this.maxConcurrency = maxConcurrency;
|
37
|
-
}
|
38
|
-
toInternal(future) {
|
39
|
-
let resolve = () => { };
|
40
|
-
let reject = () => { };
|
41
|
-
const promise = new Promise((res, rej) => {
|
42
|
-
resolve = res;
|
43
|
-
reject = rej;
|
44
|
-
});
|
45
|
-
return {
|
46
|
-
execute: future,
|
47
|
-
promise,
|
48
|
-
resolve,
|
49
|
-
reject,
|
50
|
-
};
|
51
|
-
}
|
52
|
-
add(future) {
|
53
|
-
const internal = this.toInternal(future);
|
54
|
-
this.queue.push(internal);
|
55
|
-
this.next();
|
56
|
-
return internal.promise;
|
57
|
-
}
|
58
|
-
next() {
|
59
|
-
if (this.active.length >= this.maxConcurrency) {
|
60
|
-
return false;
|
61
|
-
}
|
62
|
-
if (this.queue.length === 0) {
|
63
|
-
return false;
|
64
|
-
}
|
65
|
-
const future = this.queue.shift();
|
66
|
-
if (!future) {
|
67
|
-
return false;
|
68
|
-
}
|
69
|
-
const promise = future
|
70
|
-
.execute()
|
71
|
-
.then(future.resolve)
|
72
|
-
.catch(future.reject)
|
73
|
-
.finally(() => {
|
74
|
-
this.active.splice(this.active.indexOf(promise), 1);
|
75
|
-
this.next();
|
76
|
-
});
|
77
|
-
this.active.push(promise);
|
78
|
-
return this.next();
|
79
|
-
}
|
80
|
-
cancel() {
|
81
|
-
this.queue.splice(0, this.queue.length);
|
82
|
-
this.active.splice(0, this.active.length);
|
83
|
-
}
|
84
|
-
async wait() {
|
85
|
-
while (this.queue.length > 0 || this.active.length > 0) {
|
86
|
-
await Promise.allSettled(this.active);
|
87
|
-
this.next();
|
88
|
-
}
|
89
|
-
}
|
90
|
-
get empty() {
|
91
|
-
return this.queue.length === 0 && this.active.length === 0;
|
92
|
-
}
|
93
|
-
get length() {
|
94
|
-
return this.queue.length + this.active.length;
|
95
|
-
}
|
96
|
-
}
|
97
|
-
exports.PromiseQueue = PromiseQueue;
|