@atproto/common 0.1.0 → 0.2.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/src/async.ts DELETED
@@ -1,100 +0,0 @@
1
- import { bailableWait } from './util'
2
-
3
- // reads values from a generator into a list
4
- // NOTE: does not signal generator to close. it *will* continue to produce values
5
- export const readFromGenerator = async <T>(
6
- gen: AsyncGenerator<T>,
7
- maxLength = Number.MAX_SAFE_INTEGER,
8
- timeout = 2000,
9
- ): Promise<T[]> => {
10
- const evts: T[] = []
11
- while (evts.length < maxLength) {
12
- const { bail, wait } = bailableWait(timeout)
13
- const maybeEvt = await Promise.race([gen.next(), wait()])
14
- bail()
15
- if (!maybeEvt) break
16
- const evt = maybeEvt as IteratorResult<T>
17
- if (evt.done) break
18
- evts.push(evt.value)
19
- }
20
- return evts
21
- }
22
-
23
- export type Deferrable = {
24
- resolve: () => void
25
- complete: Promise<void>
26
- }
27
-
28
- export const createDeferrable = (): Deferrable => {
29
- let resolve
30
- const promise: Promise<void> = new Promise((res) => {
31
- resolve = () => res()
32
- })
33
- return { resolve, complete: promise }
34
- }
35
-
36
- export const createDeferrables = (count: number): Deferrable[] => {
37
- const list: Deferrable[] = []
38
- for (let i = 0; i < count; i++) {
39
- list.push(createDeferrable())
40
- }
41
- return list
42
- }
43
-
44
- export const allComplete = async (deferrables: Deferrable[]): Promise<void> => {
45
- await Promise.all(deferrables.map((d) => d.complete))
46
- }
47
-
48
- export class AsyncBuffer<T> {
49
- private buffer: T[] = []
50
- private promise: Promise<void>
51
- private resolve: () => void
52
-
53
- constructor(public maxSize?: number) {
54
- this.resetPromise()
55
- }
56
-
57
- get curr(): T[] {
58
- return this.buffer
59
- }
60
-
61
- get size(): number {
62
- return this.buffer.length
63
- }
64
-
65
- resetPromise() {
66
- this.promise = new Promise<void>((r) => (this.resolve = r))
67
- }
68
-
69
- push(item: T) {
70
- this.buffer.push(item)
71
- this.resolve()
72
- }
73
-
74
- pushMany(items: T[]) {
75
- items.forEach((i) => this.buffer.push(i))
76
- this.resolve()
77
- }
78
-
79
- async *events(): AsyncGenerator<T> {
80
- while (true) {
81
- await this.promise
82
- if (this.maxSize && this.size > this.maxSize) {
83
- throw new AsyncBufferFullError(this.maxSize)
84
- }
85
- const [first, ...rest] = this.buffer
86
- if (first) {
87
- this.buffer = rest
88
- yield first
89
- } else {
90
- this.resetPromise()
91
- }
92
- }
93
- }
94
- }
95
-
96
- export class AsyncBufferFullError extends Error {
97
- constructor(maxSize: number) {
98
- super(`ReachedMaxBufferSize: ${maxSize}`)
99
- }
100
- }
package/src/check.ts DELETED
@@ -1,25 +0,0 @@
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/tid.ts DELETED
@@ -1,108 +0,0 @@
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 DELETED
@@ -1,4 +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
package/src/types.ts DELETED
@@ -1,43 +0,0 @@
1
- import * as mf from 'multiformats/cid'
2
- import { z } from 'zod'
3
- import { Def } from './check'
4
-
5
- const cidSchema = z
6
- .any()
7
- .refine((obj: unknown) => mf.CID.asCID(obj) !== null, {
8
- message: 'Not a CID',
9
- })
10
- .transform((obj: unknown) => mf.CID.asCID(obj) as mf.CID)
11
-
12
- export const schema = {
13
- cid: cidSchema,
14
- bytes: z.instanceof(Uint8Array),
15
- string: z.string(),
16
- record: z.record(z.string(), z.unknown()),
17
- unknown: z.unknown(),
18
- }
19
-
20
- export const def = {
21
- cid: {
22
- name: 'cid',
23
- schema: schema.cid,
24
- } as Def<mf.CID>,
25
- bytes: {
26
- name: 'bytes',
27
- schema: schema.bytes,
28
- } as Def<Uint8Array>,
29
- string: {
30
- name: 'string',
31
- schema: schema.string,
32
- } as Def<string>,
33
- record: {
34
- name: 'record',
35
- schema: schema.record,
36
- } as Def<Record<string, unknown>>,
37
- unknown: {
38
- name: 'unknown',
39
- schema: schema.unknown,
40
- } as Def<unknown>,
41
- }
42
-
43
- export type ArrayEl<A> = A extends readonly (infer T)[] ? T : never
package/src/util.ts DELETED
@@ -1,108 +0,0 @@
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
- }
@@ -1,22 +0,0 @@
1
- import { readFromGenerator, wait } from '../src'
2
-
3
- describe('async', () => {
4
- describe('readFromGenerator', () => {
5
- async function* waitToYield(time: number) {
6
- for (let i = 0; i < 5; i++) {
7
- await wait(time)
8
- yield true
9
- }
10
- }
11
-
12
- it('reads from generator with timeout', async () => {
13
- const read = await readFromGenerator(waitToYield(100), undefined, 105)
14
- expect(read).toEqual([true, true, true, true, true])
15
- })
16
-
17
- it('stops reading at timeout', async () => {
18
- const read = await readFromGenerator(waitToYield(100), undefined, 95)
19
- expect(read).toEqual([])
20
- })
21
- })
22
- })
package/tests/tid.test.ts DELETED
@@ -1,18 +0,0 @@
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
- })
File without changes