performance-helpers 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/package.json +146 -1
- package/src/index.js +1 -0
- package/.eslintrc.cjs +0 -22
- package/.nojekyll +0 -0
- package/.prettierrc +0 -6
- package/CONTRIBUTING.md +0 -178
- package/assets/1_Caching.md +0 -4
- package/assets/2_Parallelizing.md +0 -18
- package/assets/3_Logging.md +0 -3
- package/assets/404.md +0 -3
- package/assets/4_Utils.md +0 -10
- package/assets/logo.png +0 -0
- package/assets/navigation.md +0 -10
- package/bench/README.md +0 -97
- package/bench/results.json +0 -94
- package/bench/results.md +0 -233
- package/bench/run.js +0 -2639
- package/bench/worker.js +0 -43
- package/docs/README.md +0 -38
- package/docs/docs-typedoc.json +0 -38714
- package/docs/helpers/constants/README.md +0 -34
- package/docs/helpers/constants/variables/DEFAULT_AUTOSCALE_BACKOFF_MAX_MULTIPLIER.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_AUTOSCALE_COOLDOWN_MS.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_AUTOSCALE_INTERVAL_MS.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_AUTOSCALE_MIN_INTERVAL_MS.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_BACKPRESSURE_QUEUE_CAPACITY.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_BACKPRESSURE_REFILL_INTERVAL_MS.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_BATCH_MAX_SIZE.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_CACHE_DEFAULT_TTL_MS.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_CACHE_MAX_POOL_SIZE.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_CACHE_MAX_WEIGHT_BYTES.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_HISTOGRAM_BUCKET_COUNT.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_HISTOGRAM_MAX_VALUE.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_MAX_CLEANUP_PER_TICK.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_QUEUE_CAPACITY.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_REAPER_MIN_INTERVAL_MS.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_REFILL_INTERVAL_MS.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_RETRY_BASE_DELAY_MS.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_RETRY_MAX_DELAY_MS.md +0 -9
- package/docs/helpers/constants/variables/DEFAULT_TIMEOUT_MS.md +0 -9
- package/docs/helpers/constants/variables/ENCODE_CACHE_LARGE_KEY_LENGTH.md +0 -9
- package/docs/helpers/constants/variables/MAX_DEEP_EQUAL_DEPTH.md +0 -9
- package/docs/helpers/constants/variables/MS_PER_MIN.md +0 -9
- package/docs/helpers/constants/variables/MS_PER_SEC.md +0 -9
- package/docs/helpers/constants/variables/default.md +0 -103
- package/docs/helpers/jsdoc-types/README.md +0 -33
- package/docs/helpers/jsdoc-types/interfaces/BufferDecoder.md +0 -23
- package/docs/helpers/jsdoc-types/interfaces/BufferEncoder.md +0 -23
- package/docs/helpers/jsdoc-types/interfaces/CacheNode.md +0 -43
- package/docs/helpers/jsdoc-types/interfaces/CommonPoolOptions.md +0 -31
- package/docs/helpers/jsdoc-types/interfaces/PendingResponseEntry.md +0 -51
- package/docs/helpers/jsdoc-types/interfaces/PostMessageOptions.md +0 -39
- package/docs/helpers/jsdoc-types/interfaces/PowerBatchOptions.md +0 -13
- package/docs/helpers/jsdoc-types/interfaces/PowerCacheOptions.md +0 -115
- package/docs/helpers/jsdoc-types/interfaces/PowerChunkingOptions.md +0 -31
- package/docs/helpers/jsdoc-types/interfaces/PowerCircuitOptions.md +0 -45
- package/docs/helpers/jsdoc-types/interfaces/PowerDeadlineOptions.md +0 -101
- package/docs/helpers/jsdoc-types/interfaces/PowerDeferOptions.md +0 -13
- package/docs/helpers/jsdoc-types/interfaces/PowerEventBusOptions.md +0 -19
- package/docs/helpers/jsdoc-types/interfaces/PowerLatchOptions.md +0 -23
- package/docs/helpers/jsdoc-types/interfaces/PowerLoggerOptions.md +0 -51
- package/docs/helpers/jsdoc-types/interfaces/PowerObserverOptions.md +0 -25
- package/docs/helpers/jsdoc-types/interfaces/PowerPoolOptions.md +0 -85
- package/docs/helpers/jsdoc-types/interfaces/PowerQueueOptions.md +0 -13
- package/docs/helpers/jsdoc-types/interfaces/PowerRetryOptions.md +0 -83
- package/docs/helpers/jsdoc-types/interfaces/PowerSlidingWindowOptions.md +0 -19
- package/docs/helpers/jsdoc-types/interfaces/PowerTTLMapOptions.md +0 -27
- package/docs/helpers/jsdoc-types/interfaces/PowerThrottleOptions.md +0 -31
- package/docs/helpers/jsdoc-types/interfaces/WorkerObj.md +0 -55
- package/docs/helpers/powerBackpressure/README.md +0 -17
- package/docs/helpers/powerBackpressure/classes/PowerBackpressure.md +0 -368
- package/docs/helpers/powerBatch/README.md +0 -17
- package/docs/helpers/powerBatch/classes/PowerBatch.md +0 -139
- package/docs/helpers/powerBuffer/README.md +0 -26
- package/docs/helpers/powerBuffer/functions/b2o.md +0 -25
- package/docs/helpers/powerBuffer/functions/o2b.md +0 -23
- package/docs/helpers/powerBuffer/functions/o2u8.md +0 -33
- package/docs/helpers/powerBuffer/functions/u82o.md +0 -30
- package/docs/helpers/powerBulkhead/README.md +0 -17
- package/docs/helpers/powerBulkhead/classes/PowerBulkhead.md +0 -302
- package/docs/helpers/powerCache/README.md +0 -29
- package/docs/helpers/powerCache/classes/PowerCache.md +0 -933
- package/docs/helpers/powerCache/classes/PowerMemoizer.md +0 -244
- package/docs/helpers/powerCache/classes/PowerTimedCache.md +0 -302
- package/docs/helpers/powerCache/functions/simpleArgsKey.md +0 -31
- package/docs/helpers/powerChunking/README.md +0 -17
- package/docs/helpers/powerChunking/classes/PowerChunker.md +0 -78
- package/docs/helpers/powerCircuit/README.md +0 -23
- package/docs/helpers/powerCircuit/classes/PowerCircuit.md +0 -167
- package/docs/helpers/powerDeadline/README.md +0 -23
- package/docs/helpers/powerDeadline/classes/PowerDeadline.md +0 -88
- package/docs/helpers/powerDefer/README.md +0 -17
- package/docs/helpers/powerDefer/classes/PowerDefer.md +0 -134
- package/docs/helpers/powerEventBus/README.md +0 -23
- package/docs/helpers/powerEventBus/classes/PowerEventBus.md +0 -330
- package/docs/helpers/powerHistogram/README.md +0 -17
- package/docs/helpers/powerHistogram/classes/PowerHistogram.md +0 -285
- package/docs/helpers/powerLatch/README.md +0 -17
- package/docs/helpers/powerLatch/classes/PowerLatch.md +0 -264
- package/docs/helpers/powerLogger/README.md +0 -17
- package/docs/helpers/powerLogger/classes/PowerLogger.md +0 -290
- package/docs/helpers/powerObserver/README.md +0 -23
- package/docs/helpers/powerObserver/classes/PowerObserver.md +0 -213
- package/docs/helpers/powerPermitGate/README.md +0 -11
- package/docs/helpers/powerPermitGate/classes/PowerPermitGate.md +0 -248
- package/docs/helpers/powerPool/README.md +0 -36
- package/docs/helpers/powerPool/classes/PowerPool.md +0 -973
- package/docs/helpers/powerPool/classes/PowerPoolShutdownError.md +0 -67
- package/docs/helpers/powerQueue/README.md +0 -11
- package/docs/helpers/powerQueue/classes/PowerQueue.md +0 -302
- package/docs/helpers/powerRateLimit/README.md +0 -17
- package/docs/helpers/powerRateLimit/classes/PowerRateLimit.md +0 -187
- package/docs/helpers/powerRetry/README.md +0 -23
- package/docs/helpers/powerRetry/classes/PowerRetry.md +0 -106
- package/docs/helpers/powerScheduler/README.md +0 -11
- package/docs/helpers/powerScheduler/classes/PowerScheduler.md +0 -135
- package/docs/helpers/powerSemaphore/README.md +0 -17
- package/docs/helpers/powerSemaphore/classes/PowerSemaphore.md +0 -173
- package/docs/helpers/powerSlidingWindow/README.md +0 -11
- package/docs/helpers/powerSlidingWindow/classes/PowerSlidingWindow.md +0 -83
- package/docs/helpers/powerSubscriberSet/README.md +0 -15
- package/docs/helpers/powerSubscriberSet/classes/PowerSubscriberSet.md +0 -251
- package/docs/helpers/powerSubscriberSet/functions/cleanupWeakRefs.md +0 -21
- package/docs/helpers/powerTTLMap/README.md +0 -17
- package/docs/helpers/powerTTLMap/classes/PowerTTLMap.md +0 -326
- package/docs/helpers/powerThrottle/README.md +0 -17
- package/docs/helpers/powerThrottle/classes/PowerThrottle.md +0 -216
- package/docs/index/README.md +0 -205
- package/docs/utils/errors/README.md +0 -12
- package/docs/utils/errors/functions/formatErrorObj.md +0 -30
- package/docs/utils/errors/functions/normalizeError.md +0 -50
- package/docs/utils/now/README.md +0 -19
- package/docs/utils/now/functions/measureAsync.md +0 -37
- package/docs/utils/now/functions/measureSync.md +0 -54
- package/docs/utils/now/functions/nowMs.md +0 -24
- package/guides/autoscale.md +0 -80
- package/guides/errors.md +0 -41
- package/guides/metaGuide.md +0 -440
- package/guides/now.md +0 -56
- package/guides/powerBackpressure.md +0 -110
- package/guides/powerBatch.md +0 -82
- package/guides/powerBuffer.md +0 -86
- package/guides/powerBulkhead.md +0 -61
- package/guides/powerCache.md +0 -269
- package/guides/powerChunking.md +0 -130
- package/guides/powerCircuit.md +0 -84
- package/guides/powerDeadline.md +0 -99
- package/guides/powerDefer.md +0 -56
- package/guides/powerEventBus.md +0 -89
- package/guides/powerHistogram.md +0 -71
- package/guides/powerLatch.md +0 -94
- package/guides/powerLogger.md +0 -129
- package/guides/powerObserver.md +0 -65
- package/guides/powerPermitGate.md +0 -52
- package/guides/powerPool.md +0 -321
- package/guides/powerQueue.md +0 -112
- package/guides/powerRateLimit.md +0 -37
- package/guides/powerRetry.md +0 -54
- package/guides/powerScheduler.md +0 -41
- package/guides/powerSemaphore.md +0 -65
- package/guides/powerSlidingWindow.md +0 -63
- package/guides/powerSubscriberSet.md +0 -48
- package/guides/powerTTLMap.md +0 -58
- package/guides/powerThrottle.md +0 -152
- package/index.html +0 -57
- package/results.json +0 -6692
- package/scripts/find-missing-jsdoc.js +0 -62
- package/scripts/modernize-optional-chaining.cjs +0 -36
- package/scripts/pool-debug.mjs +0 -29
- package/scripts/repro_powercache.js +0 -14
- package/scripts/static-audit-exports.cjs +0 -93
- package/scripts/static-audit-exports.json +0 -518
- package/test/powerBackpressure.test.js +0 -114
- package/test/powerBatch.branches.extra.test.js +0 -122
- package/test/powerBatch.test.js +0 -79
- package/test/powerBuffer.test.js +0 -125
- package/test/powerBulkhead.test.js +0 -210
- package/test/powerCache.branches.test.js +0 -233
- package/test/powerCache.bulk.test.js +0 -31
- package/test/powerCache.getorset.test.js +0 -110
- package/test/powerCache.hitRate.test.js +0 -35
- package/test/powerCache.inflight.test.js +0 -24
- package/test/powerCache.iterator.test.js +0 -18
- package/test/powerCache.misses.test.js +0 -52
- package/test/powerCache.more.test.js +0 -118
- package/test/powerCache.test.js +0 -37
- package/test/powerCache.timeout.test.js +0 -25
- package/test/powerCache.touch.test.js +0 -46
- package/test/powerChunking.branches.extra.test.js +0 -155
- package/test/powerChunking.errors.test.js +0 -177
- package/test/powerChunking.test.js +0 -39
- package/test/powerCircuit.observability.test.js +0 -71
- package/test/powerCircuit.test.js +0 -74
- package/test/powerDeadline.test.js +0 -140
- package/test/powerDefer.test.js +0 -55
- package/test/powerErrors.test.js +0 -32
- package/test/powerEventBus.branches.extra.test.js +0 -70
- package/test/powerEventBus.extra.test.js +0 -72
- package/test/powerEventBus.max.test.js +0 -43
- package/test/powerEventBus.more.test.js +0 -121
- package/test/powerEventBus.once_off.test.js +0 -17
- package/test/powerEventBus.test.js +0 -74
- package/test/powerEventBus.uncovered.test.js +0 -57
- package/test/powerEventBus.weak.test.js +0 -18
- package/test/powerHistogram.test.js +0 -73
- package/test/powerLatch.branches.extra.test.js +0 -115
- package/test/powerLatch.test.js +0 -57
- package/test/powerLogger.branches.test.js +0 -98
- package/test/powerLogger.formatter.name.test.js +0 -58
- package/test/powerLogger.json.test.js +0 -88
- package/test/powerLogger.output.test.js +0 -81
- package/test/powerLogger.table.debug.test.js +0 -77
- package/test/powerLogger.test.js +0 -59
- package/test/powerMemoizer.memoize.test.js +0 -100
- package/test/powerMemoizer.test.js +0 -85
- package/test/powerObserver.test.js +0 -129
- package/test/powerPermitGate.test.js +0 -66
- package/test/powerPool.autoTransfer.test.js +0 -100
- package/test/powerPool.autoscale.extra.test.js +0 -88
- package/test/powerPool.autoscale.test.js +0 -136
- package/test/powerPool.awaitDefaultTimeout.test.js +0 -52
- package/test/powerPool.awaitTimeout.test.js +0 -22
- package/test/powerPool.batch.test.js +0 -170
- package/test/powerPool.branches.extra2.test.js +0 -42
- package/test/powerPool.branches.test.js +0 -102
- package/test/powerPool.browser.messageerror.test.js +0 -45
- package/test/powerPool.correlation.test.js +0 -26
- package/test/powerPool.correlationId.test.js +0 -63
- package/test/powerPool.dispose.test.js +0 -49
- package/test/powerPool.drain.test.js +0 -57
- package/test/powerPool.events.test.js +0 -131
- package/test/powerPool.more.extra.test.js +0 -99
- package/test/powerPool.more.test.js +0 -283
- package/test/powerPool.node.messageerror.test.js +0 -46
- package/test/powerPool.postMessage.promise.test.js +0 -83
- package/test/powerPool.queueHigh.test.js +0 -55
- package/test/powerPool.queueSaturation.test.js +0 -51
- package/test/powerPool.rapidResize.test.js +0 -55
- package/test/powerPool.resize.overload.test.js +0 -65
- package/test/powerPool.resize.test.js +0 -70
- package/test/powerPool.shutdown.test.js +0 -38
- package/test/powerPool.stats.test.js +0 -40
- package/test/powerPool.stopThePress.test.js +0 -94
- package/test/powerPool.terminateShutdown.test.js +0 -22
- package/test/powerPool.test.js +0 -525
- package/test/powerPool.timers.test.js +0 -55
- package/test/powerPool.uncovered.test.js +0 -407
- package/test/powerPool.workerId.test.js +0 -47
- package/test/powerQueue.iterators.test.js +0 -67
- package/test/powerQueue.saturation.test.js +0 -18
- package/test/powerQueue.test.js +0 -48
- package/test/powerQueue.unshiftMany.test.js +0 -49
- package/test/powerRateLimit.atomic.test.js +0 -80
- package/test/powerRateLimit.extra.test.js +0 -145
- package/test/powerRateLimit.functions.test.js +0 -106
- package/test/powerRateLimit.test.js +0 -38
- package/test/powerRetry.attemptTimeout.test.js +0 -51
- package/test/powerRetry.test.js +0 -121
- package/test/powerScheduler.test.js +0 -126
- package/test/powerSemaphore.test.js +0 -108
- package/test/powerSlidingWindow.pool.test.js +0 -55
- package/test/powerSlidingWindow.test.js +0 -25
- package/test/powerSubscriberSet.test.js +0 -125
- package/test/powerTTLMap.test.js +0 -125
- package/test/powerThrottle.pool.test.js +0 -54
- package/test/powerThrottle.refill.test.js +0 -22
- package/test/powerThrottle.reserve.test.js +0 -46
- package/test/powerThrottle.test.js +0 -45
- package/test/powerTimedCache.test.js +0 -73
- package/test/umd.bundle.branches.test.js +0 -100
- package/test/umd.bundle.cache-timers.test.js +0 -48
- package/test/umd.bundle.exhaustive.test.js +0 -158
- package/test/umd.bundle.fuzz.test.js +0 -86
- package/test/umd.bundle.hasEqual.more.test.js +0 -68
- package/test/umd.bundle.hasEqual.test.js +0 -104
- package/test/umd.bundle.logger-extra.test.js +0 -48
- package/test/umd.bundle.more-coverage-2.test.js +0 -67
- package/test/umd.bundle.pool.test.js +0 -134
- package/test/umd.bundle.test.js +0 -265
- package/test/utils.measure.test.js +0 -49
- package/test/utils.now.extra.test.js +0 -30
- package/test/utils.now.more.test.js +0 -57
- package/tsconfig.json +0 -16
- package/typedoc.json +0 -25
- package/vite.config.js +0 -31
- package/vitest.config.js +0 -17
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { PowerBatch } from '../src/helpers/powerBatch.js';
|
|
3
|
-
|
|
4
|
-
describe('PowerBatch branches extra', () => {
|
|
5
|
-
it('constructor throws when handler not a function', () => {
|
|
6
|
-
expect(() => new PowerBatch(null)).toThrow();
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it('flush returns resolved promise when empty and not scheduled', async () => {
|
|
10
|
-
const b = new PowerBatch(async () => {});
|
|
11
|
-
// ensure empty
|
|
12
|
-
expect(b.size).toBe(0);
|
|
13
|
-
const res = await b.flush();
|
|
14
|
-
expect(res).toBeUndefined();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('add schedules handler and resolves regular add promise', async () => {
|
|
18
|
-
let called = false;
|
|
19
|
-
const b = new PowerBatch(async (items) => {
|
|
20
|
-
called = true;
|
|
21
|
-
expect(items.length).toBeGreaterThan(0);
|
|
22
|
-
});
|
|
23
|
-
const p = b.add(1);
|
|
24
|
-
await p;
|
|
25
|
-
// wait a tick for handler
|
|
26
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
27
|
-
expect(called).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('flush returns a promise that rejects when handler throws', async () => {
|
|
31
|
-
const b = new PowerBatch(async () => {
|
|
32
|
-
throw new Error('boom');
|
|
33
|
-
});
|
|
34
|
-
b.add(1);
|
|
35
|
-
await expect(b.flush()).rejects.toThrow('boom');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('clear rejects pending add promises and empties queue', async () => {
|
|
39
|
-
let called = false;
|
|
40
|
-
const b = new PowerBatch(async () => {
|
|
41
|
-
called = true;
|
|
42
|
-
});
|
|
43
|
-
const promise = b.add(1);
|
|
44
|
-
b.clear();
|
|
45
|
-
await expect(promise).rejects.toThrow('PowerBatch cleared before flush');
|
|
46
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
47
|
-
expect(called).toBe(false);
|
|
48
|
-
expect(b.size).toBe(0);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('creates a fresh pending batch for items added while the handler is running', async () => {
|
|
52
|
-
const calls = [];
|
|
53
|
-
let releaseFirstBatch;
|
|
54
|
-
const firstBatchGate = new Promise((resolve) => {
|
|
55
|
-
releaseFirstBatch = resolve;
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const b = new PowerBatch(async (items) => {
|
|
59
|
-
calls.push(items.slice());
|
|
60
|
-
if (calls.length === 1) {
|
|
61
|
-
await firstBatchGate;
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const first = b.add('a');
|
|
66
|
-
await Promise.resolve();
|
|
67
|
-
const second = b.add('b');
|
|
68
|
-
releaseFirstBatch();
|
|
69
|
-
|
|
70
|
-
await Promise.all([first, second]);
|
|
71
|
-
|
|
72
|
-
expect(calls).toEqual([['a'], ['b']]);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('normalizes invalid maxSize and scheduling options', async () => {
|
|
76
|
-
const calls = [];
|
|
77
|
-
const b = new PowerBatch(
|
|
78
|
-
async (items) => {
|
|
79
|
-
calls.push(items.slice());
|
|
80
|
-
},
|
|
81
|
-
{ maxSize: 0, scheduling: 'invalid' }
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
b.add(1);
|
|
85
|
-
b.add(2);
|
|
86
|
-
await b.flush();
|
|
87
|
-
|
|
88
|
-
expect(calls).toEqual([[1, 2]]);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('flush re-schedules queued work if a pending batch exists but the scheduler was canceled', async () => {
|
|
92
|
-
const calls = [];
|
|
93
|
-
const b = new PowerBatch(async (items) => {
|
|
94
|
-
calls.push(items.slice());
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const addPromise = b.add('x');
|
|
98
|
-
b._scheduler.cancel();
|
|
99
|
-
|
|
100
|
-
await b.flush();
|
|
101
|
-
await addPromise;
|
|
102
|
-
|
|
103
|
-
expect(calls).toEqual([['x']]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('resolves orphaned pending promises on empty internal runs and rethrows handler errors without pending state', async () => {
|
|
107
|
-
const b = new PowerBatch(async () => {});
|
|
108
|
-
const pending = b.add('x');
|
|
109
|
-
b._queue.clear();
|
|
110
|
-
|
|
111
|
-
await b._runBatch();
|
|
112
|
-
await expect(pending).resolves.toBeUndefined();
|
|
113
|
-
expect(b._pending).toBeNull();
|
|
114
|
-
|
|
115
|
-
const b2 = new PowerBatch(async () => {
|
|
116
|
-
throw new Error('boom');
|
|
117
|
-
});
|
|
118
|
-
b2._queue.push('y');
|
|
119
|
-
|
|
120
|
-
await expect(b2._runBatch()).rejects.toThrow('boom');
|
|
121
|
-
});
|
|
122
|
-
});
|
package/test/powerBatch.test.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { PowerBatch } from '../src/helpers/powerBatch.js';
|
|
3
|
-
|
|
4
|
-
describe('PowerBatch', () => {
|
|
5
|
-
it('coalesces synchronous adds into a single handler call', async () => {
|
|
6
|
-
const calls = [];
|
|
7
|
-
const handler = async (items) => {
|
|
8
|
-
calls.push(items.slice());
|
|
9
|
-
};
|
|
10
|
-
const b = new PowerBatch(handler);
|
|
11
|
-
b.add(1);
|
|
12
|
-
b.add(2);
|
|
13
|
-
await b.flush();
|
|
14
|
-
expect(calls.length).toBe(1);
|
|
15
|
-
expect(calls[0]).toEqual([1, 2]);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('flushes immediately when maxSize is reached', async () => {
|
|
19
|
-
const calls = [];
|
|
20
|
-
const handler = (items) => calls.push(items.slice());
|
|
21
|
-
const b = new PowerBatch(handler, { maxSize: 2 });
|
|
22
|
-
b.add('a');
|
|
23
|
-
await b.add('b'); // this should trigger immediate flush
|
|
24
|
-
expect(calls.length).toBe(1);
|
|
25
|
-
expect(calls[0]).toEqual(['a', 'b']);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('flush waits for async handler completion', async () => {
|
|
29
|
-
const calls = [];
|
|
30
|
-
const handler = async (items) => {
|
|
31
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
32
|
-
calls.push(items.slice());
|
|
33
|
-
};
|
|
34
|
-
const b = new PowerBatch(handler);
|
|
35
|
-
b.add(42);
|
|
36
|
-
await b.flush();
|
|
37
|
-
expect(calls.length).toBe(1);
|
|
38
|
-
expect(calls[0]).toEqual([42]);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('add returns a promise that resolves after the handler completes', async () => {
|
|
42
|
-
const calls = [];
|
|
43
|
-
const handler = async (items) => {
|
|
44
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
45
|
-
calls.push(items.slice());
|
|
46
|
-
};
|
|
47
|
-
const b = new PowerBatch(handler);
|
|
48
|
-
const promise = b.add('x');
|
|
49
|
-
expect(promise).toBeInstanceOf(Promise);
|
|
50
|
-
await promise;
|
|
51
|
-
expect(calls).toEqual([['x']]);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('add resolves after async handler completion when maxSize triggers immediate flush', async () => {
|
|
55
|
-
const calls = [];
|
|
56
|
-
const handler = async (items) => {
|
|
57
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
58
|
-
calls.push(items.slice());
|
|
59
|
-
};
|
|
60
|
-
const b = new PowerBatch(handler, { maxSize: 2 });
|
|
61
|
-
const promise = b.add('a');
|
|
62
|
-
const promise2 = b.add('b');
|
|
63
|
-
expect(promise).toBeInstanceOf(Promise);
|
|
64
|
-
expect(promise2).toBeInstanceOf(Promise);
|
|
65
|
-
await Promise.all([promise, promise2]);
|
|
66
|
-
expect(calls).toEqual([['a', 'b']]);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('accepts scheduling option (macrotask) and preserves basic behavior', async () => {
|
|
70
|
-
const calls = [];
|
|
71
|
-
const handler = (items) => calls.push(items.slice());
|
|
72
|
-
const b = new PowerBatch(handler, { scheduling: 'macrotask' });
|
|
73
|
-
b.add('x');
|
|
74
|
-
b.add('y');
|
|
75
|
-
await b.flush();
|
|
76
|
-
expect(calls.length).toBe(1);
|
|
77
|
-
expect(calls[0]).toEqual(['x', 'y']);
|
|
78
|
-
});
|
|
79
|
-
});
|
package/test/powerBuffer.test.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
describe('powerBuffer', () => {
|
|
4
|
-
it('encodes and decodes plain objects', async () => {
|
|
5
|
-
const mod = await import('../src/helpers/powerBuffer.js');
|
|
6
|
-
const { o2u8, u82o } = mod;
|
|
7
|
-
const obj = { a: 1, b: 'x' };
|
|
8
|
-
const u8 = o2u8(obj);
|
|
9
|
-
expect(u8).toBeInstanceOf(Uint8Array);
|
|
10
|
-
const decoded = u82o(u8);
|
|
11
|
-
expect(decoded).toEqual(obj);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('accepts ArrayBuffer and returns Uint8Array view', async () => {
|
|
15
|
-
const { o2u8 } = await import('../src/helpers/powerBuffer.js');
|
|
16
|
-
const buf = new ArrayBuffer(4);
|
|
17
|
-
const view = new Uint8Array(buf);
|
|
18
|
-
view[0] = 1;
|
|
19
|
-
const u8 = o2u8(buf);
|
|
20
|
-
expect(u8).toBeInstanceOf(Uint8Array);
|
|
21
|
-
expect(u8[0]).toBe(1);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('o2b and b2o roundtrip', async () => {
|
|
25
|
-
const { o2b, b2o } = await import('../src/helpers/powerBuffer.js');
|
|
26
|
-
const obj = { z: [1, 2, 3], s: 'hello' };
|
|
27
|
-
const ab = o2b(obj);
|
|
28
|
-
expect(ab).toBeInstanceOf(ArrayBuffer);
|
|
29
|
-
const out = b2o(ab);
|
|
30
|
-
expect(out).toEqual(obj);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('falls back to Buffer-based encoder/decoder when TextEncoder/TextDecoder are absent', async () => {
|
|
34
|
-
// Reload module with globals stubbed
|
|
35
|
-
vi.resetModules();
|
|
36
|
-
const origTE = global.TextEncoder;
|
|
37
|
-
const origTD = global.TextDecoder;
|
|
38
|
-
try {
|
|
39
|
-
// remove TextEncoder/TextDecoder to force Buffer fallback
|
|
40
|
-
// @ts-ignore
|
|
41
|
-
global.TextEncoder = undefined;
|
|
42
|
-
// @ts-ignore
|
|
43
|
-
global.TextDecoder = undefined;
|
|
44
|
-
const mod = await import('../src/helpers/powerBuffer.js');
|
|
45
|
-
const { o2u8, u82o } = mod;
|
|
46
|
-
const obj = { fallback: true };
|
|
47
|
-
const u8 = o2u8(obj);
|
|
48
|
-
expect(u8).toBeInstanceOf(Uint8Array);
|
|
49
|
-
const decoded = u82o(u8);
|
|
50
|
-
expect(decoded).toEqual(obj);
|
|
51
|
-
} finally {
|
|
52
|
-
// restore
|
|
53
|
-
// @ts-ignore
|
|
54
|
-
global.TextEncoder = origTE;
|
|
55
|
-
// @ts-ignore
|
|
56
|
-
global.TextDecoder = origTD;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('returns the same Uint8Array instance when passed through', async () => {
|
|
61
|
-
const { o2u8 } = await import('../src/helpers/powerBuffer.js');
|
|
62
|
-
const ua = new Uint8Array([1, 2, 3]);
|
|
63
|
-
const out = o2u8(ua);
|
|
64
|
-
expect(out).toBe(ua);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('handles typed-array views and o2b produces a sliced ArrayBuffer when offset present', async () => {
|
|
68
|
-
const mod = await import('../src/helpers/powerBuffer.js');
|
|
69
|
-
const { o2u8, o2b } = mod;
|
|
70
|
-
const buf = new ArrayBuffer(8);
|
|
71
|
-
const view = new Uint8Array(buf, 2, 4);
|
|
72
|
-
view.set([9, 8, 7, 6]);
|
|
73
|
-
const u8 = o2u8(view);
|
|
74
|
-
expect(u8).toBeInstanceOf(Uint8Array);
|
|
75
|
-
const ab = o2b(view);
|
|
76
|
-
expect(ab).toBeInstanceOf(ArrayBuffer);
|
|
77
|
-
expect(ab.byteLength).toBe(u8.byteLength);
|
|
78
|
-
expect(ab).not.toBe(u8.buffer);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('u82o accepts Node Buffer and decodes to object', async () => {
|
|
82
|
-
const { u82o } = await import('../src/helpers/powerBuffer.js');
|
|
83
|
-
const obj = { foo: 'bar' };
|
|
84
|
-
// Node Buffer path
|
|
85
|
-
const buf = Buffer.from(JSON.stringify(obj));
|
|
86
|
-
const out = u82o(buf);
|
|
87
|
-
expect(out).toEqual(obj);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('u82o throws for unsupported input types', async () => {
|
|
91
|
-
const { u82o } = await import('../src/helpers/powerBuffer.js');
|
|
92
|
-
expect(() => u82o('not-a-buffer')).toThrow(TypeError);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('throws when no encoder/decoder available (simulated)', async () => {
|
|
96
|
-
// reload module with globals removed so getEncoder/getDecoder return null
|
|
97
|
-
vi.resetModules();
|
|
98
|
-
const origTE = global.TextEncoder;
|
|
99
|
-
const origTD = global.TextDecoder;
|
|
100
|
-
const origBuf = global.Buffer;
|
|
101
|
-
try {
|
|
102
|
-
// remove global encoders/Buffer
|
|
103
|
-
// @ts-ignore
|
|
104
|
-
global.TextEncoder = undefined;
|
|
105
|
-
// @ts-ignore
|
|
106
|
-
global.TextDecoder = undefined;
|
|
107
|
-
// @ts-ignore
|
|
108
|
-
global.Buffer = undefined;
|
|
109
|
-
|
|
110
|
-
const mod = await import('../src/helpers/powerBuffer.js');
|
|
111
|
-
const { o2u8, u82o } = mod;
|
|
112
|
-
expect(() => o2u8({ a: 1 })).toThrow(/No TextEncoder or Buffer available/);
|
|
113
|
-
const u = new Uint8Array([1, 2, 3]);
|
|
114
|
-
expect(() => u82o(u)).toThrow(/No TextDecoder or Buffer available/);
|
|
115
|
-
} finally {
|
|
116
|
-
// restore
|
|
117
|
-
// @ts-ignore
|
|
118
|
-
global.TextEncoder = origTE;
|
|
119
|
-
// @ts-ignore
|
|
120
|
-
global.TextDecoder = origTD;
|
|
121
|
-
// @ts-ignore
|
|
122
|
-
global.Buffer = origBuf;
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
});
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { PowerBulkhead } from '../src/helpers/powerBulkhead.js';
|
|
3
|
-
|
|
4
|
-
describe('PowerBulkhead', () => {
|
|
5
|
-
it('exposes constructor-derived getters and saturation state', () => {
|
|
6
|
-
const bulkhead = new PowerBulkhead({ partitions: 3, maxConcurrency: 2, queueCapacity: 4 });
|
|
7
|
-
expect(bulkhead.partitions).toBe(3);
|
|
8
|
-
expect(bulkhead.maxConcurrency).toBe(2);
|
|
9
|
-
expect(bulkhead.queueCapacity).toBe(4);
|
|
10
|
-
expect(bulkhead.isFull).toBe(false);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('executes tasks immediately while capacity remains in a partition', async () => {
|
|
14
|
-
const bulkhead = new PowerBulkhead({ partitions: 2, maxConcurrency: 2 });
|
|
15
|
-
const results = await Promise.all([
|
|
16
|
-
bulkhead.run(() => 1, { partitionKey: 'a' }),
|
|
17
|
-
bulkhead.run(() => 2, { partitionKey: 'a' }),
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
expect(results).toEqual([1, 2]);
|
|
21
|
-
expect(bulkhead.active).toBe(0);
|
|
22
|
-
expect(bulkhead.pending).toBe(0);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('queues tasks when partition concurrency is exceeded and dispatches them in FIFO order', async () => {
|
|
26
|
-
const bulkhead = new PowerBulkhead({ partitions: 1, maxConcurrency: 1, queueCapacity: 10 });
|
|
27
|
-
const firstTask = bulkhead.run(
|
|
28
|
-
() =>
|
|
29
|
-
new Promise((resolve) => {
|
|
30
|
-
setTimeout(() => {
|
|
31
|
-
resolve('first');
|
|
32
|
-
}, 20);
|
|
33
|
-
})
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const secondTask = bulkhead.run(() => 'second');
|
|
37
|
-
|
|
38
|
-
expect(bulkhead.pending).toBe(1);
|
|
39
|
-
expect(secondTask).toBeInstanceOf(Promise);
|
|
40
|
-
|
|
41
|
-
const result = await secondTask;
|
|
42
|
-
expect(result).toBe('second');
|
|
43
|
-
expect(await firstTask).toBe('first');
|
|
44
|
-
expect(bulkhead.pending).toBe(0);
|
|
45
|
-
expect(bulkhead.active).toBe(0);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('updates active and pending counters as queued work starts running', async () => {
|
|
49
|
-
const bulkhead = new PowerBulkhead({ partitions: 1, maxConcurrency: 1, queueCapacity: 10 });
|
|
50
|
-
let releaseFirst;
|
|
51
|
-
let resolveFirstStarted;
|
|
52
|
-
const firstStarted = new Promise((resolve) => {
|
|
53
|
-
resolveFirstStarted = resolve;
|
|
54
|
-
});
|
|
55
|
-
let resolveSecondStarted;
|
|
56
|
-
const secondStarted = new Promise((resolve) => {
|
|
57
|
-
resolveSecondStarted = resolve;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const firstTask = bulkhead.run(
|
|
61
|
-
() =>
|
|
62
|
-
new Promise((resolve) => {
|
|
63
|
-
releaseFirst = () => resolve('first');
|
|
64
|
-
resolveFirstStarted();
|
|
65
|
-
})
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
const secondTask = bulkhead.run(async () => {
|
|
69
|
-
resolveSecondStarted();
|
|
70
|
-
return 'second';
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
expect(bulkhead.active).toBe(1);
|
|
74
|
-
expect(bulkhead.pending).toBe(1);
|
|
75
|
-
|
|
76
|
-
await firstStarted;
|
|
77
|
-
releaseFirst();
|
|
78
|
-
await secondStarted;
|
|
79
|
-
|
|
80
|
-
expect(bulkhead.active).toBe(1);
|
|
81
|
-
expect(bulkhead.pending).toBe(0);
|
|
82
|
-
|
|
83
|
-
await expect(secondTask).resolves.toBe('second');
|
|
84
|
-
await expect(firstTask).resolves.toBe('first');
|
|
85
|
-
expect(bulkhead.active).toBe(0);
|
|
86
|
-
expect(bulkhead.pending).toBe(0);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('isolates noisy partitions so one partition queue does not block other partitions', async () => {
|
|
90
|
-
const bulkhead = new PowerBulkhead({ partitions: 2, maxConcurrency: 1, queueCapacity: 10 });
|
|
91
|
-
const executionOrder = [];
|
|
92
|
-
|
|
93
|
-
const hot1 = bulkhead.run(
|
|
94
|
-
() =>
|
|
95
|
-
new Promise((resolve) => {
|
|
96
|
-
executionOrder.push('hot-start');
|
|
97
|
-
setTimeout(() => {
|
|
98
|
-
executionOrder.push('hot-end');
|
|
99
|
-
resolve('hot');
|
|
100
|
-
}, 40);
|
|
101
|
-
}),
|
|
102
|
-
{ partitionKey: 'hot' }
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const hot2 = bulkhead.run(() => 'hot-queued', { partitionKey: 'hot' });
|
|
106
|
-
|
|
107
|
-
const cold = bulkhead.run(
|
|
108
|
-
() => {
|
|
109
|
-
executionOrder.push('cold');
|
|
110
|
-
return 'cold';
|
|
111
|
-
},
|
|
112
|
-
{ partitionKey: 'cold' }
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
expect(bulkhead.pending).toBe(1);
|
|
116
|
-
expect(await cold).toBe('cold');
|
|
117
|
-
expect(await hot2).toBe('hot-queued');
|
|
118
|
-
expect(await hot1).toBe('hot');
|
|
119
|
-
expect(executionOrder).toEqual(['hot-start', 'cold', 'hot-end']);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('rejects new tasks when global queue capacity is exceeded', async () => {
|
|
123
|
-
const bulkhead = new PowerBulkhead({ partitions: 1, maxConcurrency: 1, queueCapacity: 1 });
|
|
124
|
-
|
|
125
|
-
bulkhead.run(() => new Promise((resolve) => setTimeout(resolve, 20)));
|
|
126
|
-
|
|
127
|
-
const queued = bulkhead.run(() => 'queued');
|
|
128
|
-
expect(bulkhead.pending).toBe(1);
|
|
129
|
-
expect(bulkhead.isFull).toBe(true);
|
|
130
|
-
|
|
131
|
-
await expect(bulkhead.run(() => 'overflow')).rejects.toThrow('PowerBulkhead queue is full');
|
|
132
|
-
expect(await queued).toBe('queued');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('tryRun throws for invalid tasks and returns null when partition is busy', async () => {
|
|
136
|
-
const bulkhead = new PowerBulkhead({ partitions: 1, maxConcurrency: 1, queueCapacity: 1 });
|
|
137
|
-
|
|
138
|
-
expect(() => bulkhead.tryRun(null)).toThrow('PowerBulkhead.tryRun() requires a function');
|
|
139
|
-
|
|
140
|
-
let release;
|
|
141
|
-
let resolveStarted;
|
|
142
|
-
const started = new Promise((resolve) => {
|
|
143
|
-
resolveStarted = resolve;
|
|
144
|
-
});
|
|
145
|
-
const running = bulkhead.run(
|
|
146
|
-
() =>
|
|
147
|
-
new Promise((resolve) => {
|
|
148
|
-
release = () => resolve('done');
|
|
149
|
-
resolveStarted();
|
|
150
|
-
})
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
await started;
|
|
154
|
-
|
|
155
|
-
expect(bulkhead.tryRun(() => 'later')).toBeNull();
|
|
156
|
-
release();
|
|
157
|
-
await expect(running).resolves.toBe('done');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('tryRun executes immediately when capacity is available', async () => {
|
|
161
|
-
const bulkhead = new PowerBulkhead({ partitions: 2, maxConcurrency: 1 });
|
|
162
|
-
await expect(bulkhead.tryRun(() => 'ok')).resolves.toBe('ok');
|
|
163
|
-
expect(bulkhead.active).toBe(0);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('drain resolves immediately when already idle', async () => {
|
|
167
|
-
const bulkhead = new PowerBulkhead({ partitions: 2, maxConcurrency: 1 });
|
|
168
|
-
await expect(bulkhead.drain()).resolves.toBeUndefined();
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('uses a custom partitioner when provided', async () => {
|
|
172
|
-
const seen = [];
|
|
173
|
-
const bulkhead = new PowerBulkhead({
|
|
174
|
-
partitions: 2,
|
|
175
|
-
maxConcurrency: 1,
|
|
176
|
-
partitioner(key) {
|
|
177
|
-
seen.push(key);
|
|
178
|
-
return 1;
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
await expect(bulkhead.run(() => 'ok', { partitionKey: 'custom' })).resolves.toBe('ok');
|
|
183
|
-
expect(seen).toEqual(['custom']);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('drain() resolves after all active and queued tasks finish', async () => {
|
|
187
|
-
const bulkhead = new PowerBulkhead({ partitions: 1, maxConcurrency: 1, queueCapacity: 10 });
|
|
188
|
-
const results = [];
|
|
189
|
-
|
|
190
|
-
bulkhead.run(
|
|
191
|
-
() =>
|
|
192
|
-
new Promise((resolve) =>
|
|
193
|
-
setTimeout(() => {
|
|
194
|
-
results.push('first');
|
|
195
|
-
resolve('first');
|
|
196
|
-
}, 20)
|
|
197
|
-
)
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
bulkhead.run(() => {
|
|
201
|
-
results.push('second');
|
|
202
|
-
return 'second';
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
await bulkhead.drain();
|
|
206
|
-
expect(results).toEqual(['first', 'second']);
|
|
207
|
-
expect(bulkhead.active).toBe(0);
|
|
208
|
-
expect(bulkhead.pending).toBe(0);
|
|
209
|
-
});
|
|
210
|
-
});
|