@duplojs/utils 1.6.0 → 1.6.2
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/dist/common/asserts.cjs +12 -0
- package/dist/common/asserts.d.ts +26 -0
- package/dist/common/asserts.mjs +12 -1
- package/dist/common/callThen.cjs +14 -0
- package/dist/common/callThen.d.ts +35 -0
- package/dist/common/callThen.mjs +12 -0
- package/dist/common/index.d.ts +2 -0
- package/dist/common/queue.cjs +90 -0
- package/dist/common/queue.d.ts +46 -0
- package/dist/common/queue.mjs +87 -0
- package/dist/flow/calledByNext.cjs +13 -0
- package/dist/flow/calledByNext.d.ts +47 -0
- package/dist/flow/calledByNext.mjs +11 -0
- package/dist/flow/debounce.cjs +16 -0
- package/dist/flow/debounce.d.ts +63 -0
- package/dist/flow/debounce.mjs +14 -0
- package/dist/flow/exec.cjs +101 -0
- package/dist/flow/exec.d.ts +4 -3
- package/dist/flow/exec.mjs +101 -0
- package/dist/flow/index.cjs +28 -0
- package/dist/flow/index.d.ts +5 -0
- package/dist/flow/index.mjs +10 -1
- package/dist/flow/initializer.d.ts +3 -3
- package/dist/flow/queue.cjs +19 -0
- package/dist/flow/queue.d.ts +47 -0
- package/dist/flow/queue.mjs +17 -0
- package/dist/flow/run.cjs +118 -0
- package/dist/flow/run.d.ts +6 -4
- package/dist/flow/run.mjs +113 -1
- package/dist/flow/theFlow/calledByNext.cjs +11 -0
- package/dist/flow/theFlow/calledByNext.d.ts +5 -0
- package/dist/flow/theFlow/calledByNext.mjs +8 -0
- package/dist/flow/theFlow/debounce.cjs +11 -0
- package/dist/flow/theFlow/debounce.d.ts +9 -0
- package/dist/flow/theFlow/debounce.mjs +8 -0
- package/dist/flow/theFlow/index.d.ts +11 -3
- package/dist/flow/theFlow/queue.cjs +11 -0
- package/dist/flow/theFlow/queue.d.ts +9 -0
- package/dist/flow/theFlow/queue.mjs +8 -0
- package/dist/flow/theFlow/throttling.cjs +11 -0
- package/dist/flow/theFlow/throttling.d.ts +10 -0
- package/dist/flow/theFlow/throttling.mjs +8 -0
- package/dist/flow/throttling.cjs +27 -0
- package/dist/flow/throttling.d.ts +60 -0
- package/dist/flow/throttling.mjs +25 -0
- package/dist/flow/toFunction.cjs +15 -0
- package/dist/flow/toFunction.d.ts +56 -0
- package/dist/flow/toFunction.mjs +13 -0
- package/dist/index.cjs +6 -0
- package/dist/index.mjs +3 -1
- package/dist/metadata.json +99 -0
- package/dist/object/types/getPropsWithValue.d.ts +1 -1
- package/dist/object/types/getPropsWithValueExtends.d.ts +1 -1
- package/package.json +1 -1
package/dist/common/asserts.cjs
CHANGED
|
@@ -18,6 +18,18 @@ function asserts(input, predicate) {
|
|
|
18
18
|
throw new AssertsError(input);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
function forwardAsserts(...args) {
|
|
22
|
+
if (args.length === 1) {
|
|
23
|
+
const [theFunction] = args;
|
|
24
|
+
return (input) => forwardAsserts(input, theFunction);
|
|
25
|
+
}
|
|
26
|
+
const [input, theFunction] = args;
|
|
27
|
+
if (!theFunction(input)) {
|
|
28
|
+
throw new AssertsError(input);
|
|
29
|
+
}
|
|
30
|
+
return input;
|
|
31
|
+
}
|
|
21
32
|
|
|
22
33
|
exports.AssertsError = AssertsError;
|
|
23
34
|
exports.asserts = asserts;
|
|
35
|
+
exports.forwardAsserts = forwardAsserts;
|
package/dist/common/asserts.d.ts
CHANGED
|
@@ -33,4 +33,30 @@ export declare class AssertsError extends AssertsError_base {
|
|
|
33
33
|
*
|
|
34
34
|
*/
|
|
35
35
|
export declare function asserts<GenericInput extends unknown, GenericPredicate extends GenericInput>(input: GenericInput, predicate: (input: GenericInput) => input is GenericPredicate): asserts input is GenericPredicate;
|
|
36
|
+
/**
|
|
37
|
+
* The forwardAsserts() function throws when a predicate fails and returns the validated input. It supports classic and curried forms, with type-guard and boolean predicates.
|
|
38
|
+
*
|
|
39
|
+
* **Supported call styles:**
|
|
40
|
+
* - Classic: `forwardAsserts(input, predicate)` → returns the narrowed input or throws
|
|
41
|
+
* - Curried: `forwardAsserts(predicate)(input)` → returns the validated input or throws
|
|
42
|
+
*
|
|
43
|
+
* It is useful when you want both runtime validation and a returned value you can keep using directly, especially inside `pipe()`. With a type guard predicate, the return type is narrowed. With a boolean predicate, the original input type is preserved. It throws an `AssertsError` with the failing input value.
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* const input = "demo" as string | number;
|
|
47
|
+
* const result = forwardAsserts(input, isType("string"));
|
|
48
|
+
*
|
|
49
|
+
* const pipedResult = pipe(
|
|
50
|
+
* "admin" as string | number,
|
|
51
|
+
* forwardAsserts(isType("string")),
|
|
52
|
+
* );
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @see https://utils.duplojs.dev/en/v1/api/common/forwardAsserts
|
|
56
|
+
*
|
|
57
|
+
*/
|
|
58
|
+
export declare function forwardAsserts<GenericInput extends unknown, GenericPredicate extends GenericInput>(predicate: (input: GenericInput) => input is GenericPredicate): (input: GenericInput) => GenericPredicate;
|
|
59
|
+
export declare function forwardAsserts<GenericInput extends unknown>(predicate: (input: GenericInput) => boolean): (input: GenericInput) => GenericInput;
|
|
60
|
+
export declare function forwardAsserts<GenericInput extends unknown, GenericPredicate extends GenericInput>(input: GenericInput, predicate: (input: GenericInput) => input is GenericPredicate): GenericPredicate;
|
|
61
|
+
export declare function forwardAsserts<GenericInput extends unknown>(input: GenericInput, predicate: (input: GenericInput) => boolean): GenericInput;
|
|
36
62
|
export {};
|
package/dist/common/asserts.mjs
CHANGED
|
@@ -16,5 +16,16 @@ function asserts(input, predicate) {
|
|
|
16
16
|
throw new AssertsError(input);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
function forwardAsserts(...args) {
|
|
20
|
+
if (args.length === 1) {
|
|
21
|
+
const [theFunction] = args;
|
|
22
|
+
return (input) => forwardAsserts(input, theFunction);
|
|
23
|
+
}
|
|
24
|
+
const [input, theFunction] = args;
|
|
25
|
+
if (!theFunction(input)) {
|
|
26
|
+
throw new AssertsError(input);
|
|
27
|
+
}
|
|
28
|
+
return input;
|
|
29
|
+
}
|
|
19
30
|
|
|
20
|
-
export { AssertsError, asserts };
|
|
31
|
+
export { AssertsError, asserts, forwardAsserts };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* {@include common/callThen/index.md}
|
|
5
|
+
*/
|
|
6
|
+
function callThen(input, TheFunction) {
|
|
7
|
+
if (input instanceof Promise) {
|
|
8
|
+
return input
|
|
9
|
+
.then(TheFunction);
|
|
10
|
+
}
|
|
11
|
+
return TheFunction(input);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.callThen = callThen;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The callThen() function applies a callback to a value immediately, or waits for a promise input before running the same callback.
|
|
3
|
+
*
|
|
4
|
+
* Supported call styles:
|
|
5
|
+
* - Classic: `callThen(input, callback)` → returns a value or a promise depending on the input and callback
|
|
6
|
+
*
|
|
7
|
+
* Behavior:
|
|
8
|
+
* - direct values call the callback synchronously
|
|
9
|
+
* - `Promise` inputs call the callback through `.then(...)`
|
|
10
|
+
* - callback promises are preserved for direct values and flattened for `Promise` inputs
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const syncResult = callThen(
|
|
14
|
+
* 2,
|
|
15
|
+
* (value) => value * 3,
|
|
16
|
+
* );
|
|
17
|
+
* // type: number
|
|
18
|
+
*
|
|
19
|
+
* const asyncFromSync = callThen(
|
|
20
|
+
* "duplo",
|
|
21
|
+
* async(value) => Promise.resolve(value.toUpperCase()),
|
|
22
|
+
* );
|
|
23
|
+
* // type: Promise<string>
|
|
24
|
+
*
|
|
25
|
+
* const asyncFromPromise = callThen(
|
|
26
|
+
* Promise.resolve({
|
|
27
|
+
* count: 2,
|
|
28
|
+
* }),
|
|
29
|
+
* ({ count }) => count + 1,
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @see https://utils.duplojs.dev/en/v1/api/common/callThen
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
export declare function callThen<GenericInput extends unknown, GenericOutput extends unknown>(input: GenericInput, TheFunction: (input: Awaited<GenericInput>) => GenericOutput): GenericInput extends Promise<unknown> ? Promise<Awaited<GenericOutput>> : GenericOutput;
|
package/dist/common/index.d.ts
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var callThen = require('./callThen.cjs');
|
|
4
|
+
var externalPromise = require('./externalPromise.cjs');
|
|
5
|
+
var kind = require('./kind.cjs');
|
|
6
|
+
var create = require('../either/left/create.cjs');
|
|
7
|
+
|
|
8
|
+
const queueKind = kind.createKind("queue");
|
|
9
|
+
/**
|
|
10
|
+
* {@include common/queue/index.md}
|
|
11
|
+
*/
|
|
12
|
+
function createQueue(params) {
|
|
13
|
+
const concurrency = params?.concurrency === undefined || params.concurrency < 1
|
|
14
|
+
? 1
|
|
15
|
+
: params.concurrency;
|
|
16
|
+
let quantityRunning = 0;
|
|
17
|
+
let firstElement = undefined;
|
|
18
|
+
function add(theFunction) {
|
|
19
|
+
const externalPromise$1 = externalPromise.createExternalPromise();
|
|
20
|
+
const preparedFunction = () => {
|
|
21
|
+
quantityRunning++;
|
|
22
|
+
if (firstElement?.theFunction === preparedFunction) {
|
|
23
|
+
if (firstElement === firstElement.next) {
|
|
24
|
+
firstElement = undefined;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const newFirst = firstElement.next;
|
|
28
|
+
const last = firstElement.previous;
|
|
29
|
+
newFirst.previous = last;
|
|
30
|
+
last.next = newFirst;
|
|
31
|
+
firstElement = newFirst;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let result = undefined;
|
|
35
|
+
try {
|
|
36
|
+
const MaybePromise = theFunction();
|
|
37
|
+
result = MaybePromise instanceof Promise
|
|
38
|
+
? MaybePromise.catch((error) => create.left("execution-error", error))
|
|
39
|
+
: MaybePromise;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
result = create.left("execution-error", error);
|
|
43
|
+
}
|
|
44
|
+
callThen.callThen(result, (output) => {
|
|
45
|
+
externalPromise$1.resolve(output);
|
|
46
|
+
quantityRunning--;
|
|
47
|
+
firstElement?.theFunction();
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
if (quantityRunning < concurrency) {
|
|
51
|
+
void preparedFunction();
|
|
52
|
+
}
|
|
53
|
+
else if (firstElement === undefined) {
|
|
54
|
+
firstElement = {
|
|
55
|
+
theFunction: preparedFunction,
|
|
56
|
+
next: undefined,
|
|
57
|
+
previous: undefined,
|
|
58
|
+
};
|
|
59
|
+
firstElement.next = firstElement;
|
|
60
|
+
firstElement.previous = firstElement;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const oldLast = firstElement.previous;
|
|
64
|
+
const newLastElement = {
|
|
65
|
+
theFunction: preparedFunction,
|
|
66
|
+
next: firstElement,
|
|
67
|
+
previous: firstElement.previous,
|
|
68
|
+
};
|
|
69
|
+
oldLast.next = newLastElement;
|
|
70
|
+
firstElement.previous = newLastElement;
|
|
71
|
+
}
|
|
72
|
+
return externalPromise$1.promise;
|
|
73
|
+
}
|
|
74
|
+
function addExternal() {
|
|
75
|
+
const externalPromiseToStart = externalPromise.createExternalPromise();
|
|
76
|
+
const externalPromiseToFinish = externalPromise.createExternalPromise();
|
|
77
|
+
void add(() => {
|
|
78
|
+
externalPromiseToStart.resolve(externalPromiseToFinish.resolve);
|
|
79
|
+
return externalPromiseToFinish.promise;
|
|
80
|
+
});
|
|
81
|
+
return externalPromiseToStart.promise;
|
|
82
|
+
}
|
|
83
|
+
return queueKind.setTo({
|
|
84
|
+
add,
|
|
85
|
+
addExternal,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exports.createQueue = createQueue;
|
|
90
|
+
exports.queueKind = queueKind;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Kind } from "./kind";
|
|
2
|
+
import * as DEither from "../either";
|
|
3
|
+
export declare const queueKind: import("./kind").KindHandler<import("./kind").KindDefinition<"queue", unknown>>;
|
|
4
|
+
export interface Queue extends Kind<typeof queueKind.definition> {
|
|
5
|
+
add<GenericOutput extends unknown>(theFunction: () => GenericOutput): Promise<Awaited<GenericOutput> | DEither.Left<"execution-error", unknown>>;
|
|
6
|
+
addExternal(): Promise<() => void>;
|
|
7
|
+
}
|
|
8
|
+
export interface CreateQueueParams {
|
|
9
|
+
concurrency?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The createQueue() function creates a FIFO queue object that limits how many tasks run at the same time and resolves each task result as a promise.
|
|
13
|
+
*
|
|
14
|
+
* Supported call styles:
|
|
15
|
+
* - Classic: `createQueue()` → returns a queue object
|
|
16
|
+
* - Classic with options: `createQueue({ concurrency })` → returns a queue object
|
|
17
|
+
*
|
|
18
|
+
* Behavior:
|
|
19
|
+
* - `queue.add(callback)` starts the callback immediately when a slot is available, otherwise it enqueues it
|
|
20
|
+
* - task results are always exposed through a promise
|
|
21
|
+
* - thrown errors and rejected promises are converted to `DEither.left("execution-error", error)`
|
|
22
|
+
* - `queue.addExternal()` reserves one slot and resolves with a release function
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* const defaultQueue = createQueue();
|
|
26
|
+
* const numberResult = await defaultQueue.add(() => 42);
|
|
27
|
+
* // type: 42 | Left<"execution-error", unknown>
|
|
28
|
+
*
|
|
29
|
+
* const serialQueue = createQueue({
|
|
30
|
+
* concurrency: 1,
|
|
31
|
+
* });
|
|
32
|
+
* const textResult = await serialQueue.add(() => "hello" as const);
|
|
33
|
+
* // type: "hello" | Left<"execution-error", unknown>
|
|
34
|
+
*
|
|
35
|
+
* const asyncQueue = createQueue();
|
|
36
|
+
* const asyncResult = await asyncQueue.add(
|
|
37
|
+
* async() => Promise.resolve("done" as const),
|
|
38
|
+
* );
|
|
39
|
+
* // type: "done" | Left<"execution-error", unknown>
|
|
40
|
+
*
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @see https://utils.duplojs.dev/en/v1/api/common/queue
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
46
|
+
export declare function createQueue(params?: CreateQueueParams): Queue;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { callThen } from './callThen.mjs';
|
|
2
|
+
import { createExternalPromise } from './externalPromise.mjs';
|
|
3
|
+
import { createKind } from './kind.mjs';
|
|
4
|
+
import { left } from '../either/left/create.mjs';
|
|
5
|
+
|
|
6
|
+
const queueKind = createKind("queue");
|
|
7
|
+
/**
|
|
8
|
+
* {@include common/queue/index.md}
|
|
9
|
+
*/
|
|
10
|
+
function createQueue(params) {
|
|
11
|
+
const concurrency = params?.concurrency === undefined || params.concurrency < 1
|
|
12
|
+
? 1
|
|
13
|
+
: params.concurrency;
|
|
14
|
+
let quantityRunning = 0;
|
|
15
|
+
let firstElement = undefined;
|
|
16
|
+
function add(theFunction) {
|
|
17
|
+
const externalPromise = createExternalPromise();
|
|
18
|
+
const preparedFunction = () => {
|
|
19
|
+
quantityRunning++;
|
|
20
|
+
if (firstElement?.theFunction === preparedFunction) {
|
|
21
|
+
if (firstElement === firstElement.next) {
|
|
22
|
+
firstElement = undefined;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const newFirst = firstElement.next;
|
|
26
|
+
const last = firstElement.previous;
|
|
27
|
+
newFirst.previous = last;
|
|
28
|
+
last.next = newFirst;
|
|
29
|
+
firstElement = newFirst;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let result = undefined;
|
|
33
|
+
try {
|
|
34
|
+
const MaybePromise = theFunction();
|
|
35
|
+
result = MaybePromise instanceof Promise
|
|
36
|
+
? MaybePromise.catch((error) => left("execution-error", error))
|
|
37
|
+
: MaybePromise;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
result = left("execution-error", error);
|
|
41
|
+
}
|
|
42
|
+
callThen(result, (output) => {
|
|
43
|
+
externalPromise.resolve(output);
|
|
44
|
+
quantityRunning--;
|
|
45
|
+
firstElement?.theFunction();
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
if (quantityRunning < concurrency) {
|
|
49
|
+
void preparedFunction();
|
|
50
|
+
}
|
|
51
|
+
else if (firstElement === undefined) {
|
|
52
|
+
firstElement = {
|
|
53
|
+
theFunction: preparedFunction,
|
|
54
|
+
next: undefined,
|
|
55
|
+
previous: undefined,
|
|
56
|
+
};
|
|
57
|
+
firstElement.next = firstElement;
|
|
58
|
+
firstElement.previous = firstElement;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const oldLast = firstElement.previous;
|
|
62
|
+
const newLastElement = {
|
|
63
|
+
theFunction: preparedFunction,
|
|
64
|
+
next: firstElement,
|
|
65
|
+
previous: firstElement.previous,
|
|
66
|
+
};
|
|
67
|
+
oldLast.next = newLastElement;
|
|
68
|
+
firstElement.previous = newLastElement;
|
|
69
|
+
}
|
|
70
|
+
return externalPromise.promise;
|
|
71
|
+
}
|
|
72
|
+
function addExternal() {
|
|
73
|
+
const externalPromiseToStart = createExternalPromise();
|
|
74
|
+
const externalPromiseToFinish = createExternalPromise();
|
|
75
|
+
void add(() => {
|
|
76
|
+
externalPromiseToStart.resolve(externalPromiseToFinish.resolve);
|
|
77
|
+
return externalPromiseToFinish.promise;
|
|
78
|
+
});
|
|
79
|
+
return externalPromiseToStart.promise;
|
|
80
|
+
}
|
|
81
|
+
return queueKind.setTo({
|
|
82
|
+
add,
|
|
83
|
+
addExternal,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { createQueue, queueKind };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var calledByNext$1 = require('./theFlow/calledByNext.cjs');
|
|
4
|
+
|
|
5
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
6
|
+
/**
|
|
7
|
+
* {@include flow/calledByNext/index.md}
|
|
8
|
+
*/
|
|
9
|
+
async function* calledByNext(theFunction) {
|
|
10
|
+
yield calledByNext$1.createCalledByNext(theFunction);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
exports.calledByNext = calledByNext;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers a callback that the flow runner can execute before the next overlapping async run of the same flow continues.
|
|
3
|
+
*
|
|
4
|
+
* **Supported call styles:**
|
|
5
|
+
* - Classic: `calledByNext(theFunction)` -> yields a `called-by-next` effect
|
|
6
|
+
*
|
|
7
|
+
* `calledByNext` declares a side callback on the current flow execution.
|
|
8
|
+
* When the same async flow starts again while a previous callback is still registered, the runner executes the previous callback and replaces it with the new one.
|
|
9
|
+
* This helper is mainly useful for cross-run invalidation or deferred refresh logic tied to async flow re-entry.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const loadUsersFlow = F.create(
|
|
13
|
+
* async function *(page: number) {
|
|
14
|
+
* const controller = new AbortController();
|
|
15
|
+
*
|
|
16
|
+
* yield *F.calledByNext(() => void controller.abort());
|
|
17
|
+
*
|
|
18
|
+
* const response = await fetch(
|
|
19
|
+
* `/api/users?page=${page}`,
|
|
20
|
+
* {
|
|
21
|
+
* signal: controller.signal,
|
|
22
|
+
* },
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* return response.json();
|
|
26
|
+
* },
|
|
27
|
+
* );
|
|
28
|
+
* const loadUsers = F.toFunction(loadUsersFlow);
|
|
29
|
+
*
|
|
30
|
+
* const firstRequest = loadUsers(1);
|
|
31
|
+
* const secondRequest = loadUsers(2);
|
|
32
|
+
* // The second call aborts the first pending fetch.
|
|
33
|
+
*
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* - `calledByNext` is only observable through the async flow runner behavior
|
|
38
|
+
* - Use a stable flow reference such as `F.create(...)` or `F.toFunction(...)` when you expect cross-run behavior
|
|
39
|
+
* - If the same flow execution yields `calledByNext(...)` multiple times, only the first yielded effect is applied by the runner
|
|
40
|
+
*
|
|
41
|
+
* @see [`F.run`](https://utils.duplojs.dev/en/v1/api/flow/run) For the runtime behavior that consumes this effect
|
|
42
|
+
* @see https://utils.duplojs.dev/en/v1/api/flow/calledByNext
|
|
43
|
+
*
|
|
44
|
+
* @namespace F
|
|
45
|
+
*
|
|
46
|
+
*/
|
|
47
|
+
export declare function calledByNext<GenericOutput extends unknown>(theFunction: () => GenericOutput): AsyncGenerator<import("./theFlow").CalledByNext<GenericOutput>, void, unknown>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createCalledByNext } from './theFlow/calledByNext.mjs';
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
4
|
+
/**
|
|
5
|
+
* {@include flow/calledByNext/index.md}
|
|
6
|
+
*/
|
|
7
|
+
async function* calledByNext(theFunction) {
|
|
8
|
+
yield createCalledByNext(theFunction);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { calledByNext };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var debounce$1 = require('./theFlow/debounce.cjs');
|
|
4
|
+
|
|
5
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
6
|
+
/**
|
|
7
|
+
* {@include flow/debounce/index.md}
|
|
8
|
+
*/
|
|
9
|
+
async function* debounce(time, params) {
|
|
10
|
+
yield debounce$1.createDebounce({
|
|
11
|
+
time,
|
|
12
|
+
returnValue: params?.returnValue,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
exports.debounce = debounce;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delays a flow execution and cancels the previous run when a new one arrives before the delay ends.
|
|
3
|
+
*
|
|
4
|
+
* **Supported call styles:**
|
|
5
|
+
* - Classic: `debounce(time)` -> yields a debounce effect
|
|
6
|
+
* - Classic with options: `debounce(time, params)` -> yields a debounce effect with a fallback return value
|
|
7
|
+
*
|
|
8
|
+
* `debounce` lets the runner wait before continuing the current run of the same flow.
|
|
9
|
+
* If another run starts during that delay, the previous run is cancelled and can return `params.returnValue`.
|
|
10
|
+
* This makes it useful for search inputs, refresh triggers, or any async flow where only the latest call should continue.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const searchFlow = F.create(
|
|
14
|
+
* async function *(query: string) {
|
|
15
|
+
* yield *F.debounce(
|
|
16
|
+
* 300,
|
|
17
|
+
* { returnValue: "skipped" as const },
|
|
18
|
+
* );
|
|
19
|
+
* return Promise.resolve(query);
|
|
20
|
+
* },
|
|
21
|
+
* );
|
|
22
|
+
* const runSearch = F.toFunction(searchFlow);
|
|
23
|
+
*
|
|
24
|
+
* void runSearch("first"); // Promise<"first" | "skipped">
|
|
25
|
+
*
|
|
26
|
+
* void runSearch("second"); // Promise<"second" | "skipped">
|
|
27
|
+
*
|
|
28
|
+
* const refreshFlow = F.create(
|
|
29
|
+
* async function *(section: string) {
|
|
30
|
+
* yield *F.debounce(500);
|
|
31
|
+
* return Promise.resolve(section);
|
|
32
|
+
* },
|
|
33
|
+
* );
|
|
34
|
+
* const runRefresh = F.toFunction(refreshFlow);
|
|
35
|
+
* await runRefresh("users"); // Promise<"users" | undefined>
|
|
36
|
+
*
|
|
37
|
+
* const saveFlow = F.create(
|
|
38
|
+
* async function *(name: string) {
|
|
39
|
+
* yield *F.debounce(
|
|
40
|
+
* 200,
|
|
41
|
+
* { returnValue: "cancelled" as const },
|
|
42
|
+
* );
|
|
43
|
+
* return Promise.resolve(`saved:${name}` as const);
|
|
44
|
+
* },
|
|
45
|
+
* );
|
|
46
|
+
* const runSave = F.toFunction(saveFlow);
|
|
47
|
+
* await runSave("alice"); // Promise<`saved:${string}` | "cancelled">
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* - `debounce` is an async generator because the runner always waits before continuing the preserved run
|
|
52
|
+
* - Debounce state is attached to the flow reference, so examples should reuse the same created flow or wrapped function
|
|
53
|
+
* - If the same flow execution yields `debounce(...)` multiple times, only the first yielded effect is applied by the runner
|
|
54
|
+
*
|
|
55
|
+
* @see [`F.run`](https://utils.duplojs.dev/en/v1/api/flow/run) For the debounce behavior implemented by the runner
|
|
56
|
+
* @see https://utils.duplojs.dev/en/v1/api/flow/debounce
|
|
57
|
+
*
|
|
58
|
+
* @namespace F
|
|
59
|
+
*
|
|
60
|
+
*/
|
|
61
|
+
export declare function debounce<GenericValue extends unknown = undefined>(time: number, params?: {
|
|
62
|
+
returnValue?: GenericValue;
|
|
63
|
+
}): AsyncGenerator<import("./theFlow").Debounce<GenericValue | undefined>, void, unknown>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createDebounce } from './theFlow/debounce.mjs';
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
4
|
+
/**
|
|
5
|
+
* {@include flow/debounce/index.md}
|
|
6
|
+
*/
|
|
7
|
+
async function* debounce(time, params) {
|
|
8
|
+
yield createDebounce({
|
|
9
|
+
time,
|
|
10
|
+
returnValue: params?.returnValue,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { debounce };
|
package/dist/flow/exec.cjs
CHANGED
|
@@ -3,12 +3,19 @@
|
|
|
3
3
|
var index = require('./theFlow/index.cjs');
|
|
4
4
|
var defer = require('./theFlow/defer.cjs');
|
|
5
5
|
var finalizer = require('./theFlow/finalizer.cjs');
|
|
6
|
+
var run = require('./run.cjs');
|
|
6
7
|
var justExec = require('../common/justExec.cjs');
|
|
7
8
|
var _break = require('./theFlow/break.cjs');
|
|
8
9
|
var exit = require('./theFlow/exit.cjs');
|
|
9
10
|
var step = require('./theFlow/step.cjs');
|
|
10
11
|
var injection = require('./theFlow/injection.cjs');
|
|
11
12
|
var dependence = require('./theFlow/dependence.cjs');
|
|
13
|
+
var throttling = require('./theFlow/throttling.cjs');
|
|
14
|
+
var externalPromise = require('../common/externalPromise.cjs');
|
|
15
|
+
var calledByNext = require('./theFlow/calledByNext.cjs');
|
|
16
|
+
var queue = require('./theFlow/queue.cjs');
|
|
17
|
+
var queue$1 = require('../common/queue.cjs');
|
|
18
|
+
var debounce = require('./theFlow/debounce.cjs');
|
|
12
19
|
var forward = require('../common/forward.cjs');
|
|
13
20
|
|
|
14
21
|
/**
|
|
@@ -17,6 +24,10 @@ var forward = require('../common/forward.cjs');
|
|
|
17
24
|
function exec(theFlow, ...[params]) {
|
|
18
25
|
let result = undefined;
|
|
19
26
|
let deferFunctions = undefined;
|
|
27
|
+
let alreadyUseThrottling = undefined;
|
|
28
|
+
let alreadyUseDebounce = undefined;
|
|
29
|
+
let alreadyUseCalledByNext = undefined;
|
|
30
|
+
let alreadyUseQueue = undefined;
|
|
20
31
|
const generator = justExec.justExec(() => {
|
|
21
32
|
if (Symbol.asyncIterator in theFlow || Symbol.iterator in theFlow) {
|
|
22
33
|
return forward.forward(theFlow);
|
|
@@ -64,10 +75,90 @@ function exec(theFlow, ...[params]) {
|
|
|
64
75
|
injectionProperties.inject(params.dependencies[dependenceName]);
|
|
65
76
|
}
|
|
66
77
|
}
|
|
78
|
+
else if (throttling.throttlingKind.has(result.value)) {
|
|
79
|
+
if (alreadyUseThrottling) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
alreadyUseThrottling = true;
|
|
83
|
+
const { time, keepLast, returnValue } = throttling.throttlingKind.getValue(result.value);
|
|
84
|
+
const lastTime = run.throttlingLastTimeWeakStore.get(theFlow);
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
run.throttlingLastTimeWeakStore.set(theFlow, now);
|
|
87
|
+
if (typeof lastTime === "number" && (lastTime + time) > now) {
|
|
88
|
+
if (keepLast === true) {
|
|
89
|
+
const resumer = run.throttlingResumerWeakStore.get(theFlow);
|
|
90
|
+
resumer?.(false);
|
|
91
|
+
const externalPromise$1 = externalPromise.createExternalPromise();
|
|
92
|
+
run.throttlingResumerWeakStore.set(theFlow, externalPromise$1.resolve);
|
|
93
|
+
if (await externalPromise$1.promise) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
result = await generator.return(returnValue);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
else if (keepLast === true) {
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
const resumer = run.throttlingResumerWeakStore.get(theFlow);
|
|
103
|
+
resumer?.(true);
|
|
104
|
+
}, time);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (calledByNext.calledByNextKind.has(result.value)) {
|
|
108
|
+
if (alreadyUseCalledByNext) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
alreadyUseCalledByNext = calledByNext.calledByNextKind.getValue(result.value);
|
|
112
|
+
const lastFunction = run.calledByNextFunctionWeakStore.get(theFlow);
|
|
113
|
+
const lastResult = lastFunction?.();
|
|
114
|
+
if (lastResult instanceof Promise) {
|
|
115
|
+
await lastResult;
|
|
116
|
+
}
|
|
117
|
+
run.calledByNextFunctionWeakStore.set(theFlow, alreadyUseCalledByNext);
|
|
118
|
+
}
|
|
119
|
+
else if (queue.queueKind.has(result.value)) {
|
|
120
|
+
if (alreadyUseQueue) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const { concurrency, injectResolver } = queue.queueKind.getValue(result.value);
|
|
124
|
+
let queue$2 = run.queuesWeakStore.get(theFlow);
|
|
125
|
+
if (queue$2 === undefined) {
|
|
126
|
+
queue$2 = queue$1.createQueue({ concurrency });
|
|
127
|
+
run.queuesWeakStore.set(theFlow, queue$2);
|
|
128
|
+
}
|
|
129
|
+
alreadyUseQueue = await queue$2.addExternal();
|
|
130
|
+
injectResolver(alreadyUseQueue);
|
|
131
|
+
}
|
|
132
|
+
else if (debounce.debounceKind.has(result.value)) {
|
|
133
|
+
if (alreadyUseDebounce) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
alreadyUseDebounce = true;
|
|
137
|
+
const { time, returnValue } = debounce.debounceKind.getValue(result.value);
|
|
138
|
+
const lastTimeout = run.debounceTimeoutIdWeakStore.get(theFlow);
|
|
139
|
+
clearTimeout(lastTimeout);
|
|
140
|
+
const lastResumer = run.debounceResumerWeakStore.get(theFlow);
|
|
141
|
+
lastResumer?.(false);
|
|
142
|
+
const externalPromise$1 = externalPromise.createExternalPromise();
|
|
143
|
+
run.debounceTimeoutIdWeakStore.set(theFlow, setTimeout(() => void externalPromise$1.resolve(true), time));
|
|
144
|
+
run.debounceResumerWeakStore.set(theFlow, externalPromise$1.resolve);
|
|
145
|
+
if (await externalPromise$1.promise) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
result = await generator.return(returnValue);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
67
151
|
} while (true);
|
|
68
152
|
return result.value;
|
|
69
153
|
}
|
|
70
154
|
finally {
|
|
155
|
+
if (alreadyUseCalledByNext
|
|
156
|
+
&& run.calledByNextFunctionWeakStore.get(theFlow) === alreadyUseCalledByNext) {
|
|
157
|
+
run.calledByNextFunctionWeakStore.delete(theFlow);
|
|
158
|
+
}
|
|
159
|
+
if (alreadyUseQueue) {
|
|
160
|
+
alreadyUseQueue();
|
|
161
|
+
}
|
|
71
162
|
await generator.return(undefined);
|
|
72
163
|
if (deferFunctions) {
|
|
73
164
|
await Promise.all(deferFunctions.map(justExec.justExec));
|
|
@@ -110,6 +201,16 @@ function exec(theFlow, ...[params]) {
|
|
|
110
201
|
injectionProperties.inject(params.dependencies[dependenceName]);
|
|
111
202
|
}
|
|
112
203
|
}
|
|
204
|
+
else if (throttling.throttlingKind.has(result.value)) {
|
|
205
|
+
const { time, returnValue } = throttling.throttlingKind.getValue(result.value);
|
|
206
|
+
const lastTime = run.throttlingLastTimeWeakStore.get(theFlow);
|
|
207
|
+
const now = Date.now();
|
|
208
|
+
run.throttlingLastTimeWeakStore.set(theFlow, now);
|
|
209
|
+
if (typeof lastTime === "number" && (lastTime + time) > now) {
|
|
210
|
+
result = generator.return(returnValue);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
113
214
|
} while (true);
|
|
114
215
|
return result.value;
|
|
115
216
|
}
|