@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/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;
@@ -0,0 +1,2 @@
1
+ export declare const utf8Len: (str: string) => number;
2
+ export declare const graphemeLen: (str: string) => number;
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;
@@ -0,0 +1,4 @@
1
+ export declare const SECOND = 1000;
2
+ export declare const MINUTE: number;
3
+ export declare const HOUR: number;
4
+ export declare const DAY: number;
@@ -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
@@ -0,0 +1,6 @@
1
+ const base = require('../../jest.config.base.js')
2
+
3
+ module.exports = {
4
+ ...base,
5
+ displayName: 'Common Web',
6
+ }
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
@@ -0,0 +1,10 @@
1
+ export * as check from './check'
2
+ export * as util from './util'
3
+
4
+ export * from './async'
5
+ export * from './util'
6
+ export * from './tid'
7
+ export * from './ipld'
8
+ export * from './types'
9
+ export * from './times'
10
+ export * from './strings'
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
@@ -0,0 +1,4 @@
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 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
+ })
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["**/*.spec.ts", "**/*.test.ts"]
4
+ }