@adviser/cement 0.2.28 → 0.2.29
Sign up to get free protection for your applications and to get access to all the features.
- package/{base-sys-abstraction-C9WW3w57.d.cts → base-sys-abstraction-BkEiLHl0.d.ts} +8 -4
- package/{base-sys-abstraction-C9WW3w57.d.ts → base-sys-abstraction-Qj7pkY1N.d.cts} +8 -4
- package/{chunk-OXD3YZZ7.js → chunk-7KFVMTOS.js} +3 -47
- package/chunk-7KFVMTOS.js.map +1 -0
- package/{chunk-P5RXA4C4.js → chunk-GES3MUGV.js} +24 -27
- package/{chunk-LCPYQVWT.js.map → chunk-GES3MUGV.js.map} +1 -1
- package/{chunk-YDIUMYIE.js → chunk-Q65HLCNL.js} +8 -7
- package/chunk-Q65HLCNL.js.map +1 -0
- package/{chunk-DPIL5UIL.js → chunk-WMMUXBDX.js} +6 -2
- package/chunk-WMMUXBDX.js.map +1 -0
- package/{index-Dhb2fQiw.d.cts → index-Q3phXzYr.d.cts} +2 -21
- package/{index-CcsGcehs.d.ts → index-tIGZMHTc.d.ts} +2 -21
- package/index.cjs +264 -7343
- package/index.cjs.map +1 -1
- package/index.d.cts +72 -12
- package/index.d.ts +72 -12
- package/index.js +217 -7265
- package/index.js.map +1 -1
- package/node/index.cjs +220 -39
- package/node/index.cjs.map +1 -1
- package/node/index.d.cts +22 -4
- package/node/index.d.ts +22 -4
- package/node/index.js +201 -25
- package/node/index.js.map +1 -1
- package/package.json +23 -12
- package/src/LICENSE +201 -0
- package/src/README.md +39 -0
- package/src/base-sys-abstraction.test.ts +95 -0
- package/src/base-sys-abstraction.ts +242 -0
- package/src/bin2text.test.ts +59 -0
- package/src/bin2text.ts +47 -0
- package/src/crypto.test.ts +15 -0
- package/src/crypto.ts +125 -0
- package/src/file-service.ts +24 -0
- package/src/future.test.ts +32 -0
- package/src/future.ts +27 -0
- package/src/index.ts +22 -0
- package/src/jsr.json +20 -0
- package/src/log-level-impl.ts +87 -0
- package/src/log-writer-impl.ts +58 -0
- package/src/logger-impl.ts +498 -0
- package/src/logger.test.ts +1132 -0
- package/src/logger.ts +208 -0
- package/src/node/deno-file-service.ts +92 -0
- package/src/node/deno-sys-abstraction.ts +133 -0
- package/src/node/index.ts +4 -0
- package/src/node/mock-file-service.ts +45 -0
- package/src/node/node-file-service.ts +91 -0
- package/src/node/node-sys-abstraction.ts +121 -0
- package/src/option.ts +60 -0
- package/src/resolve-once.test.ts +321 -0
- package/src/resolve-once.ts +179 -0
- package/src/result.test.ts +102 -0
- package/src/result.ts +165 -0
- package/src/runtime.ts +36 -0
- package/src/sys-abstraction.ts +53 -0
- package/src/sys-env.test.ts +53 -0
- package/src/sys-env.ts +216 -0
- package/src/test/log-write-stream.ts +95 -0
- package/src/test/mock-logger.ts +40 -0
- package/src/time.ts +20 -0
- package/src/tracer.test.ts +314 -0
- package/src/tracer.ts +222 -0
- package/src/txt-en-decoder.ts +21 -0
- package/src/uri.test.ts +155 -0
- package/src/uri.ts +421 -0
- package/src/utils/console-write-stream.ts +72 -0
- package/src/utils/fanout-write-stream.ts +32 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/rebuffer.ts +75 -0
- package/src/utils/stream-map.ts +67 -0
- package/src/utils/stream2string.ts +47 -0
- package/src/utils/string2stream.ts +14 -0
- package/src/version.ts +3 -0
- package/src/web/index.ts +1 -0
- package/src/web/web-sys-abstraction.ts +80 -0
- package/ts/base-sys-abstraction.d.ts +84 -0
- package/ts/base-sys-abstraction.d.ts.map +1 -0
- package/ts/base-sys-abstraction.js +178 -0
- package/ts/base-sys-abstraction.js.map +1 -0
- package/ts/base-sys-abstraction.test.d.ts +2 -0
- package/ts/base-sys-abstraction.test.d.ts.map +1 -0
- package/ts/base-sys-abstraction.test.js +82 -0
- package/ts/base-sys-abstraction.test.js.map +1 -0
- package/ts/bin2text.d.ts +3 -0
- package/ts/bin2text.d.ts.map +1 -0
- package/ts/bin2text.js +43 -0
- package/ts/bin2text.js.map +1 -0
- package/ts/bin2text.test.d.ts +2 -0
- package/ts/bin2text.test.d.ts.map +1 -0
- package/ts/bin2text.test.js +51 -0
- package/ts/bin2text.test.js.map +1 -0
- package/ts/crypto.d.ts +76 -0
- package/ts/crypto.d.ts.map +1 -0
- package/ts/crypto.js +22 -0
- package/ts/crypto.js.map +1 -0
- package/ts/crypto.test.d.ts +2 -0
- package/ts/crypto.test.d.ts.map +1 -0
- package/ts/crypto.test.js +14 -0
- package/ts/crypto.test.js.map +1 -0
- package/ts/file-service.d.ts +17 -0
- package/ts/file-service.d.ts.map +1 -0
- package/ts/file-service.js +2 -0
- package/ts/file-service.js.map +1 -0
- package/ts/future.d.ts +8 -0
- package/ts/future.d.ts.map +1 -0
- package/ts/future.js +38 -0
- package/ts/future.js.map +1 -0
- package/ts/future.test.d.ts +2 -0
- package/ts/future.test.d.ts.map +1 -0
- package/ts/future.test.js +28 -0
- package/ts/future.test.js.map +1 -0
- package/ts/index.d.ts +23 -0
- package/ts/index.d.ts.map +1 -0
- package/ts/index.js +23 -0
- package/ts/index.js.map +1 -0
- package/ts/log-level-impl.d.ts +14 -0
- package/ts/log-level-impl.d.ts.map +1 -0
- package/ts/log-level-impl.js +72 -0
- package/ts/log-level-impl.js.map +1 -0
- package/ts/log-writer-impl.d.ts +10 -0
- package/ts/log-writer-impl.d.ts.map +1 -0
- package/ts/log-writer-impl.js +45 -0
- package/ts/log-writer-impl.js.map +1 -0
- package/ts/logger-impl.d.ts +71 -0
- package/ts/logger-impl.d.ts.map +1 -0
- package/ts/logger-impl.js +412 -0
- package/ts/logger-impl.js.map +1 -0
- package/ts/logger.d.ts +84 -0
- package/ts/logger.d.ts.map +1 -0
- package/ts/logger.js +114 -0
- package/ts/logger.js.map +1 -0
- package/ts/logger.test.d.ts +2 -0
- package/ts/logger.test.d.ts.map +1 -0
- package/ts/logger.test.js +1023 -0
- package/ts/logger.test.js.map +1 -0
- package/ts/node/deno-file-service.d.ts +17 -0
- package/ts/node/deno-file-service.d.ts.map +1 -0
- package/ts/node/deno-file-service.js +65 -0
- package/ts/node/deno-file-service.js.map +1 -0
- package/ts/node/deno-sys-abstraction.d.ts +22 -0
- package/ts/node/deno-sys-abstraction.d.ts.map +1 -0
- package/ts/node/deno-sys-abstraction.js +101 -0
- package/ts/node/deno-sys-abstraction.js.map +1 -0
- package/ts/node/index.d.ts +5 -0
- package/ts/node/index.d.ts.map +1 -0
- package/ts/node/index.js +5 -0
- package/ts/node/index.js.map +1 -0
- package/ts/node/mock-file-service.d.ts +11 -0
- package/ts/node/mock-file-service.d.ts.map +1 -0
- package/ts/node/mock-file-service.js +34 -0
- package/ts/node/mock-file-service.js.map +1 -0
- package/ts/node/mock-file-service.test.d.ts +2 -0
- package/ts/node/mock-file-service.test.d.ts.map +1 -0
- package/ts/node/mock-file-service.test.js +31 -0
- package/ts/node/mock-file-service.test.js.map +1 -0
- package/ts/node/node-file-service.d.ts +16 -0
- package/ts/node/node-file-service.d.ts.map +1 -0
- package/ts/node/node-file-service.js +71 -0
- package/ts/node/node-file-service.js.map +1 -0
- package/ts/node/node-sys-abstraction.d.ts +22 -0
- package/ts/node/node-sys-abstraction.d.ts.map +1 -0
- package/ts/node/node-sys-abstraction.js +99 -0
- package/ts/node/node-sys-abstraction.js.map +1 -0
- package/ts/node/node-sys-abstraction.test.d.ts +2 -0
- package/ts/node/node-sys-abstraction.test.d.ts.map +1 -0
- package/ts/node/node-sys-abstraction.test.js +87 -0
- package/ts/node/node-sys-abstraction.test.js.map +1 -0
- package/ts/option.d.ts +25 -0
- package/ts/option.d.ts.map +1 -0
- package/ts/option.js +47 -0
- package/ts/option.js.map +1 -0
- package/ts/resolve-once.d.ts +46 -0
- package/ts/resolve-once.d.ts.map +1 -0
- package/ts/resolve-once.js +152 -0
- package/ts/resolve-once.js.map +1 -0
- package/ts/resolve-once.test.d.ts +2 -0
- package/ts/resolve-once.test.d.ts.map +1 -0
- package/ts/resolve-once.test.js +283 -0
- package/ts/resolve-once.test.js.map +1 -0
- package/ts/result.d.ts +34 -0
- package/ts/result.d.ts.map +1 -0
- package/ts/result.js +85 -0
- package/ts/result.js.map +1 -0
- package/ts/result.test.d.ts +2 -0
- package/ts/result.test.d.ts.map +1 -0
- package/ts/result.test.js +79 -0
- package/ts/result.test.js.map +1 -0
- package/ts/runtime.d.ts +8 -0
- package/ts/runtime.d.ts.map +1 -0
- package/ts/runtime.js +26 -0
- package/ts/runtime.js.map +1 -0
- package/ts/sys-abstraction.d.ts +36 -0
- package/ts/sys-abstraction.d.ts.map +1 -0
- package/ts/sys-abstraction.js +31 -0
- package/ts/sys-abstraction.js.map +1 -0
- package/ts/sys-env.d.ts +48 -0
- package/ts/sys-env.d.ts.map +1 -0
- package/ts/sys-env.js +176 -0
- package/ts/sys-env.js.map +1 -0
- package/ts/sys-env.test.d.ts +2 -0
- package/ts/sys-env.test.d.ts.map +1 -0
- package/ts/sys-env.test.js +51 -0
- package/ts/sys-env.test.js.map +1 -0
- package/ts/test/log-write-stream.d.ts +27 -0
- package/ts/test/log-write-stream.d.ts.map +1 -0
- package/ts/test/log-write-stream.js +74 -0
- package/ts/test/log-write-stream.js.map +1 -0
- package/ts/test/mock-logger.d.ts +14 -0
- package/ts/test/mock-logger.d.ts.map +1 -0
- package/ts/test/mock-logger.js +29 -0
- package/ts/test/mock-logger.js.map +1 -0
- package/ts/test/mock-logger.test.d.ts +2 -0
- package/ts/test/mock-logger.test.d.ts.map +1 -0
- package/ts/test/mock-logger.test.js +63 -0
- package/ts/test/mock-logger.test.js.map +1 -0
- package/ts/test/test-exit-handler.d.ts +2 -0
- package/ts/test/test-exit-handler.d.ts.map +1 -0
- package/ts/test/test-exit-handler.js +57 -0
- package/ts/test/test-exit-handler.js.map +1 -0
- package/ts/time.d.ts +13 -0
- package/ts/time.d.ts.map +1 -0
- package/ts/time.js +14 -0
- package/ts/time.js.map +1 -0
- package/ts/tracer.d.ts +59 -0
- package/ts/tracer.d.ts.map +1 -0
- package/ts/tracer.js +148 -0
- package/ts/tracer.js.map +1 -0
- package/ts/tracer.test.d.ts +2 -0
- package/ts/tracer.test.d.ts.map +1 -0
- package/ts/tracer.test.js +311 -0
- package/ts/tracer.test.js.map +1 -0
- package/ts/txt-en-decoder.d.ts +10 -0
- package/ts/txt-en-decoder.d.ts.map +1 -0
- package/ts/txt-en-decoder.js +15 -0
- package/ts/txt-en-decoder.js.map +1 -0
- package/ts/uri.d.ts +67 -0
- package/ts/uri.d.ts.map +1 -0
- package/ts/uri.js +283 -0
- package/ts/uri.js.map +1 -0
- package/ts/uri.test.d.ts +2 -0
- package/ts/uri.test.d.ts.map +1 -0
- package/ts/uri.test.js +119 -0
- package/ts/uri.test.js.map +1 -0
- package/ts/utils/console-write-stream.d.ts +21 -0
- package/ts/utils/console-write-stream.d.ts.map +1 -0
- package/ts/utils/console-write-stream.js +62 -0
- package/ts/utils/console-write-stream.js.map +1 -0
- package/ts/utils/fanout-write-stream.d.ts +12 -0
- package/ts/utils/fanout-write-stream.d.ts.map +1 -0
- package/ts/utils/fanout-write-stream.js +24 -0
- package/ts/utils/fanout-write-stream.js.map +1 -0
- package/ts/utils/index.d.ts +7 -0
- package/ts/utils/index.d.ts.map +1 -0
- package/ts/utils/index.js +7 -0
- package/ts/utils/index.js.map +1 -0
- package/ts/utils/rebuffer.d.ts +3 -0
- package/ts/utils/rebuffer.d.ts.map +1 -0
- package/ts/utils/rebuffer.js +60 -0
- package/ts/utils/rebuffer.js.map +1 -0
- package/ts/utils/rebuffer.test.d.ts +2 -0
- package/ts/utils/rebuffer.test.d.ts.map +1 -0
- package/ts/utils/rebuffer.test.js +77 -0
- package/ts/utils/rebuffer.test.js.map +1 -0
- package/ts/utils/stream-map.d.ts +9 -0
- package/ts/utils/stream-map.d.ts.map +1 -0
- package/ts/utils/stream-map.js +62 -0
- package/ts/utils/stream-map.js.map +1 -0
- package/ts/utils/stream-map.test.d.ts +2 -0
- package/ts/utils/stream-map.test.d.ts.map +1 -0
- package/ts/utils/stream-map.test.js +87 -0
- package/ts/utils/stream-map.test.js.map +1 -0
- package/ts/utils/stream-test-helper.d.ts +17 -0
- package/ts/utils/stream-test-helper.d.ts.map +1 -0
- package/ts/utils/stream-test-helper.js +37 -0
- package/ts/utils/stream-test-helper.js.map +1 -0
- package/ts/utils/stream2string.d.ts +3 -0
- package/ts/utils/stream2string.d.ts.map +1 -0
- package/ts/utils/stream2string.js +48 -0
- package/ts/utils/stream2string.js.map +1 -0
- package/ts/utils/stream2string.test.d.ts +2 -0
- package/ts/utils/stream2string.test.d.ts.map +1 -0
- package/ts/utils/stream2string.test.js +29 -0
- package/ts/utils/stream2string.test.js.map +1 -0
- package/ts/utils/string2stream.d.ts +4 -0
- package/ts/utils/string2stream.d.ts.map +1 -0
- package/ts/utils/string2stream.js +13 -0
- package/ts/utils/string2stream.js.map +1 -0
- package/ts/utils/string2stream.test.d.ts +2 -0
- package/ts/utils/string2stream.test.d.ts.map +1 -0
- package/ts/utils/string2stream.test.js +6 -0
- package/ts/utils/string2stream.test.js.map +1 -0
- package/ts/version.d.ts +2 -0
- package/ts/version.d.ts.map +1 -0
- package/ts/version.js +4 -0
- package/ts/version.js.map +1 -0
- package/ts/web/index.d.ts +2 -0
- package/ts/web/index.d.ts.map +1 -0
- package/ts/web/index.js +2 -0
- package/ts/web/index.js.map +1 -0
- package/ts/web/web-sys-abstraction.d.ts +4 -0
- package/ts/web/web-sys-abstraction.d.ts.map +1 -0
- package/ts/web/web-sys-abstraction.js +64 -0
- package/ts/web/web-sys-abstraction.js.map +1 -0
- package/utils/index.cjs +0 -42
- package/utils/index.cjs.map +1 -1
- package/utils/index.d.cts +1 -2
- package/utils/index.d.ts +1 -2
- package/utils/index.js +2 -7
- package/web/index.cjs +25 -7
- package/web/index.cjs.map +1 -1
- package/web/index.d.cts +2 -1
- package/web/index.d.ts +2 -1
- package/web/index.js +3 -3
- package/chunk-DPIL5UIL.js.map +0 -1
- package/chunk-LCPYQVWT.js +0 -21
- package/chunk-OXD3YZZ7.js.map +0 -1
- package/chunk-P5RXA4C4.js.map +0 -1
- package/chunk-YDIUMYIE.js.map +0 -1
package/src/option.ts
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
export abstract class Option<T> {
|
2
|
+
static Some<T>(t: T): Option<T> {
|
3
|
+
return new Some(t);
|
4
|
+
}
|
5
|
+
|
6
|
+
static None<T>(): Option<T> {
|
7
|
+
return new None();
|
8
|
+
}
|
9
|
+
|
10
|
+
static Is<T>(t: unknown): t is Option<T> {
|
11
|
+
return t instanceof Option;
|
12
|
+
}
|
13
|
+
|
14
|
+
IsNone(): boolean {
|
15
|
+
return this.is_none();
|
16
|
+
}
|
17
|
+
|
18
|
+
IsSome(): boolean {
|
19
|
+
return this.is_some();
|
20
|
+
}
|
21
|
+
Unwrap(): T {
|
22
|
+
return this.unwrap();
|
23
|
+
}
|
24
|
+
|
25
|
+
abstract is_none(): boolean;
|
26
|
+
abstract is_some(): boolean;
|
27
|
+
abstract unwrap(): T;
|
28
|
+
}
|
29
|
+
|
30
|
+
export class Some<T> extends Option<T> {
|
31
|
+
private _t: T;
|
32
|
+
constructor(_t: T) {
|
33
|
+
super();
|
34
|
+
this._t = _t;
|
35
|
+
}
|
36
|
+
|
37
|
+
is_none(): boolean {
|
38
|
+
return false;
|
39
|
+
}
|
40
|
+
is_some(): boolean {
|
41
|
+
return true;
|
42
|
+
}
|
43
|
+
unwrap(): T {
|
44
|
+
return this._t;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
export class None<T> extends Option<T> {
|
49
|
+
is_none(): boolean {
|
50
|
+
return true;
|
51
|
+
}
|
52
|
+
is_some(): boolean {
|
53
|
+
return false;
|
54
|
+
}
|
55
|
+
unwrap(): T {
|
56
|
+
throw new Error("None.unwrap");
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
export type WithoutOption<T> = T extends Option<infer U> ? U : T;
|
@@ -0,0 +1,321 @@
|
|
1
|
+
import { KeyedResolvOnce, ResolveOnce, ResolveSeq } from "@adviser/cement";
|
2
|
+
|
3
|
+
describe("resolve-once", () => {
|
4
|
+
it("sequence", async () => {
|
5
|
+
const once = new ResolveOnce<number>();
|
6
|
+
|
7
|
+
const reallyOnce = vi.fn(async () => {
|
8
|
+
return new Promise<number>((resolve) => {
|
9
|
+
setTimeout(() => {
|
10
|
+
resolve(42);
|
11
|
+
}, 100);
|
12
|
+
});
|
13
|
+
});
|
14
|
+
const start = Date.now();
|
15
|
+
const fn = (): Promise<number> => once.once(async () => reallyOnce());
|
16
|
+
expect(reallyOnce).toHaveBeenCalledTimes(0);
|
17
|
+
expect(await fn()).toBe(42);
|
18
|
+
expect(reallyOnce).toHaveBeenCalledTimes(1);
|
19
|
+
expect(await fn()).toBe(42);
|
20
|
+
expect(reallyOnce).toHaveBeenCalledTimes(1);
|
21
|
+
expect(await fn()).toBe(42);
|
22
|
+
expect(reallyOnce).toHaveBeenCalledTimes(1);
|
23
|
+
const diff = Date.now() - start;
|
24
|
+
expect(diff).toBeGreaterThanOrEqual(99);
|
25
|
+
expect(diff).toBeLessThan(150);
|
26
|
+
});
|
27
|
+
it("parallel", async () => {
|
28
|
+
const once = new ResolveOnce<number>();
|
29
|
+
const reallyOnce = vi.fn(async () => {
|
30
|
+
return new Promise<number>((resolve) => {
|
31
|
+
setTimeout(() => {
|
32
|
+
resolve(42);
|
33
|
+
}, 100);
|
34
|
+
});
|
35
|
+
});
|
36
|
+
const fn = (): Promise<number> => once.once(async () => reallyOnce());
|
37
|
+
const start = Date.now();
|
38
|
+
expect(
|
39
|
+
await Promise.all(
|
40
|
+
Array(100)
|
41
|
+
.fill(fn)
|
42
|
+
.map((fn) => fn()),
|
43
|
+
),
|
44
|
+
).toEqual(Array(100).fill(42));
|
45
|
+
expect(reallyOnce).toHaveBeenCalledTimes(1);
|
46
|
+
const diff = Date.now() - start;
|
47
|
+
expect(diff).toBeGreaterThanOrEqual(99);
|
48
|
+
expect(diff).toBeLessThan(150);
|
49
|
+
});
|
50
|
+
|
51
|
+
it("works with void", async () => {
|
52
|
+
const once = new ResolveOnce<void>();
|
53
|
+
const reallyOnce = vi.fn(async () => {
|
54
|
+
return new Promise<void>((resolve) => {
|
55
|
+
setTimeout(() => {
|
56
|
+
resolve();
|
57
|
+
}, 100);
|
58
|
+
});
|
59
|
+
});
|
60
|
+
const fn = (): Promise<void> => once.once(async () => reallyOnce());
|
61
|
+
const start = Date.now();
|
62
|
+
expect(
|
63
|
+
await Promise.all(
|
64
|
+
Array(100)
|
65
|
+
.fill(fn)
|
66
|
+
.map((fn) => fn()),
|
67
|
+
),
|
68
|
+
).toEqual(Array(100).fill(undefined));
|
69
|
+
expect(reallyOnce).toHaveBeenCalledTimes(1);
|
70
|
+
const diff = Date.now() - start;
|
71
|
+
expect(diff).toBeGreaterThanOrEqual(99);
|
72
|
+
expect(diff).toBeLessThan(150);
|
73
|
+
});
|
74
|
+
|
75
|
+
it("throws", async () => {
|
76
|
+
const once = new ResolveOnce<number>();
|
77
|
+
const reallyOnce = vi.fn(async () => {
|
78
|
+
return new Promise<number>((rs, rj) => {
|
79
|
+
setTimeout(() => {
|
80
|
+
rj(new Error("nope"));
|
81
|
+
}, 100);
|
82
|
+
});
|
83
|
+
});
|
84
|
+
const fn = (): Promise<number> => once.once(async () => reallyOnce());
|
85
|
+
const start = Date.now();
|
86
|
+
await new Promise((rs) => {
|
87
|
+
for (let i = 0; i < 100; i++) {
|
88
|
+
fn()
|
89
|
+
.then(() => {
|
90
|
+
assert.fail("should not happen");
|
91
|
+
})
|
92
|
+
.catch((e) => {
|
93
|
+
expect(e).toEqual(new Error("nope"));
|
94
|
+
expect(reallyOnce).toHaveBeenCalledTimes(1);
|
95
|
+
if (i === 99) {
|
96
|
+
rs(undefined);
|
97
|
+
}
|
98
|
+
});
|
99
|
+
}
|
100
|
+
});
|
101
|
+
const diff = Date.now() - start;
|
102
|
+
expect(diff).toBeGreaterThanOrEqual(99);
|
103
|
+
expect(diff).toBeLessThan(150);
|
104
|
+
});
|
105
|
+
|
106
|
+
it("preserves order", async () => {
|
107
|
+
const once = new ResolveOnce<number>();
|
108
|
+
const reallyOnce = vi.fn(async () => {
|
109
|
+
return new Promise<number>((resolve) => {
|
110
|
+
setTimeout(() => {
|
111
|
+
resolve(42);
|
112
|
+
}, 100);
|
113
|
+
});
|
114
|
+
});
|
115
|
+
let order = 0;
|
116
|
+
const fn = async (): Promise<string> => {
|
117
|
+
const o = order++;
|
118
|
+
const ret = await once.once(async () => reallyOnce());
|
119
|
+
return `${o}:${ret}`;
|
120
|
+
};
|
121
|
+
const start = Date.now();
|
122
|
+
expect(
|
123
|
+
await Promise.all(
|
124
|
+
Array(100)
|
125
|
+
.fill(fn)
|
126
|
+
.map((fn) => fn()),
|
127
|
+
),
|
128
|
+
).toEqual(
|
129
|
+
Array(100)
|
130
|
+
.fill(undefined)
|
131
|
+
.map((_, i) => `${i}:42`),
|
132
|
+
);
|
133
|
+
expect(reallyOnce).toHaveBeenCalledTimes(1);
|
134
|
+
const diff = Date.now() - start;
|
135
|
+
expect(diff).toBeGreaterThanOrEqual(99);
|
136
|
+
expect(diff).toBeLessThan(150);
|
137
|
+
});
|
138
|
+
|
139
|
+
it("preserves call order to resolv order", async () => {
|
140
|
+
const once = new ResolveOnce<number>();
|
141
|
+
const reallyOnce = vi.fn(async () => {
|
142
|
+
return new Promise<number>((resolve) => {
|
143
|
+
setTimeout(() => {
|
144
|
+
resolve(42);
|
145
|
+
}, 100);
|
146
|
+
});
|
147
|
+
});
|
148
|
+
const start = Date.now();
|
149
|
+
const orderFn = vi.fn();
|
150
|
+
const fns = Array(100)
|
151
|
+
.fill(0)
|
152
|
+
.map((_, i) => {
|
153
|
+
return once
|
154
|
+
.once(() => reallyOnce())
|
155
|
+
.then((once) => {
|
156
|
+
orderFn(i, once);
|
157
|
+
// expect(i).toBe(order++);
|
158
|
+
return `${i}:${once}`;
|
159
|
+
});
|
160
|
+
});
|
161
|
+
expect(await Promise.all(fns)).toEqual(
|
162
|
+
Array(100)
|
163
|
+
.fill(undefined)
|
164
|
+
.map((_, i) => `${i}:42`),
|
165
|
+
);
|
166
|
+
expect(reallyOnce).toHaveBeenCalledTimes(1);
|
167
|
+
const diff = Date.now() - start;
|
168
|
+
expect(diff).toBeGreaterThanOrEqual(99);
|
169
|
+
expect(diff).toBeLessThan(150);
|
170
|
+
expect(orderFn).toHaveBeenCalledTimes(100);
|
171
|
+
expect(orderFn.mock.calls.map(([i]) => i)).toEqual(
|
172
|
+
Array(100)
|
173
|
+
.fill(0)
|
174
|
+
.map((_, i) => i),
|
175
|
+
);
|
176
|
+
});
|
177
|
+
|
178
|
+
it("reset", async () => {
|
179
|
+
const once = new ResolveOnce<number>();
|
180
|
+
const orderFn = vi.fn(async () => 42);
|
181
|
+
once.once(orderFn);
|
182
|
+
once.once(orderFn);
|
183
|
+
once.once(orderFn);
|
184
|
+
once.reset();
|
185
|
+
once.once(orderFn);
|
186
|
+
once.once(orderFn);
|
187
|
+
once.reset();
|
188
|
+
once.once(orderFn);
|
189
|
+
once.once(orderFn);
|
190
|
+
once.reset();
|
191
|
+
expect(orderFn).toHaveBeenCalledTimes(3);
|
192
|
+
});
|
193
|
+
|
194
|
+
it("keyed", async () => {
|
195
|
+
const keyed = new KeyedResolvOnce<number>();
|
196
|
+
const a_orderFn = vi.fn(async () => 42);
|
197
|
+
const b_orderFn = vi.fn(async () => 42);
|
198
|
+
for (let i = 0; i < 5; i++) {
|
199
|
+
keyed.get("a").once(a_orderFn);
|
200
|
+
keyed.get(() => "a").once(a_orderFn);
|
201
|
+
keyed.get("b").once(b_orderFn);
|
202
|
+
keyed.get(() => "b").once(b_orderFn);
|
203
|
+
expect(a_orderFn).toHaveBeenCalledTimes(i + 1);
|
204
|
+
expect(b_orderFn).toHaveBeenCalledTimes(i + 1);
|
205
|
+
keyed.reset();
|
206
|
+
}
|
207
|
+
});
|
208
|
+
|
209
|
+
it("keyed with pass ctx", async () => {
|
210
|
+
const keyed = new KeyedResolvOnce<number>();
|
211
|
+
const a_orderFn = vi.fn(async (key) => key);
|
212
|
+
const b_orderFn = vi.fn(async (key) => key);
|
213
|
+
await Promise.all([
|
214
|
+
keyed.get("a").once(a_orderFn),
|
215
|
+
keyed.get(() => "a").once(a_orderFn),
|
216
|
+
keyed.get("b").once(b_orderFn),
|
217
|
+
keyed.get(() => "b").once(b_orderFn),
|
218
|
+
]);
|
219
|
+
expect(a_orderFn).toHaveBeenCalledTimes(1);
|
220
|
+
expect(a_orderFn).toHaveBeenCalledWith("a");
|
221
|
+
expect(b_orderFn).toHaveBeenCalledTimes(1);
|
222
|
+
expect(b_orderFn).toHaveBeenCalledWith("b");
|
223
|
+
});
|
224
|
+
|
225
|
+
it("keyed asyncGet", async () => {
|
226
|
+
const keyed = new KeyedResolvOnce<number>();
|
227
|
+
const a_orderFn = vi.fn(async (key) => key);
|
228
|
+
const b_orderFn = vi.fn(async (key) => key);
|
229
|
+
await Promise.all([
|
230
|
+
keyed
|
231
|
+
.asyncGet(async () => {
|
232
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
233
|
+
return "a";
|
234
|
+
})
|
235
|
+
.then((resolveOnce) => {
|
236
|
+
resolveOnce.once(a_orderFn);
|
237
|
+
}),
|
238
|
+
keyed
|
239
|
+
.asyncGet(async () => {
|
240
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
241
|
+
return "b";
|
242
|
+
})
|
243
|
+
.then((resolveOnce) => {
|
244
|
+
resolveOnce.once(b_orderFn);
|
245
|
+
}),
|
246
|
+
]);
|
247
|
+
expect(a_orderFn).toHaveBeenCalledTimes(1);
|
248
|
+
expect(a_orderFn).toHaveBeenCalledWith("a");
|
249
|
+
expect(b_orderFn).toHaveBeenCalledTimes(1);
|
250
|
+
expect(b_orderFn).toHaveBeenCalledWith("b");
|
251
|
+
});
|
252
|
+
|
253
|
+
function shuffle<T>(array: T[]): T[] {
|
254
|
+
let currentIndex = array.length;
|
255
|
+
|
256
|
+
// While there remain elements to shuffle...
|
257
|
+
while (currentIndex != 0) {
|
258
|
+
// Pick a remaining element...
|
259
|
+
const randomIndex = Math.floor(Math.random() * currentIndex);
|
260
|
+
currentIndex--;
|
261
|
+
|
262
|
+
// And swap it with the current element.
|
263
|
+
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
264
|
+
}
|
265
|
+
return array;
|
266
|
+
}
|
267
|
+
|
268
|
+
it("ResolveSeq", async () => {
|
269
|
+
const seq = new ResolveSeq<number>();
|
270
|
+
let enter = 0;
|
271
|
+
let leave = 0;
|
272
|
+
const actions = Array(10)
|
273
|
+
.fill(0)
|
274
|
+
.map((_, i) => {
|
275
|
+
return seq.add(async () => {
|
276
|
+
expect(enter++).toBe(i);
|
277
|
+
await new Promise((resolve) => setTimeout(resolve, i * 3));
|
278
|
+
await new Promise((resolve) => setTimeout(resolve, i * 2));
|
279
|
+
expect(leave++).toBe(i);
|
280
|
+
expect(leave).toBe(enter);
|
281
|
+
return i;
|
282
|
+
}, i);
|
283
|
+
});
|
284
|
+
const ret = await Promise.all(shuffle(actions));
|
285
|
+
expect(ret.length).toBe(10);
|
286
|
+
expect(enter).toBe(10);
|
287
|
+
expect(leave).toBe(10);
|
288
|
+
});
|
289
|
+
|
290
|
+
it("with promise", async () => {
|
291
|
+
const once = new ResolveOnce<number>();
|
292
|
+
let val = 42;
|
293
|
+
const fn = async (): Promise<number> => {
|
294
|
+
return new Promise<number>((resolve) => {
|
295
|
+
setTimeout(() => {
|
296
|
+
resolve(val++);
|
297
|
+
}, 10);
|
298
|
+
});
|
299
|
+
};
|
300
|
+
expect(await once.once(fn)).toBe(42);
|
301
|
+
expect(await once.once(fn)).toBe(42);
|
302
|
+
});
|
303
|
+
|
304
|
+
it("without promise", () => {
|
305
|
+
const once = new ResolveOnce<number>();
|
306
|
+
let val = 42;
|
307
|
+
const fn = (): number => val++;
|
308
|
+
expect(once.once(fn)).toBe(42);
|
309
|
+
expect(once.once(fn)).toBe(42);
|
310
|
+
});
|
311
|
+
|
312
|
+
it("without promise but exception", () => {
|
313
|
+
const once = new ResolveOnce<number>();
|
314
|
+
let val = 42;
|
315
|
+
const fn = (): Promise<number> => {
|
316
|
+
throw new Error(`nope ${val++}`);
|
317
|
+
};
|
318
|
+
expect(() => once.once(fn)).toThrowError("nope 42");
|
319
|
+
expect(() => once.once(fn)).toThrowError("nope 42");
|
320
|
+
});
|
321
|
+
});
|
@@ -0,0 +1,179 @@
|
|
1
|
+
import { Future } from "./future.js";
|
2
|
+
|
3
|
+
interface ResolveSeqItem<T, C> {
|
4
|
+
readonly future: Future<T>;
|
5
|
+
readonly fn: (c: C) => Promise<T>;
|
6
|
+
readonly id?: number;
|
7
|
+
}
|
8
|
+
|
9
|
+
export class ResolveSeq<T, C = void> {
|
10
|
+
readonly ctx: C;
|
11
|
+
constructor(ctx?: C) {
|
12
|
+
this.ctx = ctx as C;
|
13
|
+
}
|
14
|
+
reset(): void {
|
15
|
+
/* noop */
|
16
|
+
}
|
17
|
+
async _step(item?: ResolveSeqItem<T, C> | undefined): Promise<void> {
|
18
|
+
if (!item) {
|
19
|
+
// done
|
20
|
+
return;
|
21
|
+
}
|
22
|
+
item
|
23
|
+
.fn(this.ctx)
|
24
|
+
.then((value) => item.future.resolve(value))
|
25
|
+
.catch((e) => item.future.reject(e as Error))
|
26
|
+
.finally(() => {
|
27
|
+
this._seqFutures.shift();
|
28
|
+
this._step(this._seqFutures[0]);
|
29
|
+
});
|
30
|
+
}
|
31
|
+
readonly _seqFutures: ResolveSeqItem<T, C>[] = [];
|
32
|
+
async add(fn: (c: C) => Promise<T>, id?: number): Promise<T> {
|
33
|
+
const future = new Future<T>();
|
34
|
+
this._seqFutures.push({ future, fn, id });
|
35
|
+
if (this._seqFutures.length === 1) {
|
36
|
+
this._step(this._seqFutures[0]);
|
37
|
+
}
|
38
|
+
return future.asPromise();
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
export class ResolveOnce<T, CTX = void> {
|
43
|
+
_onceDone = false;
|
44
|
+
readonly _onceFutures: Future<T>[] = [];
|
45
|
+
_onceOk = false;
|
46
|
+
_onceValue?: T;
|
47
|
+
_onceError?: Error;
|
48
|
+
_isPromise = false;
|
49
|
+
|
50
|
+
readonly ctx: CTX;
|
51
|
+
|
52
|
+
constructor(ctx?: CTX) {
|
53
|
+
this.ctx = ctx as CTX;
|
54
|
+
}
|
55
|
+
|
56
|
+
get ready(): boolean {
|
57
|
+
return this._onceDone;
|
58
|
+
}
|
59
|
+
|
60
|
+
reset(): void {
|
61
|
+
this._onceDone = false;
|
62
|
+
this._onceOk = false;
|
63
|
+
this._onceValue = undefined;
|
64
|
+
this._onceError = undefined;
|
65
|
+
this._onceFutures.length = 0;
|
66
|
+
}
|
67
|
+
|
68
|
+
// T extends Option<infer U> ? U : T
|
69
|
+
once<R>(fn: (c: CTX) => R): R {
|
70
|
+
if (this._onceDone) {
|
71
|
+
if (this._onceError) {
|
72
|
+
if (this._isPromise) {
|
73
|
+
return Promise.reject(this._onceError) as unknown as R;
|
74
|
+
} else {
|
75
|
+
throw this._onceError;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
if (this._onceOk) {
|
79
|
+
if (this._isPromise) {
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
81
|
+
return Promise.resolve(this._onceValue!) as unknown as R;
|
82
|
+
} else {
|
83
|
+
return this._onceValue as unknown as R;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
throw new Error("ResolveOnce.once impossible");
|
87
|
+
}
|
88
|
+
const future = new Future<T>();
|
89
|
+
this._onceFutures.push(future);
|
90
|
+
if (this._onceFutures.length === 1) {
|
91
|
+
const okFn = (value: T): void => {
|
92
|
+
this._onceValue = value;
|
93
|
+
this._onceOk = true;
|
94
|
+
this._onceDone = true;
|
95
|
+
if (this._isPromise) {
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
97
|
+
this._onceFutures.forEach((f) => f.resolve(this._onceValue!));
|
98
|
+
}
|
99
|
+
this._onceFutures.length = 0;
|
100
|
+
};
|
101
|
+
const catchFn = (e: Error): void => {
|
102
|
+
this._onceError = e as Error;
|
103
|
+
this._onceOk = false;
|
104
|
+
this._onceValue = undefined;
|
105
|
+
this._onceDone = true;
|
106
|
+
if (this._isPromise) {
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
108
|
+
this._onceFutures.forEach((f) => f.reject(this._onceError!));
|
109
|
+
}
|
110
|
+
this._onceFutures.length = 0;
|
111
|
+
};
|
112
|
+
try {
|
113
|
+
const ret = fn(this.ctx);
|
114
|
+
if (typeof (ret as Promise<T>).then === "function") {
|
115
|
+
this._isPromise = true;
|
116
|
+
(ret as Promise<T>).then(okFn).catch(catchFn);
|
117
|
+
} else {
|
118
|
+
okFn(ret as unknown as T);
|
119
|
+
}
|
120
|
+
} catch (e) {
|
121
|
+
catchFn(e as Error);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
if (this._isPromise) {
|
125
|
+
return future.asPromise() as unknown as R;
|
126
|
+
} else {
|
127
|
+
// abit funky but i don't want to impl the return just once
|
128
|
+
return this.once(fn);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
export class Keyed<T extends { reset: () => void }, K = string> {
|
134
|
+
private readonly _map = new Map<K, T>();
|
135
|
+
|
136
|
+
readonly factory: (key: K) => T;
|
137
|
+
constructor(factory: (key: K) => T) {
|
138
|
+
this.factory = factory;
|
139
|
+
}
|
140
|
+
|
141
|
+
async asyncGet(key: () => Promise<K>): Promise<T> {
|
142
|
+
return this.get(await key());
|
143
|
+
}
|
144
|
+
|
145
|
+
get(key: K | (() => K)): T {
|
146
|
+
if (typeof key === "function") {
|
147
|
+
key = (key as () => K)();
|
148
|
+
}
|
149
|
+
let keyed = this._map.get(key);
|
150
|
+
if (!keyed) {
|
151
|
+
keyed = this.factory(key);
|
152
|
+
this._map.set(key, keyed);
|
153
|
+
}
|
154
|
+
return keyed;
|
155
|
+
}
|
156
|
+
|
157
|
+
unget(key: K): void {
|
158
|
+
const keyed = this._map.get(key);
|
159
|
+
keyed?.reset();
|
160
|
+
this._map.delete(key);
|
161
|
+
}
|
162
|
+
|
163
|
+
reset(): void {
|
164
|
+
this._map.forEach((keyed) => keyed.reset());
|
165
|
+
this._map.clear();
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
export class KeyedResolvOnce<T, K = string> extends Keyed<ResolveOnce<T, K>, K> {
|
170
|
+
constructor() {
|
171
|
+
super((key) => new ResolveOnce<T, K>(key));
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
export class KeyedResolvSeq<T, K = string> extends Keyed<ResolveSeq<T, K>, K> {
|
176
|
+
constructor() {
|
177
|
+
super((key) => new ResolveSeq<T, K>(key));
|
178
|
+
}
|
179
|
+
}
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import { exception2Result, Result, WithoutResult } from "@adviser/cement";
|
2
|
+
// import { it } from "vitest/globals";
|
3
|
+
|
4
|
+
it("ResultOk", () => {
|
5
|
+
const result = Result.Ok(1);
|
6
|
+
expect(result.isOk()).toBe(true);
|
7
|
+
expect(result.is_ok()).toBe(true);
|
8
|
+
expect(result.Ok()).toBe(1);
|
9
|
+
expect(result.unwrap()).toBe(1);
|
10
|
+
|
11
|
+
expect(result.isErr()).toBe(false);
|
12
|
+
expect(result.is_err()).toBe(false);
|
13
|
+
expect(() => result.Err()).toThrow();
|
14
|
+
expect(() => result.unwrap_err()).toThrow();
|
15
|
+
});
|
16
|
+
|
17
|
+
it("ResultErr", () => {
|
18
|
+
const result = Result.Err("xxx");
|
19
|
+
expect(result.isOk()).toBe(false);
|
20
|
+
expect(result.is_ok()).toBe(false);
|
21
|
+
expect(result.Err().message).toEqual("xxx");
|
22
|
+
expect(result.unwrap_err().message).toBe("xxx");
|
23
|
+
|
24
|
+
expect(result.isErr()).toBe(true);
|
25
|
+
expect(result.is_err()).toBe(true);
|
26
|
+
expect(() => result.Ok()).toThrow();
|
27
|
+
expect(() => result.unwrap()).toThrow();
|
28
|
+
});
|
29
|
+
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
31
|
+
class xResult {}
|
32
|
+
class fakeResult {
|
33
|
+
is_ok(): boolean {
|
34
|
+
return true;
|
35
|
+
}
|
36
|
+
is_err(): boolean {
|
37
|
+
return false;
|
38
|
+
}
|
39
|
+
unwrap(): number {
|
40
|
+
return 1;
|
41
|
+
}
|
42
|
+
unwrap_err(): Error {
|
43
|
+
throw new Error("Result is Ok");
|
44
|
+
}
|
45
|
+
}
|
46
|
+
it("is Result", () => {
|
47
|
+
expect(Result.Is(Result.Ok(1))).toBe(true);
|
48
|
+
expect(Result.Is(Result.Err("xxx"))).toEqual(true);
|
49
|
+
expect(Result.Is(new fakeResult())).toBe(true);
|
50
|
+
expect(Result.Is(new xResult())).toBe(false);
|
51
|
+
});
|
52
|
+
|
53
|
+
it("WithoutResult", () => {
|
54
|
+
const result = Result.Ok({ a: 1 });
|
55
|
+
const a1: Partial<WithoutResult<typeof result>> = {};
|
56
|
+
a1.a = 1;
|
57
|
+
expect(a1.a).toEqual(1);
|
58
|
+
expect(result.Ok().a).toEqual(1);
|
59
|
+
});
|
60
|
+
|
61
|
+
it("sync exception2Result ok", () => {
|
62
|
+
expect(exception2Result(() => 1)).toEqual(Result.Ok(1));
|
63
|
+
});
|
64
|
+
|
65
|
+
it("sync exception2Result throw", () => {
|
66
|
+
expect(
|
67
|
+
exception2Result(() => {
|
68
|
+
throw new Error("x");
|
69
|
+
}),
|
70
|
+
).toEqual(Result.Err("x"));
|
71
|
+
});
|
72
|
+
|
73
|
+
it("async exception2Result ok", async () => {
|
74
|
+
expect(await exception2Result(async () => 1)).toEqual(Result.Ok(1));
|
75
|
+
});
|
76
|
+
|
77
|
+
it("async exception2Result throw", async () => {
|
78
|
+
expect(
|
79
|
+
await exception2Result(async () => {
|
80
|
+
throw new Error("x");
|
81
|
+
}),
|
82
|
+
).toEqual(Result.Err("x"));
|
83
|
+
});
|
84
|
+
|
85
|
+
it("result typ", () => {
|
86
|
+
function ok(): Result<number> {
|
87
|
+
return Result.Ok(1);
|
88
|
+
}
|
89
|
+
function err(): Result<number> {
|
90
|
+
return Result.Err("x");
|
91
|
+
}
|
92
|
+
expect(ok().Ok()).toBe(1);
|
93
|
+
expect(err().Err().message).toBe("x");
|
94
|
+
});
|
95
|
+
|
96
|
+
// it("Result.OK with void", () => {
|
97
|
+
// const result = Result.Ok();
|
98
|
+
// expect(result.isOk()).toBe(true);
|
99
|
+
// expect(result.is_ok()).toBe(true);
|
100
|
+
// expect(result.isErr()).toBe(false);
|
101
|
+
// expect(result.is_err()).toBe(false);
|
102
|
+
// }
|