@strictly/base 0.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/.eslintrc.cjs +26 -0
- package/.out/errors/not_implemented.d.ts +3 -0
- package/.out/errors/not_implemented.js +5 -0
- package/.out/errors/unexpected_implementation.d.ts +3 -0
- package/.out/errors/unexpected_implementation.js +5 -0
- package/.out/errors/unreachable.d.ts +3 -0
- package/.out/errors/unreachable.js +6 -0
- package/.out/index.d.ts +20 -0
- package/.out/index.js +20 -0
- package/.out/test/index.d.ts +1 -0
- package/.out/test/index.js +1 -0
- package/.out/test/vitest/expect.d.ts +4 -0
- package/.out/test/vitest/expect.js +13 -0
- package/.out/tsconfig.json +16 -0
- package/.out/tsconfig.tsbuildinfo +1 -0
- package/.out/tsup.config.d.ts +3 -0
- package/.out/tsup.config.js +12 -0
- package/.out/types/element_of_array.d.ts +1 -0
- package/.out/types/element_of_array.js +1 -0
- package/.out/types/extract_generics.d.ts +2 -0
- package/.out/types/extract_generics.js +1 -0
- package/.out/types/is_field_optional.d.ts +1 -0
- package/.out/types/is_field_optional.js +1 -0
- package/.out/types/is_field_readonly.d.ts +8 -0
- package/.out/types/is_field_readonly.js +2 -0
- package/.out/types/maybe.d.ts +3 -0
- package/.out/types/maybe.js +1 -0
- package/.out/types/printable_of.d.ts +1 -0
- package/.out/types/printable_of.js +1 -0
- package/.out/types/required_of_record.d.ts +3 -0
- package/.out/types/required_of_record.js +1 -0
- package/.out/types/specs/element_of_array.tests.d.ts +1 -0
- package/.out/types/specs/element_of_array.tests.js +6 -0
- package/.out/types/specs/is_field_readonly.tests.d.ts +1 -0
- package/.out/types/specs/is_field_readonly.tests.js +9 -0
- package/.out/types/specs/printable_of.tests.d.ts +1 -0
- package/.out/types/specs/printable_of.tests.js +6 -0
- package/.out/types/specs/required_of_record.tests.d.ts +1 -0
- package/.out/types/specs/required_of_record.tests.js +12 -0
- package/.out/types/specs/string_key_of.tests.d.ts +1 -0
- package/.out/types/specs/string_key_of.tests.js +10 -0
- package/.out/types/string_key_of.d.ts +1 -0
- package/.out/types/string_key_of.js +1 -0
- package/.out/util/array.d.ts +1 -0
- package/.out/util/array.js +8 -0
- package/.out/util/cache.d.ts +13 -0
- package/.out/util/cache.js +42 -0
- package/.out/util/delay.d.ts +12 -0
- package/.out/util/delay.js +32 -0
- package/.out/util/format.d.ts +2 -0
- package/.out/util/format.js +11 -0
- package/.out/util/json.d.ts +1 -0
- package/.out/util/json.js +11 -0
- package/.out/util/poll.d.ts +8 -0
- package/.out/util/poll.js +23 -0
- package/.out/util/preconditions.d.ts +11 -0
- package/.out/util/preconditions.js +43 -0
- package/.out/util/promises.d.ts +1 -0
- package/.out/util/promises.js +11 -0
- package/.out/util/record.d.ts +10 -0
- package/.out/util/record.js +64 -0
- package/.out/util/specs/cache.tests.d.ts +1 -0
- package/.out/util/specs/cache.tests.js +49 -0
- package/.out/util/specs/format.tests.d.ts +1 -0
- package/.out/util/specs/format.tests.js +67 -0
- package/.out/util/specs/poll.tests.d.ts +1 -0
- package/.out/util/specs/poll.tests.js +47 -0
- package/.out/vitest.workspace.d.ts +2 -0
- package/.out/vitest.workspace.js +7 -0
- package/.turbo/turbo-build.log +18 -0
- package/.turbo/turbo-check-types.log +3 -0
- package/.turbo/turbo-release$colon$exports.log +3 -0
- package/README.md +3 -0
- package/dist/index.cjs +387 -0
- package/dist/index.d.cts +109 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +328 -0
- package/errors/not_implemented.ts +5 -0
- package/errors/unexpected_implementation.ts +5 -0
- package/errors/unreachable.ts +6 -0
- package/index.ts +20 -0
- package/package.exports.json +18 -0
- package/package.json +50 -0
- package/test/index.ts +1 -0
- package/test/vitest/expect.ts +16 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +16 -0
- package/tsup.config.ts +16 -0
- package/types/element_of_array.ts +1 -0
- package/types/extract_generics.ts +2 -0
- package/types/is_field_optional.ts +5 -0
- package/types/is_field_readonly.ts +8 -0
- package/types/maybe.ts +5 -0
- package/types/printable_of.ts +1 -0
- package/types/required_of_record.ts +4 -0
- package/types/specs/element_of_array.tests.ts +9 -0
- package/types/specs/is_field_readonly.tests.ts +15 -0
- package/types/specs/printable_of.tests.ts +8 -0
- package/types/specs/required_of_record.tests.ts +16 -0
- package/types/specs/string_key_of.tests.ts +16 -0
- package/types/string_key_of.ts +1 -0
- package/util/array.ts +12 -0
- package/util/cache.ts +52 -0
- package/util/delay.ts +36 -0
- package/util/format.ts +23 -0
- package/util/json.ts +10 -0
- package/util/poll.ts +40 -0
- package/util/preconditions.ts +83 -0
- package/util/promises.ts +11 -0
- package/util/record.ts +133 -0
- package/util/specs/cache.tests.ts +68 -0
- package/util/specs/format.tests.ts +70 -0
- package/util/specs/poll.tests.ts +75 -0
- package/vitest.workspace.ts +11 -0
package/util/delay.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type Delay = () => Promise<void>
|
|
2
|
+
|
|
3
|
+
export function createDelay(millis: number): Delay {
|
|
4
|
+
return function () {
|
|
5
|
+
return delay(millis)
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* creates a delay that blocks everything for the specified number of cold milliseconds initially, then
|
|
11
|
+
* the warm millis thereafter
|
|
12
|
+
* @param coldMillis the number of milliseconds to block initial delays for
|
|
13
|
+
* @param warmMillis the number of milliseconds to block subsequent delays for
|
|
14
|
+
* @returns a Promise that awaits for the specified time
|
|
15
|
+
*/
|
|
16
|
+
export function createWarmupDelay(coldMillis: number, warmMillis: number) {
|
|
17
|
+
let warmup: Promise<void> | undefined
|
|
18
|
+
return function () {
|
|
19
|
+
if (warmup != null) {
|
|
20
|
+
return warmup.then(function () {
|
|
21
|
+
return delay(warmMillis)
|
|
22
|
+
})
|
|
23
|
+
} else {
|
|
24
|
+
warmup = delay(coldMillis)
|
|
25
|
+
return warmup
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function delay(millis: number): Promise<void> {
|
|
31
|
+
return new Promise(function (resolve) {
|
|
32
|
+
setTimeout(resolve, millis)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const secondDelay = createDelay(1000)
|
package/util/format.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type FormatArg =
|
|
2
|
+
| string
|
|
3
|
+
| number
|
|
4
|
+
| boolean
|
|
5
|
+
| object
|
|
6
|
+
| null
|
|
7
|
+
| undefined
|
|
8
|
+
| symbol
|
|
9
|
+
|
|
10
|
+
export function format(
|
|
11
|
+
message: string,
|
|
12
|
+
...args: readonly FormatArg[]
|
|
13
|
+
): string {
|
|
14
|
+
let index = 0
|
|
15
|
+
return message.replaceAll(/{(\d*)}/g, function (_substring: string, indexString: string) {
|
|
16
|
+
let argIndex = parseInt(indexString)
|
|
17
|
+
if (Number.isNaN(argIndex)) {
|
|
18
|
+
argIndex = index
|
|
19
|
+
index++
|
|
20
|
+
}
|
|
21
|
+
return JSON.stringify(args[argIndex])
|
|
22
|
+
})
|
|
23
|
+
}
|
package/util/json.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2
|
+
export function errorHandlingJsonParse<T = any>(json: string, errorHandler?: (e: unknown) => void): T | null {
|
|
3
|
+
try {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
5
|
+
return JSON.parse(json) as T
|
|
6
|
+
} catch (e) {
|
|
7
|
+
errorHandler?.(e)
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
10
|
+
}
|
package/util/poll.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Maybe,
|
|
3
|
+
} from 'types/maybe'
|
|
4
|
+
import { delay } from './delay'
|
|
5
|
+
|
|
6
|
+
export function constantPollInterval(delay: number) {
|
|
7
|
+
return function () {
|
|
8
|
+
return delay
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type PollOptions = {
|
|
13
|
+
pollInterval?: (retries: number) => number,
|
|
14
|
+
retries?: number,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function poll<T>(
|
|
18
|
+
f: () => Promise<Maybe<T>>,
|
|
19
|
+
{
|
|
20
|
+
pollInterval = constantPollInterval(200),
|
|
21
|
+
retries = 3,
|
|
22
|
+
}: PollOptions = {},
|
|
23
|
+
): Promise<Maybe<T>> {
|
|
24
|
+
let retriesRemaining = retries
|
|
25
|
+
while (retriesRemaining > 0) {
|
|
26
|
+
await delay(pollInterval(retriesRemaining))
|
|
27
|
+
retriesRemaining--
|
|
28
|
+
const v = await f()
|
|
29
|
+
if (v != null) {
|
|
30
|
+
return v
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// TODO
|
|
37
|
+
// function createPoll<P, T>(
|
|
38
|
+
// p: (params: P) => Promise<Maybe<T>>,
|
|
39
|
+
// options: PollOptions,
|
|
40
|
+
// ): (params: P) => Promise<Maybe<T>>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
format,
|
|
3
|
+
type FormatArg,
|
|
4
|
+
} from './format'
|
|
5
|
+
|
|
6
|
+
export class PreconditionFailedError extends Error {
|
|
7
|
+
constructor(message: string, ...args: readonly FormatArg[]) {
|
|
8
|
+
super(format(message, ...args))
|
|
9
|
+
this.name = 'PreconditionFailedError'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function assertExistsAndReturn<T>(
|
|
14
|
+
t: T,
|
|
15
|
+
message: string,
|
|
16
|
+
...args: readonly FormatArg[]
|
|
17
|
+
): NonNullable<T> {
|
|
18
|
+
assertExists(t, message, ...args)
|
|
19
|
+
return t
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function assertExists<V>(v: V, message: string, ...args: readonly FormatArg[]): asserts v is NonNullable<V> {
|
|
23
|
+
if (v == null) {
|
|
24
|
+
throw new PreconditionFailedError(message, ...args)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function assertEqual<T extends FormatArg>(
|
|
29
|
+
a: T,
|
|
30
|
+
b: T,
|
|
31
|
+
message: string = '{} != {}',
|
|
32
|
+
arg1: FormatArg = a,
|
|
33
|
+
arg2: FormatArg = b,
|
|
34
|
+
...args: readonly FormatArg[]
|
|
35
|
+
) {
|
|
36
|
+
if (a !== b) {
|
|
37
|
+
throw new PreconditionFailedError(
|
|
38
|
+
message,
|
|
39
|
+
arg1,
|
|
40
|
+
arg2,
|
|
41
|
+
...args,
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function assertState(
|
|
47
|
+
condition: boolean,
|
|
48
|
+
message: string,
|
|
49
|
+
...args: readonly FormatArg[]
|
|
50
|
+
): asserts condition is true {
|
|
51
|
+
if (!condition) {
|
|
52
|
+
throw new PreconditionFailedError(message, ...args)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function assertIs<V, T extends V>(
|
|
57
|
+
v: V,
|
|
58
|
+
condition: (v: V) => v is T,
|
|
59
|
+
message: string,
|
|
60
|
+
...args: readonly FormatArg[]
|
|
61
|
+
): asserts v is T {
|
|
62
|
+
if (!condition(v)) {
|
|
63
|
+
throw new PreconditionFailedError(message, ...args)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function checkUnary<T>(
|
|
68
|
+
t: readonly T[],
|
|
69
|
+
message: string,
|
|
70
|
+
...args: readonly FormatArg[]
|
|
71
|
+
): T {
|
|
72
|
+
if (t.length !== 1) {
|
|
73
|
+
throw new PreconditionFailedError(message, ...args)
|
|
74
|
+
}
|
|
75
|
+
return t[0]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function checkValidNumber(n: number, message: string, ...args: readonly FormatArg[]): number {
|
|
79
|
+
if (isNaN(n) || !isFinite(n)) {
|
|
80
|
+
throw new PreconditionFailedError(message, ...args)
|
|
81
|
+
}
|
|
82
|
+
return n
|
|
83
|
+
}
|
package/util/promises.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function callAsPromise(f: (cb: (e?: unknown) => void) => void): Promise<void> {
|
|
2
|
+
return new Promise<void>(function (resolve, reject) {
|
|
3
|
+
f(function (e?: unknown) {
|
|
4
|
+
if (e == null) {
|
|
5
|
+
resolve()
|
|
6
|
+
}
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
8
|
+
reject(e)
|
|
9
|
+
})
|
|
10
|
+
})
|
|
11
|
+
}
|
package/util/record.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export function reverse<
|
|
2
|
+
Key extends string | number | symbol,
|
|
3
|
+
Value extends string | number | symbol,
|
|
4
|
+
>(obj: Record<Key, Value>): Record<Value, Key> {
|
|
5
|
+
return Object.keys(obj).reduce((acc, stringKey) => {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
7
|
+
const key = stringKey as Key
|
|
8
|
+
const value = obj[key]
|
|
9
|
+
acc[value] = key
|
|
10
|
+
return acc
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
12
|
+
}, {} as Record<Value, Key>)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// TODO simplify the generics
|
|
16
|
+
export function rollup<
|
|
17
|
+
R extends Record<K, V>,
|
|
18
|
+
K extends string | number | symbol = keyof R,
|
|
19
|
+
V = R[K],
|
|
20
|
+
>(...records: Partial<R>[]): R {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
22
|
+
return records.slice(1).reduce<Partial<R>>((acc, record) => {
|
|
23
|
+
Object.keys(record).forEach((key) => {
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
25
|
+
const k = key as K
|
|
26
|
+
acc[k] = acc[k] ?? record[k]
|
|
27
|
+
})
|
|
28
|
+
return acc
|
|
29
|
+
}, records[0]) as R
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// TODO simplify the generics
|
|
33
|
+
export function union<
|
|
34
|
+
R1 extends Readonly<Record<K1, V1>>,
|
|
35
|
+
K1 extends string | number | symbol,
|
|
36
|
+
V1 extends R1[K1],
|
|
37
|
+
R2 extends Readonly<Record<K2, V2>>,
|
|
38
|
+
K2 extends string | number | symbol,
|
|
39
|
+
V2 extends R2[K2],
|
|
40
|
+
>(
|
|
41
|
+
r1: R1,
|
|
42
|
+
r2: R2,
|
|
43
|
+
): R1 & R2 {
|
|
44
|
+
return {
|
|
45
|
+
...r1,
|
|
46
|
+
...r2,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// TODO simplify the generics
|
|
51
|
+
export function map<
|
|
52
|
+
K extends string | number | symbol,
|
|
53
|
+
V,
|
|
54
|
+
R = V,
|
|
55
|
+
>(
|
|
56
|
+
r: Readonly<Record<K, V>>,
|
|
57
|
+
f: (k: K, v: V) => R,
|
|
58
|
+
): Record<K, R> {
|
|
59
|
+
// TODO can use reduce to implement map
|
|
60
|
+
return Object.entries<V>(r).reduce(
|
|
61
|
+
function (acc, [
|
|
62
|
+
k,
|
|
63
|
+
v,
|
|
64
|
+
]) {
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
66
|
+
const typedKey = k as K
|
|
67
|
+
acc[typedKey] = f(typedKey, v)
|
|
68
|
+
return acc
|
|
69
|
+
},
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
71
|
+
{} as Record<K, R>,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// TODO simplify the generics
|
|
76
|
+
export function reduce<
|
|
77
|
+
K extends string | number | symbol,
|
|
78
|
+
V,
|
|
79
|
+
A,
|
|
80
|
+
>(
|
|
81
|
+
r: Readonly<Record<K, V>>,
|
|
82
|
+
f: (acc: A, k: K, v: V) => A,
|
|
83
|
+
a: A,
|
|
84
|
+
): A {
|
|
85
|
+
return Object.entries<V>(r).reduce(
|
|
86
|
+
function (acc, [
|
|
87
|
+
k,
|
|
88
|
+
v,
|
|
89
|
+
]) {
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
91
|
+
const typedKey = k as K
|
|
92
|
+
return f(acc, typedKey, v)
|
|
93
|
+
},
|
|
94
|
+
a,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function forEach<
|
|
99
|
+
R extends Readonly<Record<K, V>>,
|
|
100
|
+
K extends string | number | symbol = R extends Readonly<Record<infer Kk, infer _Vv>> ? Kk : never,
|
|
101
|
+
V = R extends Readonly<Record<infer _Kk, infer Vv>> ? Vv : never,
|
|
102
|
+
>(r: R, f: (k: K, v: R[K]) => void) {
|
|
103
|
+
return Object.entries<V>(r).forEach(
|
|
104
|
+
function ([
|
|
105
|
+
k,
|
|
106
|
+
v,
|
|
107
|
+
]) {
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
109
|
+
return f(k as K, v as R[K])
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function toArray<
|
|
115
|
+
K extends string | number | symbol,
|
|
116
|
+
V,
|
|
117
|
+
>(r: Readonly<Record<K, V>>): readonly (readonly [K, V])[] {
|
|
118
|
+
return reduce<K, V, [K, V][]>(
|
|
119
|
+
r,
|
|
120
|
+
function (acc, k, v) {
|
|
121
|
+
acc.push([
|
|
122
|
+
k,
|
|
123
|
+
v,
|
|
124
|
+
])
|
|
125
|
+
return acc
|
|
126
|
+
},
|
|
127
|
+
[],
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type Mutable<T> = {
|
|
132
|
+
-readonly [K in keyof T]: T[K]
|
|
133
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Cache,
|
|
3
|
+
type CacheValueFactory,
|
|
4
|
+
} from 'util/cache'
|
|
5
|
+
import {
|
|
6
|
+
type Mock,
|
|
7
|
+
vi,
|
|
8
|
+
} from 'vitest'
|
|
9
|
+
|
|
10
|
+
describe('cache', function () {
|
|
11
|
+
type Args = [string, number, boolean]
|
|
12
|
+
let cache: Cache<Args, boolean>
|
|
13
|
+
let valueFactory: Mock<CacheValueFactory<Args, boolean>>
|
|
14
|
+
|
|
15
|
+
beforeEach(function () {
|
|
16
|
+
valueFactory = vi.fn()
|
|
17
|
+
valueFactory.mockReturnValue(true)
|
|
18
|
+
cache = new Cache(
|
|
19
|
+
valueFactory,
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('creates value that does not exist', function () {
|
|
24
|
+
let value: boolean
|
|
25
|
+
const params: Args = [
|
|
26
|
+
'a',
|
|
27
|
+
1,
|
|
28
|
+
false,
|
|
29
|
+
]
|
|
30
|
+
beforeEach(function () {
|
|
31
|
+
value = cache.retrieveOrCreate(...params)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('calls the value factory with the transformed key', function () {
|
|
35
|
+
expect(valueFactory).toHaveBeenCalledOnce()
|
|
36
|
+
expect(valueFactory).toHaveBeenCalledWith(...params)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('returns the expected value', function () {
|
|
40
|
+
expect(value).toBeTruthy()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('retrieveByKey', function () {
|
|
44
|
+
it('retrieves value by key', function () {
|
|
45
|
+
expect(cache.retrieve(...params)).toEqual([true])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('returns nothing when a non-existent key is supplied', function () {
|
|
49
|
+
expect(cache.retrieve('a', 1, true)).toBeNull()
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('looking up previously created value', function () {
|
|
54
|
+
let cachedValue: boolean
|
|
55
|
+
beforeEach(function () {
|
|
56
|
+
cachedValue = cache.retrieveOrCreate(...params)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('does not create the value again', function () {
|
|
60
|
+
expect(valueFactory).toHaveBeenCalledOnce()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('returns the expected value', function () {
|
|
64
|
+
expect(cachedValue).toBeTruthy()
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { format } from 'util/format'
|
|
2
|
+
|
|
3
|
+
describe('format', function () {
|
|
4
|
+
it.each(
|
|
5
|
+
[
|
|
6
|
+
[
|
|
7
|
+
'no args',
|
|
8
|
+
'message',
|
|
9
|
+
'message',
|
|
10
|
+
],
|
|
11
|
+
[
|
|
12
|
+
'one anonymous arg',
|
|
13
|
+
'arg {}',
|
|
14
|
+
'arg 1',
|
|
15
|
+
1,
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
'one indexed arg',
|
|
19
|
+
'arg {0}',
|
|
20
|
+
'arg "0"',
|
|
21
|
+
'0',
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
'two args',
|
|
25
|
+
'args {}, {}',
|
|
26
|
+
'args 1, 2',
|
|
27
|
+
1,
|
|
28
|
+
2,
|
|
29
|
+
],
|
|
30
|
+
[
|
|
31
|
+
'two indexed args',
|
|
32
|
+
'args {0}, {1}',
|
|
33
|
+
'args 1, 2',
|
|
34
|
+
1,
|
|
35
|
+
2,
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
'two indexed args, reversed',
|
|
39
|
+
'args {1}, {0}',
|
|
40
|
+
'args 2, 1',
|
|
41
|
+
1,
|
|
42
|
+
2,
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
'null',
|
|
46
|
+
'null? {}',
|
|
47
|
+
'null? null',
|
|
48
|
+
null,
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
'mixed argument types',
|
|
52
|
+
'{} {} {} {}',
|
|
53
|
+
'1 "2" true -5',
|
|
54
|
+
1,
|
|
55
|
+
'2',
|
|
56
|
+
true,
|
|
57
|
+
-5,
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
'object',
|
|
61
|
+
'{}',
|
|
62
|
+
'{"b":true}',
|
|
63
|
+
{ b: true },
|
|
64
|
+
],
|
|
65
|
+
] as const,
|
|
66
|
+
)('formats %s', function (_name, message, expectedResult, ...args) {
|
|
67
|
+
const result = format(message, ...args)
|
|
68
|
+
expect(result).toEqual(expectedResult)
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Maybe,
|
|
3
|
+
} from 'types/maybe'
|
|
4
|
+
import {
|
|
5
|
+
constantPollInterval,
|
|
6
|
+
poll,
|
|
7
|
+
} from 'util/poll'
|
|
8
|
+
import {
|
|
9
|
+
type Mock,
|
|
10
|
+
vi,
|
|
11
|
+
} from 'vitest'
|
|
12
|
+
|
|
13
|
+
describe('poll', function () {
|
|
14
|
+
const pollInterval = constantPollInterval(1)
|
|
15
|
+
let callee: Mock<() => Promise<Maybe<number>>>
|
|
16
|
+
|
|
17
|
+
beforeEach(function () {
|
|
18
|
+
callee = vi.fn()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('returns the success value', async function () {
|
|
22
|
+
const value = 1
|
|
23
|
+
callee.mockResolvedValueOnce([value])
|
|
24
|
+
const result = await poll(
|
|
25
|
+
callee,
|
|
26
|
+
{
|
|
27
|
+
pollInterval,
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
expect(result).toEqual([value])
|
|
31
|
+
expect(callee).toHaveBeenCalledTimes(1)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('returns the success value after a retry', async function () {
|
|
35
|
+
const value = 1
|
|
36
|
+
callee.mockResolvedValueOnce(null)
|
|
37
|
+
callee.mockResolvedValueOnce([value])
|
|
38
|
+
const result = await poll(
|
|
39
|
+
callee,
|
|
40
|
+
{
|
|
41
|
+
pollInterval,
|
|
42
|
+
retries: 2,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
expect(result).toEqual([value])
|
|
46
|
+
expect(callee).toHaveBeenCalledTimes(2)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('returns null when polling result not available after retries', async function () {
|
|
50
|
+
callee.mockResolvedValue(null)
|
|
51
|
+
const retries = 4
|
|
52
|
+
const result = await poll(
|
|
53
|
+
callee,
|
|
54
|
+
{
|
|
55
|
+
pollInterval,
|
|
56
|
+
retries,
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
expect(result).toBe(null)
|
|
60
|
+
expect(callee).toHaveBeenCalledTimes(retries)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('reports errors immediately', async function () {
|
|
64
|
+
const error = new Error()
|
|
65
|
+
callee.mockRejectedValue(error)
|
|
66
|
+
|
|
67
|
+
await expect(poll(
|
|
68
|
+
callee,
|
|
69
|
+
{
|
|
70
|
+
pollInterval,
|
|
71
|
+
},
|
|
72
|
+
)).rejects.toBe(error)
|
|
73
|
+
expect(callee).toHaveBeenCalledTimes(1)
|
|
74
|
+
})
|
|
75
|
+
})
|