@atproto/common-web 0.5.1 → 0.5.3
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/CHANGELOG.md +26 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +5 -3
- package/dist/util.js.map +1 -1
- package/package.json +18 -13
- package/jest.config.cjs +0 -21
- package/src/arrays.ts +0 -23
- package/src/async.ts +0 -214
- package/src/check.ts +0 -31
- package/src/did-doc.ts +0 -202
- package/src/index.ts +0 -13
- package/src/ipld.ts +0 -48
- package/src/retry.ts +0 -58
- package/src/strings.ts +0 -49
- package/src/tid.ts +0 -110
- package/src/times.ts +0 -15
- package/src/types.ts +0 -64
- package/src/util.ts +0 -180
- package/tests/check.test.ts +0 -89
- package/tests/retry.test.ts +0 -93
- package/tests/tid.test.ts +0 -142
- package/tests/util.test.ts +0 -107
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
package/src/ipld.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { LexValue, lexEquals } from '@atproto/lex-data'
|
|
2
|
-
import { JsonValue, jsonToLex, lexToJson } from '@atproto/lex-json'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @deprecated Use {@link JsonValue} from `@atproto/lex-cbor` instead.
|
|
6
|
-
*/
|
|
7
|
-
export type LegacyJsonValue = unknown
|
|
8
|
-
|
|
9
|
-
export type { LegacyJsonValue as JsonValue }
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @deprecated Use {@link LexValue} from `@atproto/lex-cbor` instead.
|
|
13
|
-
*/
|
|
14
|
-
export type IpldValue = unknown
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Converts a JSON-compatible value to an IPLD-compatible value.
|
|
18
|
-
* @deprecated Use {@link jsonToLex} from `@atproto/lex-cbor` instead.
|
|
19
|
-
*/
|
|
20
|
-
export const jsonToIpld = (val: LegacyJsonValue): IpldValue => {
|
|
21
|
-
return jsonToLex(val as JsonValue, { strict: false })
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Converts an IPLD-compatible value to a JSON-compatible value.
|
|
26
|
-
* @deprecated Use {@link lexToJson} from `@atproto/lex-cbor` instead.
|
|
27
|
-
*/
|
|
28
|
-
export const ipldToJson = (val: IpldValue): LegacyJsonValue => {
|
|
29
|
-
// Legacy behavior(s)
|
|
30
|
-
if (val === undefined) return val
|
|
31
|
-
if (Number.isNaN(val)) return val
|
|
32
|
-
|
|
33
|
-
return lexToJson(val as LexValue)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Compares two IPLD-compatible values for deep equality.
|
|
38
|
-
* @deprecated Use {@link lexEquals} from `@atproto/lex-cbor` instead.
|
|
39
|
-
*/
|
|
40
|
-
export const ipldEquals = (a: IpldValue, b: IpldValue): boolean => {
|
|
41
|
-
if (!lexEquals(a as LexValue, b as LexValue)) return false
|
|
42
|
-
|
|
43
|
-
// @NOTE The previous implementation used "===" which treats NaN as unequal to
|
|
44
|
-
// NaN.
|
|
45
|
-
if (Number.isNaN(a)) return false
|
|
46
|
-
|
|
47
|
-
return true
|
|
48
|
-
}
|
package/src/retry.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { wait } from './util.js'
|
|
2
|
-
|
|
3
|
-
export type RetryOptions = {
|
|
4
|
-
maxRetries?: number
|
|
5
|
-
getWaitMs?: (n: number) => number | null
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export async function retry<T>(
|
|
9
|
-
fn: () => Promise<T>,
|
|
10
|
-
opts: RetryOptions & {
|
|
11
|
-
retryable?: (err: unknown) => boolean
|
|
12
|
-
} = {},
|
|
13
|
-
): Promise<T> {
|
|
14
|
-
const { maxRetries = 3, retryable = () => true, getWaitMs = backoffMs } = opts
|
|
15
|
-
let retries = 0
|
|
16
|
-
let doneError: unknown
|
|
17
|
-
while (!doneError) {
|
|
18
|
-
try {
|
|
19
|
-
return await fn()
|
|
20
|
-
} catch (err) {
|
|
21
|
-
const waitMs = getWaitMs(retries)
|
|
22
|
-
const willRetry =
|
|
23
|
-
retries < maxRetries && waitMs !== null && retryable(err)
|
|
24
|
-
if (willRetry) {
|
|
25
|
-
retries += 1
|
|
26
|
-
if (waitMs !== 0) {
|
|
27
|
-
await wait(waitMs)
|
|
28
|
-
}
|
|
29
|
-
} else {
|
|
30
|
-
doneError = err
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
throw doneError
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function createRetryable(retryable: (err: unknown) => boolean) {
|
|
38
|
-
return async <T>(fn: () => Promise<T>, opts?: RetryOptions) =>
|
|
39
|
-
retry(fn, { ...opts, retryable })
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Waits exponential backoff with max and jitter: ~100, ~200, ~400, ~800, ~1000, ~1000, ...
|
|
43
|
-
export function backoffMs(n: number, multiplier = 100, max = 1000) {
|
|
44
|
-
const exponentialMs = Math.pow(2, n) * multiplier
|
|
45
|
-
const ms = Math.min(exponentialMs, max)
|
|
46
|
-
return jitter(ms)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Adds randomness +/-15% of value
|
|
50
|
-
function jitter(value: number) {
|
|
51
|
-
const delta = value * 0.15
|
|
52
|
-
return value + randomRange(-delta, delta)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function randomRange(from: number, to: number) {
|
|
56
|
-
const rand = Math.random() * (to - from)
|
|
57
|
-
return rand + from
|
|
58
|
-
}
|
package/src/strings.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { fromBase64, graphemeLen, toBase64, utf8Len } from '@atproto/lex-data'
|
|
2
|
-
import {
|
|
3
|
-
LanguageTag,
|
|
4
|
-
isValidLanguage,
|
|
5
|
-
parseLanguageString,
|
|
6
|
-
} from '@atproto/syntax'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @deprecated Use {@link graphemeLen} from `@atproto/lex-data` instead.
|
|
10
|
-
*/
|
|
11
|
-
const graphemeLenLegacy = graphemeLen
|
|
12
|
-
export { graphemeLenLegacy as graphemeLen }
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @deprecated Use {@link utf8Len} from `@atproto/lex-data` instead.
|
|
16
|
-
*/
|
|
17
|
-
const utf8LenLegacy = utf8Len
|
|
18
|
-
export { utf8LenLegacy as utf8Len }
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @deprecated Use {@link LanguageTag} from `@atproto/lex-data` instead.
|
|
22
|
-
*/
|
|
23
|
-
type LanguageTagLegacy = LanguageTag
|
|
24
|
-
export type { LanguageTagLegacy as LanguageTag }
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @deprecated Use {@link parseLanguageString} from `@atproto/syntax` instead.
|
|
28
|
-
*/
|
|
29
|
-
const parseLanguageLegacy = parseLanguageString
|
|
30
|
-
export { parseLanguageLegacy as parseLanguage }
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @deprecated Use {@link isLanguageString} from `@atproto/syntax` instead.
|
|
34
|
-
*/
|
|
35
|
-
export const validateLanguage = isValidLanguage
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @deprecated Use {@link toBase64} from `@atproto/lex-data` instead.
|
|
39
|
-
*/
|
|
40
|
-
export const utf8ToB64Url = (utf8: string): string => {
|
|
41
|
-
return toBase64(new TextEncoder().encode(utf8), 'base64url')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @deprecated Use {@link fromBase64} from `@atproto/lex-data` instead.
|
|
46
|
-
*/
|
|
47
|
-
export const b64UrlToUtf8 = (b64: string): string => {
|
|
48
|
-
return new TextDecoder().decode(fromBase64(b64, 'base64url'))
|
|
49
|
-
}
|
package/src/tid.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { s32decode, s32encode } from './util.js'
|
|
2
|
-
|
|
3
|
-
const TID_LEN = 13
|
|
4
|
-
|
|
5
|
-
let lastTimestamp = 0
|
|
6
|
-
let timestampCount = 0
|
|
7
|
-
let clockid: number | null = null
|
|
8
|
-
|
|
9
|
-
function dedash(str: string): string {
|
|
10
|
-
return str.replaceAll('-', '')
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class TID {
|
|
14
|
-
str: string
|
|
15
|
-
|
|
16
|
-
constructor(str: string) {
|
|
17
|
-
const noDashes = dedash(str)
|
|
18
|
-
if (noDashes.length !== TID_LEN) {
|
|
19
|
-
throw new Error(`Poorly formatted TID: ${noDashes.length} length`)
|
|
20
|
-
}
|
|
21
|
-
this.str = noDashes
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static next(prev?: TID): TID {
|
|
25
|
-
// javascript does not have microsecond precision
|
|
26
|
-
// instead, we append a counter to the timestamp to indicate if multiple timestamps were created within the same millisecond
|
|
27
|
-
// take max of current time & last timestamp to prevent tids moving backwards if system clock drifts backwards
|
|
28
|
-
const time = Math.max(Date.now(), lastTimestamp)
|
|
29
|
-
if (time === lastTimestamp) {
|
|
30
|
-
timestampCount++
|
|
31
|
-
}
|
|
32
|
-
lastTimestamp = time
|
|
33
|
-
const timestamp = time * 1000 + timestampCount
|
|
34
|
-
// the bottom 32 clock ids can be randomized & are not guaranteed to be collision resistant
|
|
35
|
-
// we use the same clockid for all tids coming from this machine
|
|
36
|
-
if (clockid === null) {
|
|
37
|
-
clockid = Math.floor(Math.random() * 32)
|
|
38
|
-
}
|
|
39
|
-
const tid = TID.fromTime(timestamp, clockid)
|
|
40
|
-
if (!prev || tid.newerThan(prev)) {
|
|
41
|
-
return tid
|
|
42
|
-
}
|
|
43
|
-
return TID.fromTime(prev.timestamp() + 1, clockid)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static nextStr(prev?: string): string {
|
|
47
|
-
return TID.next(prev ? new TID(prev) : undefined).toString()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
static fromTime(timestamp: number, clockid: number): TID {
|
|
51
|
-
// base32 encode with encoding variant sort (s32)
|
|
52
|
-
const str = `${s32encode(timestamp)}${s32encode(clockid).padStart(2, '2')}`
|
|
53
|
-
return new TID(str)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
static fromStr(str: string): TID {
|
|
57
|
-
return new TID(str)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
static oldestFirst(a: TID, b: TID): number {
|
|
61
|
-
return a.compareTo(b)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
static newestFirst(a: TID, b: TID): number {
|
|
65
|
-
return b.compareTo(a)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
static is(str: string): boolean {
|
|
69
|
-
return dedash(str).length === TID_LEN
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
timestamp(): number {
|
|
73
|
-
return s32decode(this.str.slice(0, 11))
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
clockid(): number {
|
|
77
|
-
return s32decode(this.str.slice(11, 13))
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
formatted(): string {
|
|
81
|
-
const str = this.toString()
|
|
82
|
-
return `${str.slice(0, 4)}-${str.slice(4, 7)}-${str.slice(
|
|
83
|
-
7,
|
|
84
|
-
11,
|
|
85
|
-
)}-${str.slice(11, 13)}`
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
toString(): string {
|
|
89
|
-
return this.str
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// newer > older
|
|
93
|
-
compareTo(other: TID): number {
|
|
94
|
-
if (this.str > other.str) return 1
|
|
95
|
-
if (this.str < other.str) return -1
|
|
96
|
-
return 0
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
equals(other: TID): boolean {
|
|
100
|
-
return this.str === other.str
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
newerThan(other: TID): boolean {
|
|
104
|
-
return this.compareTo(other) > 0
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
olderThan(other: TID): boolean {
|
|
108
|
-
return this.compareTo(other) < 0
|
|
109
|
-
}
|
|
110
|
-
}
|
package/src/times.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export const SECOND = 1000
|
|
2
|
-
export const MINUTE = SECOND * 60
|
|
3
|
-
export const HOUR = MINUTE * 60
|
|
4
|
-
export const DAY = HOUR * 24
|
|
5
|
-
|
|
6
|
-
export const lessThanAgoMs = (time: Date, range: number) => {
|
|
7
|
-
return Date.now() < time.getTime() + range
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const addHoursToDate = (hours: number, startingDate?: Date): Date => {
|
|
11
|
-
// When date is passed, clone before calling `setHours()` so that we are not mutating the original date
|
|
12
|
-
const currentDate = startingDate ? new Date(startingDate) : new Date()
|
|
13
|
-
currentDate.setHours(currentDate.getHours() + hours)
|
|
14
|
-
return currentDate
|
|
15
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
import { CID } from '@atproto/lex-data'
|
|
3
|
-
import { Def } from './check.js'
|
|
4
|
-
|
|
5
|
-
const cidSchema = z.unknown().transform((obj, ctx): CID => {
|
|
6
|
-
const cid = CID.asCID(obj)
|
|
7
|
-
|
|
8
|
-
if (cid == null) {
|
|
9
|
-
ctx.addIssue({
|
|
10
|
-
code: z.ZodIssueCode.custom,
|
|
11
|
-
message: 'Not a valid CID',
|
|
12
|
-
})
|
|
13
|
-
return z.NEVER
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return cid
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
const carHeader = z.object({
|
|
20
|
-
version: z.literal(1),
|
|
21
|
-
roots: z.array(cidSchema),
|
|
22
|
-
})
|
|
23
|
-
export type CarHeader = z.infer<typeof carHeader>
|
|
24
|
-
|
|
25
|
-
export const schema = {
|
|
26
|
-
cid: cidSchema,
|
|
27
|
-
carHeader,
|
|
28
|
-
bytes: z.instanceof(Uint8Array<ArrayBufferLike>),
|
|
29
|
-
string: z.string(),
|
|
30
|
-
array: z.array(z.unknown()),
|
|
31
|
-
map: z.record(z.string(), z.unknown()),
|
|
32
|
-
unknown: z.unknown(),
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const def = {
|
|
36
|
-
cid: {
|
|
37
|
-
name: 'cid',
|
|
38
|
-
schema: schema.cid,
|
|
39
|
-
} as Def<CID>,
|
|
40
|
-
carHeader: {
|
|
41
|
-
name: 'CAR header',
|
|
42
|
-
schema: schema.carHeader,
|
|
43
|
-
} as Def<CarHeader>,
|
|
44
|
-
bytes: {
|
|
45
|
-
name: 'bytes',
|
|
46
|
-
schema: schema.bytes,
|
|
47
|
-
} as Def<Uint8Array>,
|
|
48
|
-
string: {
|
|
49
|
-
name: 'string',
|
|
50
|
-
schema: schema.string,
|
|
51
|
-
} as Def<string>,
|
|
52
|
-
map: {
|
|
53
|
-
name: 'map',
|
|
54
|
-
schema: schema.map,
|
|
55
|
-
} as Def<Record<string, unknown>>,
|
|
56
|
-
unknown: {
|
|
57
|
-
name: 'unknown',
|
|
58
|
-
schema: schema.unknown,
|
|
59
|
-
} as Def<unknown>,
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export type ArrayEl<A> = A extends readonly (infer T)[] ? T : never
|
|
63
|
-
|
|
64
|
-
export type NotEmptyArray<T> = [T, ...T[]]
|
package/src/util.ts
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
export const noUndefinedVals = <T>(
|
|
2
|
-
obj: Record<string, T | undefined>,
|
|
3
|
-
): Record<string, T> => {
|
|
4
|
-
for (const k of Object.keys(obj)) {
|
|
5
|
-
if (obj[k] === undefined) {
|
|
6
|
-
delete obj[k]
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
return obj as Record<string, T>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function aggregateErrors(
|
|
13
|
-
errors: unknown[],
|
|
14
|
-
message?: string,
|
|
15
|
-
): Error | AggregateError {
|
|
16
|
-
if (errors.length === 1) {
|
|
17
|
-
return errors[0] instanceof Error
|
|
18
|
-
? errors[0]
|
|
19
|
-
: new Error(message ?? stringifyError(errors[0]), { cause: errors[0] })
|
|
20
|
-
} else {
|
|
21
|
-
return new AggregateError(
|
|
22
|
-
errors,
|
|
23
|
-
message ?? `Multiple errors: ${errors.map(stringifyError).join('\n')}`,
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function stringifyError(reason: unknown): string {
|
|
29
|
-
if (reason instanceof Error) {
|
|
30
|
-
return reason.message
|
|
31
|
-
}
|
|
32
|
-
return String(reason)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Returns a shallow copy of the object without the specified keys. If the input
|
|
37
|
-
* is nullish, it returns the input.
|
|
38
|
-
*/
|
|
39
|
-
export function omit<
|
|
40
|
-
T extends undefined | null | Record<string, unknown>,
|
|
41
|
-
K extends keyof NonNullable<T>,
|
|
42
|
-
>(
|
|
43
|
-
object: T,
|
|
44
|
-
rejectedKeys: readonly K[],
|
|
45
|
-
): T extends undefined ? undefined : T extends null ? null : Omit<T, K>
|
|
46
|
-
export function omit(
|
|
47
|
-
src: undefined | null | Record<string, unknown>,
|
|
48
|
-
rejectedKeys: readonly string[],
|
|
49
|
-
): undefined | null | Record<string, unknown> {
|
|
50
|
-
// Hot path
|
|
51
|
-
|
|
52
|
-
if (!src) return src
|
|
53
|
-
|
|
54
|
-
const dst = {}
|
|
55
|
-
const srcKeys = Object.keys(src)
|
|
56
|
-
for (let i = 0; i < srcKeys.length; i++) {
|
|
57
|
-
const key = srcKeys[i]
|
|
58
|
-
if (!rejectedKeys.includes(key)) {
|
|
59
|
-
dst[key] = src[key]
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return dst
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export const jitter = (maxMs: number) => {
|
|
66
|
-
return Math.round((Math.random() - 0.5) * maxMs * 2)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const wait = (ms: number) => {
|
|
70
|
-
return new Promise((res) => setTimeout(res, ms))
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export type BailableWait = {
|
|
74
|
-
bail: () => void
|
|
75
|
-
wait: () => Promise<void>
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export const bailableWait = (ms: number): BailableWait => {
|
|
79
|
-
let bail
|
|
80
|
-
const waitPromise = new Promise<void>((res) => {
|
|
81
|
-
const timeout = setTimeout(res, ms)
|
|
82
|
-
bail = () => {
|
|
83
|
-
clearTimeout(timeout)
|
|
84
|
-
res()
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
return { bail, wait: () => waitPromise }
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export const flattenUint8Arrays = (arrs: Uint8Array[]): Uint8Array => {
|
|
91
|
-
const length = arrs.reduce((acc, cur) => {
|
|
92
|
-
return acc + cur.length
|
|
93
|
-
}, 0)
|
|
94
|
-
const flattened = new Uint8Array(length)
|
|
95
|
-
let offset = 0
|
|
96
|
-
arrs.forEach((arr) => {
|
|
97
|
-
flattened.set(arr, offset)
|
|
98
|
-
offset += arr.length
|
|
99
|
-
})
|
|
100
|
-
return flattened
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export const streamToBuffer = async (
|
|
104
|
-
stream: AsyncIterable<Uint8Array>,
|
|
105
|
-
): Promise<Uint8Array> => {
|
|
106
|
-
const arrays: Uint8Array[] = []
|
|
107
|
-
for await (const chunk of stream) {
|
|
108
|
-
arrays.push(chunk)
|
|
109
|
-
}
|
|
110
|
-
return flattenUint8Arrays(arrays)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const S32_CHAR = '234567abcdefghijklmnopqrstuvwxyz'
|
|
114
|
-
|
|
115
|
-
export const s32encode = (i: number): string => {
|
|
116
|
-
let s = ''
|
|
117
|
-
while (i) {
|
|
118
|
-
const c = i % 32
|
|
119
|
-
i = Math.floor(i / 32)
|
|
120
|
-
s = S32_CHAR.charAt(c) + s
|
|
121
|
-
}
|
|
122
|
-
return s
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export const s32decode = (s: string): number => {
|
|
126
|
-
let i = 0
|
|
127
|
-
for (const c of s) {
|
|
128
|
-
i = i * 32 + S32_CHAR.indexOf(c)
|
|
129
|
-
}
|
|
130
|
-
return i
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export const asyncFilter = async <T>(
|
|
134
|
-
arr: T[],
|
|
135
|
-
fn: (t: T) => Promise<boolean>,
|
|
136
|
-
) => {
|
|
137
|
-
const results = await Promise.all(arr.map((t) => fn(t)))
|
|
138
|
-
return arr.filter((_, i) => results[i])
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export const isErrnoException = (
|
|
142
|
-
err: unknown,
|
|
143
|
-
): err is NodeJS.ErrnoException => {
|
|
144
|
-
return !!err && err['code']
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export const errHasMsg = (err: unknown, msg: string): boolean => {
|
|
148
|
-
return !!err && typeof err === 'object' && err['message'] === msg
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export const chunkArray = <T>(arr: T[], chunkSize: number): T[][] => {
|
|
152
|
-
return arr.reduce((acc, cur, i) => {
|
|
153
|
-
const chunkI = Math.floor(i / chunkSize)
|
|
154
|
-
if (!acc[chunkI]) {
|
|
155
|
-
acc[chunkI] = []
|
|
156
|
-
}
|
|
157
|
-
acc[chunkI].push(cur)
|
|
158
|
-
return acc
|
|
159
|
-
}, [] as T[][])
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export const range = (num: number): number[] => {
|
|
163
|
-
const nums: number[] = []
|
|
164
|
-
for (let i = 0; i < num; i++) {
|
|
165
|
-
nums.push(i)
|
|
166
|
-
}
|
|
167
|
-
return nums
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export const dedupeStrs = <T extends string>(strs: Iterable<T>): T[] => {
|
|
171
|
-
return [...new Set(strs)]
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export const parseIntWithFallback = <T>(
|
|
175
|
-
value: string | undefined,
|
|
176
|
-
fallback: T,
|
|
177
|
-
): number | T => {
|
|
178
|
-
const parsed = parseInt(value || '', 10)
|
|
179
|
-
return isNaN(parsed) ? fallback : parsed
|
|
180
|
-
}
|
package/tests/check.test.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { ZodError } from 'zod'
|
|
2
|
-
import { check } from '../src/index.js'
|
|
3
|
-
|
|
4
|
-
describe('check', () => {
|
|
5
|
-
describe('is', () => {
|
|
6
|
-
it('checks object against definition', () => {
|
|
7
|
-
const checkable: check.Checkable<boolean> = {
|
|
8
|
-
parse(obj) {
|
|
9
|
-
return Boolean(obj)
|
|
10
|
-
},
|
|
11
|
-
safeParse(obj) {
|
|
12
|
-
return {
|
|
13
|
-
success: true,
|
|
14
|
-
data: Boolean(obj),
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
expect(check.is(true, checkable)).toBe(true)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('handles failed checks', () => {
|
|
23
|
-
const checkable: check.Checkable<boolean> = {
|
|
24
|
-
parse(obj) {
|
|
25
|
-
return Boolean(obj)
|
|
26
|
-
},
|
|
27
|
-
safeParse() {
|
|
28
|
-
return {
|
|
29
|
-
success: false,
|
|
30
|
-
error: new ZodError([]),
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
expect(check.is(true, checkable)).toBe(false)
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe('assure', () => {
|
|
40
|
-
it('returns value on success', () => {
|
|
41
|
-
const checkable: check.Checkable<boolean> = {
|
|
42
|
-
parse(obj) {
|
|
43
|
-
return Boolean(obj)
|
|
44
|
-
},
|
|
45
|
-
safeParse(obj) {
|
|
46
|
-
return {
|
|
47
|
-
success: true,
|
|
48
|
-
data: Boolean(obj),
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
expect(check.assure(checkable, true)).toEqual(true)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('throws on failure', () => {
|
|
57
|
-
const err = new Error('foo')
|
|
58
|
-
const checkable: check.Checkable<boolean> = {
|
|
59
|
-
parse() {
|
|
60
|
-
throw err
|
|
61
|
-
},
|
|
62
|
-
safeParse() {
|
|
63
|
-
throw err
|
|
64
|
-
},
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
expect(() => check.assure(checkable, true)).toThrow(err)
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
describe('isObject', () => {
|
|
72
|
-
const falseTestValues: unknown[] = [null, undefined, 'foo', 123, true]
|
|
73
|
-
|
|
74
|
-
for (const obj of falseTestValues) {
|
|
75
|
-
it(`returns false for ${obj}`, () => {
|
|
76
|
-
expect(check.isObject(obj)).toBe(false)
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
it('returns true for objects', () => {
|
|
81
|
-
expect(check.isObject({})).toBe(true)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('returns true for instances of classes', () => {
|
|
85
|
-
const obj = new (class {})()
|
|
86
|
-
expect(check.isObject(obj)).toBe(true)
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
})
|
package/tests/retry.test.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { retry } from '../src/index.js'
|
|
2
|
-
|
|
3
|
-
describe('retry', () => {
|
|
4
|
-
describe('retry()', () => {
|
|
5
|
-
it('retries until max retries', async () => {
|
|
6
|
-
let fnCalls = 0
|
|
7
|
-
let waitMsCalls = 0
|
|
8
|
-
const fn = async () => {
|
|
9
|
-
fnCalls++
|
|
10
|
-
throw new Error(`Oops ${fnCalls}!`)
|
|
11
|
-
}
|
|
12
|
-
const getWaitMs = (retries) => {
|
|
13
|
-
waitMsCalls++
|
|
14
|
-
expect(retries).toEqual(waitMsCalls - 1)
|
|
15
|
-
return 0
|
|
16
|
-
}
|
|
17
|
-
await expect(retry(fn, { maxRetries: 13, getWaitMs })).rejects.toThrow(
|
|
18
|
-
'Oops 14!',
|
|
19
|
-
)
|
|
20
|
-
expect(fnCalls).toEqual(14)
|
|
21
|
-
expect(waitMsCalls).toEqual(14)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('retries until max wait', async () => {
|
|
25
|
-
let fnCalls = 0
|
|
26
|
-
let waitMsCalls = 0
|
|
27
|
-
const fn = async () => {
|
|
28
|
-
fnCalls++
|
|
29
|
-
throw new Error(`Oops ${fnCalls}!`)
|
|
30
|
-
}
|
|
31
|
-
const getWaitMs = (retries) => {
|
|
32
|
-
waitMsCalls++
|
|
33
|
-
expect(retries).toEqual(waitMsCalls - 1)
|
|
34
|
-
if (retries === 13) {
|
|
35
|
-
return null
|
|
36
|
-
}
|
|
37
|
-
return 0
|
|
38
|
-
}
|
|
39
|
-
await expect(
|
|
40
|
-
retry(fn, { maxRetries: Infinity, getWaitMs }),
|
|
41
|
-
).rejects.toThrow('Oops 14!')
|
|
42
|
-
expect(fnCalls).toEqual(14)
|
|
43
|
-
expect(waitMsCalls).toEqual(14)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('retries until non-retryable error', async () => {
|
|
47
|
-
let fnCalls = 0
|
|
48
|
-
let waitMsCalls = 0
|
|
49
|
-
const fn = async () => {
|
|
50
|
-
fnCalls++
|
|
51
|
-
throw new Error(`Oops ${fnCalls}!`)
|
|
52
|
-
}
|
|
53
|
-
const getWaitMs = (retries) => {
|
|
54
|
-
waitMsCalls++
|
|
55
|
-
expect(retries).toEqual(waitMsCalls - 1)
|
|
56
|
-
return 0
|
|
57
|
-
}
|
|
58
|
-
const retryable = (err: unknown) => err?.['message'] !== 'Oops 14!'
|
|
59
|
-
await expect(
|
|
60
|
-
retry(fn, { maxRetries: Infinity, getWaitMs, retryable }),
|
|
61
|
-
).rejects.toThrow('Oops 14!')
|
|
62
|
-
expect(fnCalls).toEqual(14)
|
|
63
|
-
expect(waitMsCalls).toEqual(14)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('returns latest result after retries', async () => {
|
|
67
|
-
let fnCalls = 0
|
|
68
|
-
const fn = async () => {
|
|
69
|
-
fnCalls++
|
|
70
|
-
if (fnCalls < 14) {
|
|
71
|
-
throw new Error(`Oops ${fnCalls}!`)
|
|
72
|
-
}
|
|
73
|
-
return 'ok'
|
|
74
|
-
}
|
|
75
|
-
const getWaitMs = () => 0
|
|
76
|
-
const result = await retry(fn, { maxRetries: Infinity, getWaitMs })
|
|
77
|
-
expect(result).toBe('ok')
|
|
78
|
-
expect(fnCalls).toBe(14)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('returns result immediately on success', async () => {
|
|
82
|
-
let fnCalls = 0
|
|
83
|
-
const fn = async () => {
|
|
84
|
-
fnCalls++
|
|
85
|
-
return 'ok'
|
|
86
|
-
}
|
|
87
|
-
const getWaitMs = () => 0
|
|
88
|
-
const result = await retry(fn, { maxRetries: Infinity, getWaitMs })
|
|
89
|
-
expect(result).toBe('ok')
|
|
90
|
-
expect(fnCalls).toBe(1)
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
})
|