@atproto/common-web 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/babel.config.js +1 -0
- package/build.js +23 -0
- package/dist/async.d.ts +24 -0
- package/dist/check.d.ts +18 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +4489 -0
- package/dist/index.js.map +7 -0
- package/dist/ipld.d.ts +9 -0
- package/dist/strings.d.ts +2 -0
- package/dist/tid.d.ts +20 -0
- package/dist/times.d.ts +4 -0
- package/dist/types.d.ts +19 -0
- package/dist/util.d.ts +16 -0
- package/jest.config.js +6 -0
- package/package.json +26 -0
- package/src/async.ts +126 -0
- package/src/check.ts +25 -0
- package/src/index.ts +10 -0
- package/src/ipld.ts +77 -0
- package/src/strings.ts +9 -0
- package/src/tid.ts +108 -0
- package/src/times.ts +4 -0
- package/src/types.ts +44 -0
- package/src/util.ts +108 -0
- package/tests/strings.test.ts +30 -0
- package/tests/tid.test.ts +18 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +9 -0
- package/update-pkg.js +14 -0
package/dist/ipld.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid';
|
|
2
|
+
export declare type JsonValue = boolean | number | string | null | undefined | unknown | Array<JsonValue> | {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
export declare type IpldValue = JsonValue | CID | Uint8Array | Array<IpldValue> | {
|
|
6
|
+
[key: string]: IpldValue;
|
|
7
|
+
};
|
|
8
|
+
export declare const jsonToIpld: (val: JsonValue) => IpldValue;
|
|
9
|
+
export declare const ipldToJson: (val: IpldValue) => JsonValue;
|
package/dist/tid.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare class TID {
|
|
2
|
+
str: string;
|
|
3
|
+
constructor(str: string);
|
|
4
|
+
static next(): TID;
|
|
5
|
+
static nextStr(): string;
|
|
6
|
+
static fromTime(timestamp: number, clockid: number): TID;
|
|
7
|
+
static fromStr(str: string): TID;
|
|
8
|
+
static newestFirst(a: TID, b: TID): number;
|
|
9
|
+
static oldestFirst(a: TID, b: TID): number;
|
|
10
|
+
static is(str: string): boolean;
|
|
11
|
+
timestamp(): number;
|
|
12
|
+
clockid(): number;
|
|
13
|
+
formatted(): string;
|
|
14
|
+
toString(): string;
|
|
15
|
+
compareTo(other: TID): number;
|
|
16
|
+
equals(other: TID): boolean;
|
|
17
|
+
newerThan(other: TID): boolean;
|
|
18
|
+
olderThan(other: TID): boolean;
|
|
19
|
+
}
|
|
20
|
+
export default TID;
|
package/dist/times.d.ts
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { Def } from './check';
|
|
4
|
+
export declare const schema: {
|
|
5
|
+
cid: z.ZodEffects<z.ZodEffects<z.ZodAny, any, any>, CID, any>;
|
|
6
|
+
bytes: z.ZodType<Uint8Array, z.ZodTypeDef, Uint8Array>;
|
|
7
|
+
string: z.ZodString;
|
|
8
|
+
array: z.ZodArray<z.ZodUnknown, "many">;
|
|
9
|
+
map: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
10
|
+
unknown: z.ZodUnknown;
|
|
11
|
+
};
|
|
12
|
+
export declare const def: {
|
|
13
|
+
cid: Def<CID>;
|
|
14
|
+
bytes: Def<Uint8Array>;
|
|
15
|
+
string: Def<string>;
|
|
16
|
+
map: Def<Record<string, unknown>>;
|
|
17
|
+
unknown: Def<unknown>;
|
|
18
|
+
};
|
|
19
|
+
export declare type ArrayEl<A> = A extends readonly (infer T)[] ? T : never;
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export declare const noUndefinedVals: <T>(obj: Record<string, T>) => Record<string, T>;
|
|
3
|
+
export declare const wait: (ms: number) => Promise<unknown>;
|
|
4
|
+
export declare const bailableWait: (ms: number) => {
|
|
5
|
+
bail: () => void;
|
|
6
|
+
wait: () => Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
export declare const flattenUint8Arrays: (arrs: Uint8Array[]) => Uint8Array;
|
|
9
|
+
export declare const streamToArray: (stream: AsyncIterable<Uint8Array>) => Promise<Uint8Array>;
|
|
10
|
+
export declare const s32encode: (i: number) => string;
|
|
11
|
+
export declare const s32decode: (s: string) => number;
|
|
12
|
+
export declare const asyncFilter: <T>(arr: T[], fn: (t: T) => Promise<boolean>) => Promise<T[]>;
|
|
13
|
+
export declare const isErrnoException: (err: unknown) => err is NodeJS.ErrnoException;
|
|
14
|
+
export declare const errHasMsg: (err: unknown, msg: string) => boolean;
|
|
15
|
+
export declare const chunkArray: <T>(arr: T[], chunkSize: number) => T[][];
|
|
16
|
+
export declare const range: (num: number) => number[];
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atproto/common-web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest",
|
|
8
|
+
"prettier": "prettier --check src/",
|
|
9
|
+
"prettier:fix": "prettier --write src/",
|
|
10
|
+
"lint": "eslint . --ext .ts,.tsx",
|
|
11
|
+
"lint:fix": "yarn lint --fix",
|
|
12
|
+
"verify": "run-p prettier lint",
|
|
13
|
+
"verify:fix": "yarn prettier:fix && yarn lint:fix",
|
|
14
|
+
"build": "node ./build.js",
|
|
15
|
+
"postbuild": "tsc --build tsconfig.build.json",
|
|
16
|
+
"update-main-to-dist": "node ./update-pkg.js --update-main-to-dist",
|
|
17
|
+
"update-main-to-src": "node ./update-pkg.js --update-main-to-src",
|
|
18
|
+
"prepublish": "npm run update-main-to-dist",
|
|
19
|
+
"postpublish": "npm run update-main-to-src"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"multiformats": "^9.6.4",
|
|
23
|
+
"uint8arrays": "3.0.0",
|
|
24
|
+
"zod": "^3.14.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/async.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { bailableWait } from './util'
|
|
2
|
+
|
|
3
|
+
// reads values from a generator into a list
|
|
4
|
+
// breaks when isDone signals `true` AND `waitFor` completes OR when a max length is reached
|
|
5
|
+
// NOTE: does not signal generator to close. it *will* continue to produce values
|
|
6
|
+
export const readFromGenerator = async <T>(
|
|
7
|
+
gen: AsyncGenerator<T>,
|
|
8
|
+
isDone: (last?: T) => Promise<boolean> | boolean,
|
|
9
|
+
waitFor: Promise<unknown> = Promise.resolve(),
|
|
10
|
+
maxLength = Number.MAX_SAFE_INTEGER,
|
|
11
|
+
): Promise<T[]> => {
|
|
12
|
+
const evts: T[] = []
|
|
13
|
+
let bail: undefined | (() => void)
|
|
14
|
+
let hasBroke = false
|
|
15
|
+
const awaitDone = async () => {
|
|
16
|
+
if (await isDone(evts.at(-1))) {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
const bailable = bailableWait(20)
|
|
20
|
+
await bailable.wait()
|
|
21
|
+
bail = bailable.bail
|
|
22
|
+
if (hasBroke) return false
|
|
23
|
+
return await awaitDone()
|
|
24
|
+
}
|
|
25
|
+
const breakOn: Promise<void> = new Promise((resolve) => {
|
|
26
|
+
waitFor.then(() => {
|
|
27
|
+
awaitDone().then(() => resolve())
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
while (evts.length < maxLength) {
|
|
33
|
+
const maybeEvt = await Promise.race([gen.next(), breakOn])
|
|
34
|
+
if (!maybeEvt) break
|
|
35
|
+
const evt = maybeEvt as IteratorResult<T>
|
|
36
|
+
if (evt.done) break
|
|
37
|
+
evts.push(evt.value)
|
|
38
|
+
}
|
|
39
|
+
} finally {
|
|
40
|
+
hasBroke = true
|
|
41
|
+
bail && bail()
|
|
42
|
+
}
|
|
43
|
+
return evts
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type Deferrable = {
|
|
47
|
+
resolve: () => void
|
|
48
|
+
complete: Promise<void>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const createDeferrable = (): Deferrable => {
|
|
52
|
+
let resolve
|
|
53
|
+
const promise: Promise<void> = new Promise((res) => {
|
|
54
|
+
resolve = () => res()
|
|
55
|
+
})
|
|
56
|
+
return { resolve, complete: promise }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const createDeferrables = (count: number): Deferrable[] => {
|
|
60
|
+
const list: Deferrable[] = []
|
|
61
|
+
for (let i = 0; i < count; i++) {
|
|
62
|
+
list.push(createDeferrable())
|
|
63
|
+
}
|
|
64
|
+
return list
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const allComplete = async (deferrables: Deferrable[]): Promise<void> => {
|
|
68
|
+
await Promise.all(deferrables.map((d) => d.complete))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class AsyncBuffer<T> {
|
|
72
|
+
private buffer: T[] = []
|
|
73
|
+
private promise: Promise<void>
|
|
74
|
+
private resolve: () => void
|
|
75
|
+
|
|
76
|
+
constructor(public maxSize?: number) {
|
|
77
|
+
// Initializing to satisfy types/build, immediately reset by resetPromise()
|
|
78
|
+
this.promise = Promise.resolve()
|
|
79
|
+
this.resolve = () => null
|
|
80
|
+
this.resetPromise()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get curr(): T[] {
|
|
84
|
+
return this.buffer
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get size(): number {
|
|
88
|
+
return this.buffer.length
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
resetPromise() {
|
|
92
|
+
this.promise = new Promise<void>((r) => (this.resolve = r))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
push(item: T) {
|
|
96
|
+
this.buffer.push(item)
|
|
97
|
+
this.resolve()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pushMany(items: T[]) {
|
|
101
|
+
items.forEach((i) => this.buffer.push(i))
|
|
102
|
+
this.resolve()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async *events(): AsyncGenerator<T> {
|
|
106
|
+
while (true) {
|
|
107
|
+
await this.promise
|
|
108
|
+
if (this.maxSize && this.size > this.maxSize) {
|
|
109
|
+
throw new AsyncBufferFullError(this.maxSize)
|
|
110
|
+
}
|
|
111
|
+
const [first, ...rest] = this.buffer
|
|
112
|
+
if (first) {
|
|
113
|
+
this.buffer = rest
|
|
114
|
+
yield first
|
|
115
|
+
} else {
|
|
116
|
+
this.resetPromise()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class AsyncBufferFullError extends Error {
|
|
123
|
+
constructor(maxSize: number) {
|
|
124
|
+
super(`ReachedMaxBufferSize: ${maxSize}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/check.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ZodError } from 'zod'
|
|
2
|
+
|
|
3
|
+
export interface Checkable<T> {
|
|
4
|
+
parse: (obj: unknown) => T
|
|
5
|
+
safeParse: (
|
|
6
|
+
obj: unknown,
|
|
7
|
+
) => { success: true; data: T } | { success: false; error: ZodError }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface Def<T> {
|
|
11
|
+
name: string
|
|
12
|
+
schema: Checkable<T>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const is = <T>(obj: unknown, def: Checkable<T>): obj is T => {
|
|
16
|
+
return def.safeParse(obj).success
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const assure = <T>(def: Checkable<T>, obj: unknown): T => {
|
|
20
|
+
return def.parse(obj)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const isObject = (obj: unknown): obj is Record<string, unknown> => {
|
|
24
|
+
return typeof obj === 'object' && obj !== null
|
|
25
|
+
}
|
package/src/index.ts
ADDED
package/src/ipld.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import * as ui8 from 'uint8arrays'
|
|
3
|
+
|
|
4
|
+
export type JsonValue =
|
|
5
|
+
| boolean
|
|
6
|
+
| number
|
|
7
|
+
| string
|
|
8
|
+
| null
|
|
9
|
+
| undefined
|
|
10
|
+
| unknown
|
|
11
|
+
| Array<JsonValue>
|
|
12
|
+
| { [key: string]: JsonValue }
|
|
13
|
+
|
|
14
|
+
export type IpldValue =
|
|
15
|
+
| JsonValue
|
|
16
|
+
| CID
|
|
17
|
+
| Uint8Array
|
|
18
|
+
| Array<IpldValue>
|
|
19
|
+
| { [key: string]: IpldValue }
|
|
20
|
+
|
|
21
|
+
// @NOTE avoiding use of check.is() here only because it makes
|
|
22
|
+
// these implementations slow, and they often live in hot paths.
|
|
23
|
+
|
|
24
|
+
export const jsonToIpld = (val: JsonValue): IpldValue => {
|
|
25
|
+
// walk arrays
|
|
26
|
+
if (Array.isArray(val)) {
|
|
27
|
+
return val.map((item) => jsonToIpld(item))
|
|
28
|
+
}
|
|
29
|
+
// objects
|
|
30
|
+
if (val && typeof val === 'object') {
|
|
31
|
+
// check for dag json values
|
|
32
|
+
if (typeof val['$link'] === 'string' && Object.keys(val).length === 1) {
|
|
33
|
+
return CID.parse(val['$link'])
|
|
34
|
+
}
|
|
35
|
+
if (typeof val['$bytes'] === 'string' && Object.keys(val).length === 1) {
|
|
36
|
+
return ui8.fromString(val['$bytes'], 'base64')
|
|
37
|
+
}
|
|
38
|
+
// walk plain objects
|
|
39
|
+
const toReturn = {}
|
|
40
|
+
for (const key of Object.keys(val)) {
|
|
41
|
+
toReturn[key] = jsonToIpld(val[key])
|
|
42
|
+
}
|
|
43
|
+
return toReturn
|
|
44
|
+
}
|
|
45
|
+
// pass through
|
|
46
|
+
return val
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const ipldToJson = (val: IpldValue): JsonValue => {
|
|
50
|
+
// walk arrays
|
|
51
|
+
if (Array.isArray(val)) {
|
|
52
|
+
return val.map((item) => ipldToJson(item))
|
|
53
|
+
}
|
|
54
|
+
// objects
|
|
55
|
+
if (val && typeof val === 'object') {
|
|
56
|
+
// convert bytes
|
|
57
|
+
if (val instanceof Uint8Array) {
|
|
58
|
+
return {
|
|
59
|
+
$bytes: ui8.toString(val, 'base64'),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// convert cids
|
|
63
|
+
if (CID.asCID(val)) {
|
|
64
|
+
return {
|
|
65
|
+
$link: (val as CID).toString(),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// walk plain objects
|
|
69
|
+
const toReturn = {}
|
|
70
|
+
for (const key of Object.keys(val)) {
|
|
71
|
+
toReturn[key] = ipldToJson(val[key])
|
|
72
|
+
}
|
|
73
|
+
return toReturn
|
|
74
|
+
}
|
|
75
|
+
// pass through
|
|
76
|
+
return val as JsonValue
|
|
77
|
+
}
|
package/src/strings.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// counts the number of bytes in a utf8 string
|
|
2
|
+
export const utf8Len = (str: string): number => {
|
|
3
|
+
return new TextEncoder().encode(str).byteLength
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// counts the number of graphemes (user-displayed characters) in a string
|
|
7
|
+
export const graphemeLen = (str: string): number => {
|
|
8
|
+
return [...new Intl.Segmenter().segment(str)].length
|
|
9
|
+
}
|
package/src/tid.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { s32encode, s32decode } from './util'
|
|
2
|
+
let lastTimestamp = 0
|
|
3
|
+
let timestampCount = 0
|
|
4
|
+
let clockid: number | null = null
|
|
5
|
+
|
|
6
|
+
export class TID {
|
|
7
|
+
str: string
|
|
8
|
+
|
|
9
|
+
constructor(str: string) {
|
|
10
|
+
const noDashes = str.replace(/-/g, '')
|
|
11
|
+
if (noDashes.length !== 13) {
|
|
12
|
+
throw new Error(`Poorly formatted TID: ${noDashes.length} length`)
|
|
13
|
+
}
|
|
14
|
+
this.str = noDashes
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static next(): TID {
|
|
18
|
+
// javascript does not have microsecond precision
|
|
19
|
+
// instead, we append a counter to the timestamp to indicate if multiple timestamps were created within the same millisecond
|
|
20
|
+
// take max of current time & last timestamp to prevent tids moving backwards if system clock drifts backwards
|
|
21
|
+
const time = Math.max(Date.now(), lastTimestamp)
|
|
22
|
+
if (time === lastTimestamp) {
|
|
23
|
+
timestampCount++
|
|
24
|
+
}
|
|
25
|
+
lastTimestamp = time
|
|
26
|
+
const timestamp = time * 1000 + timestampCount
|
|
27
|
+
// the bottom 32 clock ids can be randomized & are not guaranteed to be collision resistant
|
|
28
|
+
// we use the same clockid for all tids coming from this machine
|
|
29
|
+
if (clockid === null) {
|
|
30
|
+
clockid = Math.floor(Math.random() * 32)
|
|
31
|
+
}
|
|
32
|
+
return TID.fromTime(timestamp, clockid)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static nextStr(): string {
|
|
36
|
+
return TID.next().toString()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static fromTime(timestamp: number, clockid: number): TID {
|
|
40
|
+
// base32 encode with encoding variant sort (s32)
|
|
41
|
+
const str = `${s32encode(timestamp)}${s32encode(clockid).padStart(2, '2')}`
|
|
42
|
+
return new TID(str)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static fromStr(str: string): TID {
|
|
46
|
+
return new TID(str)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static newestFirst(a: TID, b: TID): number {
|
|
50
|
+
return a.compareTo(b) * -1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static oldestFirst(a: TID, b: TID): number {
|
|
54
|
+
return a.compareTo(b)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static is(str: string): boolean {
|
|
58
|
+
try {
|
|
59
|
+
TID.fromStr(str)
|
|
60
|
+
return true
|
|
61
|
+
} catch (err) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
timestamp(): number {
|
|
67
|
+
const substr = this.str.slice(0, 11)
|
|
68
|
+
return s32decode(substr)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
clockid(): number {
|
|
72
|
+
const substr = this.str.slice(11, 13)
|
|
73
|
+
return s32decode(substr)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
formatted(): string {
|
|
77
|
+
const str = this.toString()
|
|
78
|
+
return `${str.slice(0, 4)}-${str.slice(4, 7)}-${str.slice(
|
|
79
|
+
7,
|
|
80
|
+
11,
|
|
81
|
+
)}-${str.slice(11, 13)}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
toString(): string {
|
|
85
|
+
return this.str
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// newer > older
|
|
89
|
+
compareTo(other: TID): number {
|
|
90
|
+
if (this.str > other.str) return 1
|
|
91
|
+
if (this.str < other.str) return -1
|
|
92
|
+
return 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
equals(other: TID): boolean {
|
|
96
|
+
return this.compareTo(other) === 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
newerThan(other: TID): boolean {
|
|
100
|
+
return this.compareTo(other) > 0
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
olderThan(other: TID): boolean {
|
|
104
|
+
return this.compareTo(other) < 0
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default TID
|
package/src/times.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { Def } from './check'
|
|
4
|
+
|
|
5
|
+
const cidSchema = z
|
|
6
|
+
.any()
|
|
7
|
+
.refine((obj: unknown) => CID.asCID(obj) !== null, {
|
|
8
|
+
message: 'Not a CID',
|
|
9
|
+
})
|
|
10
|
+
.transform((obj: unknown) => CID.asCID(obj) as CID)
|
|
11
|
+
|
|
12
|
+
export const schema = {
|
|
13
|
+
cid: cidSchema,
|
|
14
|
+
bytes: z.instanceof(Uint8Array),
|
|
15
|
+
string: z.string(),
|
|
16
|
+
array: z.array(z.unknown()),
|
|
17
|
+
map: z.record(z.string(), z.unknown()),
|
|
18
|
+
unknown: z.unknown(),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const def = {
|
|
22
|
+
cid: {
|
|
23
|
+
name: 'cid',
|
|
24
|
+
schema: schema.cid,
|
|
25
|
+
} as Def<CID>,
|
|
26
|
+
bytes: {
|
|
27
|
+
name: 'bytes',
|
|
28
|
+
schema: schema.bytes,
|
|
29
|
+
} as Def<Uint8Array>,
|
|
30
|
+
string: {
|
|
31
|
+
name: 'string',
|
|
32
|
+
schema: schema.string,
|
|
33
|
+
} as Def<string>,
|
|
34
|
+
map: {
|
|
35
|
+
name: 'map',
|
|
36
|
+
schema: schema.map,
|
|
37
|
+
} as Def<Record<string, unknown>>,
|
|
38
|
+
unknown: {
|
|
39
|
+
name: 'unknown',
|
|
40
|
+
schema: schema.unknown,
|
|
41
|
+
} as Def<unknown>,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ArrayEl<A> = A extends readonly (infer T)[] ? T : never
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export const noUndefinedVals = <T>(
|
|
2
|
+
obj: Record<string, T>,
|
|
3
|
+
): Record<string, T> => {
|
|
4
|
+
Object.keys(obj).forEach((k) => {
|
|
5
|
+
if (obj[k] === undefined) {
|
|
6
|
+
delete obj[k]
|
|
7
|
+
}
|
|
8
|
+
})
|
|
9
|
+
return obj
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const wait = (ms: number) => {
|
|
13
|
+
return new Promise((res) => setTimeout(res, ms))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const bailableWait = (
|
|
17
|
+
ms: number,
|
|
18
|
+
): { bail: () => void; wait: () => Promise<void> } => {
|
|
19
|
+
let bail
|
|
20
|
+
const waitPromise = new Promise<void>((res) => {
|
|
21
|
+
const timeout = setTimeout(res, ms)
|
|
22
|
+
bail = () => {
|
|
23
|
+
clearTimeout(timeout)
|
|
24
|
+
res()
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
return { bail, wait: () => waitPromise }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const flattenUint8Arrays = (arrs: Uint8Array[]): Uint8Array => {
|
|
31
|
+
const length = arrs.reduce((acc, cur) => {
|
|
32
|
+
return acc + cur.length
|
|
33
|
+
}, 0)
|
|
34
|
+
const flattened = new Uint8Array(length)
|
|
35
|
+
let offset = 0
|
|
36
|
+
arrs.forEach((arr) => {
|
|
37
|
+
flattened.set(arr, offset)
|
|
38
|
+
offset += arr.length
|
|
39
|
+
})
|
|
40
|
+
return flattened
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const streamToArray = async (
|
|
44
|
+
stream: AsyncIterable<Uint8Array>,
|
|
45
|
+
): Promise<Uint8Array> => {
|
|
46
|
+
const arrays: Uint8Array[] = []
|
|
47
|
+
for await (const chunk of stream) {
|
|
48
|
+
arrays.push(chunk)
|
|
49
|
+
}
|
|
50
|
+
return flattenUint8Arrays(arrays)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const S32_CHAR = '234567abcdefghijklmnopqrstuvwxyz'
|
|
54
|
+
|
|
55
|
+
export const s32encode = (i: number): string => {
|
|
56
|
+
let s = ''
|
|
57
|
+
while (i) {
|
|
58
|
+
const c = i % 32
|
|
59
|
+
i = Math.floor(i / 32)
|
|
60
|
+
s = S32_CHAR.charAt(c) + s
|
|
61
|
+
}
|
|
62
|
+
return s
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const s32decode = (s: string): number => {
|
|
66
|
+
let i = 0
|
|
67
|
+
for (const c of s) {
|
|
68
|
+
i = i * 32 + S32_CHAR.indexOf(c)
|
|
69
|
+
}
|
|
70
|
+
return i
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const asyncFilter = async <T>(
|
|
74
|
+
arr: T[],
|
|
75
|
+
fn: (t: T) => Promise<boolean>,
|
|
76
|
+
) => {
|
|
77
|
+
const results = await Promise.all(arr.map((t) => fn(t)))
|
|
78
|
+
return arr.filter((_, i) => results[i])
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const isErrnoException = (
|
|
82
|
+
err: unknown,
|
|
83
|
+
): err is NodeJS.ErrnoException => {
|
|
84
|
+
return !!err && err['code']
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const errHasMsg = (err: unknown, msg: string): boolean => {
|
|
88
|
+
return !!err && typeof err === 'object' && err['message'] === msg
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const chunkArray = <T>(arr: T[], chunkSize: number): T[][] => {
|
|
92
|
+
return arr.reduce((acc, cur, i) => {
|
|
93
|
+
const chunkI = Math.floor(i / chunkSize)
|
|
94
|
+
if (!acc[chunkI]) {
|
|
95
|
+
acc[chunkI] = []
|
|
96
|
+
}
|
|
97
|
+
acc[chunkI].push(cur)
|
|
98
|
+
return acc
|
|
99
|
+
}, [] as T[][])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const range = (num: number): number[] => {
|
|
103
|
+
const nums: number[] = []
|
|
104
|
+
for (let i = 0; i < num; i++) {
|
|
105
|
+
nums.push(i)
|
|
106
|
+
}
|
|
107
|
+
return nums
|
|
108
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { graphemeLen, utf8Len } from '../src'
|
|
2
|
+
|
|
3
|
+
describe('string', () => {
|
|
4
|
+
it('calculates utf8 string length', () => {
|
|
5
|
+
expect(utf8Len('a')).toBe(1)
|
|
6
|
+
expect(utf8Len('~')).toBe(1)
|
|
7
|
+
expect(utf8Len('ö')).toBe(2)
|
|
8
|
+
expect(utf8Len('ñ')).toBe(2)
|
|
9
|
+
expect(utf8Len('©')).toBe(2)
|
|
10
|
+
expect(utf8Len('⽘')).toBe(3)
|
|
11
|
+
expect(utf8Len('☎')).toBe(3)
|
|
12
|
+
expect(utf8Len('𓋓')).toBe(4)
|
|
13
|
+
expect(utf8Len('😀')).toBe(4)
|
|
14
|
+
expect(utf8Len('👨👩👧👧')).toBe(25)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('caluclates grapheme length', () => {
|
|
18
|
+
expect(graphemeLen('a')).toBe(1)
|
|
19
|
+
expect(graphemeLen('~')).toBe(1)
|
|
20
|
+
expect(graphemeLen('ö')).toBe(1)
|
|
21
|
+
expect(graphemeLen('ñ')).toBe(1)
|
|
22
|
+
expect(graphemeLen('©')).toBe(1)
|
|
23
|
+
expect(graphemeLen('⽘')).toBe(1)
|
|
24
|
+
expect(graphemeLen('☎')).toBe(1)
|
|
25
|
+
expect(graphemeLen('𓋓')).toBe(1)
|
|
26
|
+
expect(graphemeLen('😀')).toBe(1)
|
|
27
|
+
expect(graphemeLen('👨👩👧👧')).toBe(1)
|
|
28
|
+
expect(graphemeLen('a~öñ©⽘☎𓋓😀👨👩👧👧')).toBe(10)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import TID from '../src/tid'
|
|
2
|
+
|
|
3
|
+
describe('TIDs', () => {
|
|
4
|
+
it('creates a new TID', () => {
|
|
5
|
+
const tid = TID.next()
|
|
6
|
+
const str = tid.toString()
|
|
7
|
+
expect(typeof str).toEqual('string')
|
|
8
|
+
expect(str.length).toEqual(13)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('parses a TID', () => {
|
|
12
|
+
const tid = TID.next()
|
|
13
|
+
const str = tid.toString()
|
|
14
|
+
const parsed = TID.fromStr(str)
|
|
15
|
+
expect(parsed.timestamp()).toEqual(tid.timestamp())
|
|
16
|
+
expect(parsed.clockid()).toEqual(tid.clockid())
|
|
17
|
+
})
|
|
18
|
+
})
|