@kapeta/local-cluster-service 0.70.3 → 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 +10 -0
- package/dist/cjs/src/storm/PageGenerator.d.ts +3 -20
- package/dist/cjs/src/storm/PageGenerator.js +94 -126
- package/dist/cjs/src/storm/page-utils.js +4 -3
- 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 +94 -126
- package/dist/esm/src/storm/page-utils.js +4 -3
- 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 +104 -149
- package/src/storm/page-utils.ts +4 -3
- 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,7 +7,8 @@ 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
|
10
|
+
import PQueue from 'p-queue';
|
11
|
+
|
11
12
|
import { hasPageOnDisk, normalizePath } from './page-utils';
|
12
13
|
|
13
14
|
export interface ImagePrompt {
|
@@ -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,114 +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
|
-
)
|
152
|
-
|
153
|
-
|
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
|
+
}
|
154
157
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
break;
|
170
|
-
}
|
171
|
-
this.pages.set(normalizedPath, reference.description);
|
172
|
-
|
173
|
-
initialPrompts.push({
|
174
|
-
name: reference.name,
|
175
|
-
title: reference.title,
|
176
|
-
path: normalizedPath,
|
177
|
-
method: 'GET',
|
178
|
-
storage_prefix: this.systemId + '_',
|
179
|
-
prompt:
|
180
|
-
`Implement a page for ${reference.name} at ${reference.url} with the following description: ${reference.description}.\n` +
|
181
|
-
`The page was referenced from this page: \n### PATH: ${event.payload.path}\n\`\`\`html\n${event.payload.content}\n\`\`\`\n`,
|
182
|
-
description: reference.description,
|
183
|
-
// Only used for matching
|
184
|
-
filename: reference.name + '.ref.html',
|
185
|
-
theme: this.theme,
|
186
|
-
});
|
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)) {
|
187
172
|
break;
|
188
173
|
}
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
name: prompt.name,
|
205
|
-
title: prompt.title,
|
206
|
-
filename: prompt.filename,
|
207
|
-
method: 'GET',
|
208
|
-
path: prompt.path,
|
209
|
-
prompt: prompt.description,
|
210
|
-
conversationId: '',
|
211
|
-
content: '',
|
212
|
-
description: prompt.description,
|
213
|
-
},
|
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,
|
214
189
|
});
|
190
|
+
break;
|
215
191
|
}
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
+
}
|
225
225
|
}
|
226
226
|
|
227
227
|
public cancel() {
|
228
|
-
this.queue.
|
229
|
-
this.eventQueue.
|
228
|
+
this.queue.clear();
|
229
|
+
this.eventQueue.clear();
|
230
230
|
}
|
231
231
|
|
232
232
|
public async wait() {
|
233
|
-
while (
|
234
|
-
await this.eventQueue.
|
235
|
-
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();
|
236
236
|
}
|
237
237
|
}
|
238
238
|
|
239
|
-
public get length() {
|
240
|
-
return this.queue.length + this.eventQueue.length;
|
241
|
-
}
|
242
|
-
|
243
239
|
private async addImagePrompt(prompt: ImagePrompt) {
|
244
240
|
if (this.images.has(prompt.url)) {
|
245
241
|
//console.log('Ignoring duplicate image prompt', prompt);
|
@@ -260,56 +256,16 @@ export class PageQueue extends EventEmitter {
|
|
260
256
|
|
261
257
|
await result.waitForDone();
|
262
258
|
}
|
263
|
-
}
|
264
|
-
|
265
|
-
export class PageGenerator extends EventEmitter {
|
266
|
-
private readonly eventQueue: PromiseQueue;
|
267
|
-
private readonly conversationId: string;
|
268
|
-
public readonly prompt: UIPagePrompt;
|
269
|
-
|
270
|
-
constructor(prompt: UIPagePrompt, conversationId: string = uuid.v4()) {
|
271
|
-
super();
|
272
|
-
this.conversationId = conversationId;
|
273
|
-
this.prompt = prompt;
|
274
|
-
this.eventQueue = new PromiseQueue(Number.MAX_VALUE);
|
275
|
-
}
|
276
259
|
|
277
|
-
|
278
|
-
on(
|
279
|
-
event: 'page_refs',
|
280
|
-
listener: (data: { event: StormEventPage; references: ReferenceClassification[] }) => Promise<void>
|
281
|
-
): this;
|
282
|
-
|
283
|
-
on(event: string, listener: (...args: any[]) => void): this {
|
284
|
-
return super.on(event, (...args) => {
|
285
|
-
void this.eventQueue.add(async () => listener(...args));
|
286
|
-
});
|
287
|
-
}
|
288
|
-
|
289
|
-
emit(type: 'event', event: StormEvent): boolean;
|
290
|
-
emit(type: 'page_refs', event: { event: StormEventPage; references: ReferenceClassification[] }): boolean;
|
291
|
-
emit(eventName: string | symbol, ...args: any[]): boolean {
|
292
|
-
return super.emit(eventName, ...args);
|
293
|
-
}
|
294
|
-
|
295
|
-
public async generate() {
|
260
|
+
public async generate(prompt: UIPagePrompt, conversationId: string) {
|
296
261
|
const promises: Promise<void>[] = [];
|
297
|
-
const screenStream = await stormClient.createUIPage(
|
262
|
+
const screenStream = await stormClient.createUIPage(prompt, conversationId);
|
298
263
|
|
299
264
|
screenStream.on('data', (event: StormEvent) => {
|
300
265
|
if (event.type === 'PAGE') {
|
301
|
-
event.payload.conversationId =
|
302
|
-
|
303
|
-
promises.push(
|
304
|
-
(async () => {
|
305
|
-
const references = await this.resolveReferences(event.payload.content);
|
306
|
-
//console.log('Resolved references for page', references, event.payload);
|
307
|
-
this.emit('page_refs', {
|
308
|
-
event,
|
309
|
-
references,
|
310
|
-
});
|
311
|
-
})()
|
312
|
-
);
|
266
|
+
event.payload.conversationId = conversationId;
|
267
|
+
|
268
|
+
promises.push(this.processPageEventWithReferences(event));
|
313
269
|
return;
|
314
270
|
}
|
315
271
|
|
@@ -318,7 +274,6 @@ export class PageGenerator extends EventEmitter {
|
|
318
274
|
|
319
275
|
await screenStream.waitForDone();
|
320
276
|
await Promise.all(promises);
|
321
|
-
await this.eventQueue.wait();
|
322
277
|
}
|
323
278
|
|
324
279
|
private async resolveReferences(content: string) {
|
package/src/storm/page-utils.ts
CHANGED
@@ -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;
|