@adviser/cement 0.0.0-jsr-t1
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/LICENSE +201 -0
- package/README.md +39 -0
- package/base-sys-abstraction-BkEiLHl0.d.ts +193 -0
- package/base-sys-abstraction-Qj7pkY1N.d.cts +193 -0
- package/chunk-7KFVMTOS.js +311 -0
- package/chunk-7KFVMTOS.js.map +1 -0
- package/chunk-GES3MUGV.js +92 -0
- package/chunk-GES3MUGV.js.map +1 -0
- package/chunk-Q65HLCNL.js +601 -0
- package/chunk-Q65HLCNL.js.map +1 -0
- package/chunk-WMMUXBDX.js +87 -0
- package/chunk-WMMUXBDX.js.map +1 -0
- package/index-Q3phXzYr.d.cts +75 -0
- package/index-tIGZMHTc.d.ts +75 -0
- package/index.cjs +2593 -0
- package/index.cjs.map +1 -0
- package/index.d.cts +532 -0
- package/index.d.ts +532 -0
- package/index.js +1533 -0
- package/index.js.map +1 -0
- package/node/index.cjs +924 -0
- package/node/index.cjs.map +1 -0
- package/node/index.d.cts +65 -0
- package/node/index.d.ts +65 -0
- package/node/index.js +398 -0
- package/node/index.js.map +1 -0
- package/package.json +81 -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/LICENSE +201 -0
- package/ts/README.md +39 -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/txt-en-decoder-CZYJUju2.d.cts +11 -0
- package/txt-en-decoder-CZYJUju2.d.ts +11 -0
- package/utils/index.cjs +341 -0
- package/utils/index.cjs.map +1 -0
- package/utils/index.d.cts +2 -0
- package/utils/index.d.ts +2 -0
- package/utils/index.js +32 -0
- package/utils/index.js.map +1 -0
- package/web/index.cjs +593 -0
- package/web/index.cjs.map +1 -0
- package/web/index.d.cts +6 -0
- package/web/index.d.ts +6 -0
- package/web/index.js +9 -0
- package/web/index.js.map +1 -0
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
|
+
// }
|