@atproto/common-web 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/dist/ipld.d.ts CHANGED
@@ -7,3 +7,4 @@ export declare type IpldValue = JsonValue | CID | Uint8Array | Array<IpldValue>
7
7
  };
8
8
  export declare const jsonToIpld: (val: JsonValue) => IpldValue;
9
9
  export declare const ipldToJson: (val: IpldValue) => JsonValue;
10
+ export declare const ipldEquals: (a: IpldValue, b: IpldValue) => boolean;
package/dist/strings.d.ts CHANGED
@@ -1,2 +1,16 @@
1
1
  export declare const utf8Len: (str: string) => number;
2
2
  export declare const graphemeLen: (str: string) => number;
3
+ export declare const utf8ToB64Url: (utf8: string) => string;
4
+ export declare const b64UrlToUtf8: (b64: string) => string;
5
+ export declare const parseLanguage: (langTag: string) => LanguageTag | null;
6
+ export declare const validateLanguage: (langTag: string) => boolean;
7
+ export declare type LanguageTag = {
8
+ grandfathered?: string;
9
+ language?: string;
10
+ extlang?: string;
11
+ script?: string;
12
+ region?: string;
13
+ variant?: string;
14
+ extension?: string;
15
+ privateUse?: string;
16
+ };
package/dist/tid.d.ts CHANGED
@@ -5,8 +5,8 @@ export declare class TID {
5
5
  static nextStr(): string;
6
6
  static fromTime(timestamp: number, clockid: number): TID;
7
7
  static fromStr(str: string): TID;
8
- static newestFirst(a: TID, b: TID): number;
9
8
  static oldestFirst(a: TID, b: TID): number;
9
+ static newestFirst(a: TID, b: TID): number;
10
10
  static is(str: string): boolean;
11
11
  timestamp(): number;
12
12
  clockid(): number;
package/dist/types.d.ts CHANGED
@@ -17,3 +17,4 @@ export declare const def: {
17
17
  unknown: Def<unknown>;
18
18
  };
19
19
  export declare type ArrayEl<A> = A extends readonly (infer T)[] ? T : never;
20
+ export declare type NotEmptyArray<T> = [T, ...T[]];
package/dist/util.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- /// <reference types="node" />
2
1
  export declare const noUndefinedVals: <T>(obj: Record<string, T>) => Record<string, T>;
2
+ export declare const jitter: (maxMs: number) => number;
3
3
  export declare const wait: (ms: number) => Promise<unknown>;
4
4
  export declare const bailableWait: (ms: number) => {
5
5
  bail: () => void;
6
6
  wait: () => Promise<void>;
7
7
  };
8
8
  export declare const flattenUint8Arrays: (arrs: Uint8Array[]) => Uint8Array;
9
- export declare const streamToArray: (stream: AsyncIterable<Uint8Array>) => Promise<Uint8Array>;
9
+ export declare const streamToBuffer: (stream: AsyncIterable<Uint8Array>) => Promise<Uint8Array>;
10
10
  export declare const s32encode: (i: number) => string;
11
11
  export declare const s32decode: (s: string) => number;
12
12
  export declare const asyncFilter: <T>(arr: T[], fn: (t: T) => Promise<boolean>) => Promise<T[]>;
@@ -14,3 +14,5 @@ export declare const isErrnoException: (err: unknown) => err is NodeJS.ErrnoExce
14
14
  export declare const errHasMsg: (err: unknown, msg: string) => boolean;
15
15
  export declare const chunkArray: <T>(arr: T[], chunkSize: number) => T[][];
16
16
  export declare const range: (num: number) => number[];
17
+ export declare const dedupeStrs: (strs: string[]) => string[];
18
+ export declare const parseIntWithFallback: <T>(value: string | undefined, fallback: T) => number | T;
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "@atproto/common-web",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "main": "dist/index.js",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/bluesky-social/atproto.git",
9
+ "directory": "packages/common-web"
10
+ },
6
11
  "scripts": {
7
12
  "test": "jest",
8
- "prettier": "prettier --check src/",
9
- "prettier:fix": "prettier --write src/",
13
+ "prettier": "prettier --check src/ tests/",
14
+ "prettier:fix": "prettier --write src/ tests/",
10
15
  "lint": "eslint . --ext .ts,.tsx",
11
16
  "lint:fix": "yarn lint --fix",
12
17
  "verify": "run-p prettier lint",
@@ -19,8 +24,9 @@
19
24
  "postpublish": "npm run update-main-to-src"
20
25
  },
21
26
  "dependencies": {
27
+ "graphemer": "^1.4.0",
22
28
  "multiformats": "^9.6.4",
23
29
  "uint8arrays": "3.0.0",
24
- "zod": "^3.14.2"
30
+ "zod": "^3.21.4"
25
31
  }
26
32
  }
package/src/ipld.ts CHANGED
@@ -75,3 +75,32 @@ export const ipldToJson = (val: IpldValue): JsonValue => {
75
75
  // pass through
76
76
  return val as JsonValue
77
77
  }
78
+
79
+ export const ipldEquals = (a: IpldValue, b: IpldValue): boolean => {
80
+ // walk arrays
81
+ if (Array.isArray(a) && Array.isArray(b)) {
82
+ if (a.length !== b.length) return false
83
+ for (let i = 0; i < a.length; i++) {
84
+ if (!ipldEquals(a[i], b[i])) return false
85
+ }
86
+ return true
87
+ }
88
+ // objects
89
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
90
+ // check bytes
91
+ if (a instanceof Uint8Array && b instanceof Uint8Array) {
92
+ return ui8.equals(a, b)
93
+ }
94
+ // check cids
95
+ if (CID.asCID(a) && CID.asCID(b)) {
96
+ return CID.asCID(a)?.equals(CID.asCID(b))
97
+ }
98
+ // walk plain objects
99
+ if (Object.keys(a).length !== Object.keys(b).length) return false
100
+ for (const key of Object.keys(a)) {
101
+ if (!ipldEquals(a[key], b[key])) return false
102
+ }
103
+ return true
104
+ }
105
+ return a === b
106
+ }
package/src/strings.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import Graphemer from 'graphemer'
2
+ import * as ui8 from 'uint8arrays'
3
+
1
4
  // counts the number of bytes in a utf8 string
2
5
  export const utf8Len = (str: string): number => {
3
6
  return new TextEncoder().encode(str).byteLength
@@ -5,5 +8,49 @@ export const utf8Len = (str: string): number => {
5
8
 
6
9
  // counts the number of graphemes (user-displayed characters) in a string
7
10
  export const graphemeLen = (str: string): number => {
8
- return [...new Intl.Segmenter().segment(str)].length
11
+ const splitter = new Graphemer()
12
+ return splitter.countGraphemes(str)
13
+ }
14
+
15
+ export const utf8ToB64Url = (utf8: string): string => {
16
+ return ui8.toString(ui8.fromString(utf8, 'utf8'), 'base64url')
17
+ }
18
+
19
+ export const b64UrlToUtf8 = (b64: string): string => {
20
+ return ui8.toString(ui8.fromString(b64, 'base64url'), 'utf8')
21
+ }
22
+
23
+ export const parseLanguage = (langTag: string): LanguageTag | null => {
24
+ const parsed = langTag.match(bcp47Regexp)
25
+ if (!parsed?.groups) return null
26
+ const parts = parsed.groups
27
+ return {
28
+ grandfathered: parts.grandfathered,
29
+ language: parts.language,
30
+ extlang: parts.extlang,
31
+ script: parts.script,
32
+ region: parts.region,
33
+ variant: parts.variant,
34
+ extension: parts.extension,
35
+ privateUse: parts.privateUseA || parts.privateUseB,
36
+ }
37
+ }
38
+
39
+ export const validateLanguage = (langTag: string): boolean => {
40
+ return bcp47Regexp.test(langTag)
9
41
  }
42
+
43
+ export type LanguageTag = {
44
+ grandfathered?: string
45
+ language?: string
46
+ extlang?: string
47
+ script?: string
48
+ region?: string
49
+ variant?: string
50
+ extension?: string
51
+ privateUse?: string
52
+ }
53
+
54
+ // Validates well-formed BCP 47 syntax: https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1
55
+ const bcp47Regexp =
56
+ /^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?<privateUseA>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUseB>x(-[A-Za-z0-9]{1,8})+))$/
package/src/tid.ts CHANGED
@@ -1,14 +1,21 @@
1
1
  import { s32encode, s32decode } from './util'
2
+
3
+ const TID_LEN = 13
4
+
2
5
  let lastTimestamp = 0
3
6
  let timestampCount = 0
4
7
  let clockid: number | null = null
5
8
 
9
+ function dedash(str: string): string {
10
+ return str.replaceAll('-', '')
11
+ }
12
+
6
13
  export class TID {
7
14
  str: string
8
15
 
9
16
  constructor(str: string) {
10
- const noDashes = str.replace(/-/g, '')
11
- if (noDashes.length !== 13) {
17
+ const noDashes = dedash(str)
18
+ if (noDashes.length !== TID_LEN) {
12
19
  throw new Error(`Poorly formatted TID: ${noDashes.length} length`)
13
20
  }
14
21
  this.str = noDashes
@@ -46,31 +53,24 @@ export class TID {
46
53
  return new TID(str)
47
54
  }
48
55
 
49
- static newestFirst(a: TID, b: TID): number {
50
- return a.compareTo(b) * -1
51
- }
52
-
53
56
  static oldestFirst(a: TID, b: TID): number {
54
57
  return a.compareTo(b)
55
58
  }
56
59
 
60
+ static newestFirst(a: TID, b: TID): number {
61
+ return b.compareTo(a)
62
+ }
63
+
57
64
  static is(str: string): boolean {
58
- try {
59
- TID.fromStr(str)
60
- return true
61
- } catch (err) {
62
- return false
63
- }
65
+ return dedash(str).length === TID_LEN
64
66
  }
65
67
 
66
68
  timestamp(): number {
67
- const substr = this.str.slice(0, 11)
68
- return s32decode(substr)
69
+ return s32decode(this.str.slice(0, 11))
69
70
  }
70
71
 
71
72
  clockid(): number {
72
- const substr = this.str.slice(11, 13)
73
- return s32decode(substr)
73
+ return s32decode(this.str.slice(11, 13))
74
74
  }
75
75
 
76
76
  formatted(): string {
@@ -93,7 +93,7 @@ export class TID {
93
93
  }
94
94
 
95
95
  equals(other: TID): boolean {
96
- return this.compareTo(other) === 0
96
+ return this.str === other.str
97
97
  }
98
98
 
99
99
  newerThan(other: TID): boolean {
package/src/types.ts CHANGED
@@ -42,3 +42,5 @@ export const def = {
42
42
  }
43
43
 
44
44
  export type ArrayEl<A> = A extends readonly (infer T)[] ? T : never
45
+
46
+ export type NotEmptyArray<T> = [T, ...T[]]
package/src/util.ts CHANGED
@@ -9,6 +9,10 @@ export const noUndefinedVals = <T>(
9
9
  return obj
10
10
  }
11
11
 
12
+ export const jitter = (maxMs: number) => {
13
+ return Math.round((Math.random() - 0.5) * maxMs * 2)
14
+ }
15
+
12
16
  export const wait = (ms: number) => {
13
17
  return new Promise((res) => setTimeout(res, ms))
14
18
  }
@@ -40,7 +44,7 @@ export const flattenUint8Arrays = (arrs: Uint8Array[]): Uint8Array => {
40
44
  return flattened
41
45
  }
42
46
 
43
- export const streamToArray = async (
47
+ export const streamToBuffer = async (
44
48
  stream: AsyncIterable<Uint8Array>,
45
49
  ): Promise<Uint8Array> => {
46
50
  const arrays: Uint8Array[] = []
@@ -106,3 +110,15 @@ export const range = (num: number): number[] => {
106
110
  }
107
111
  return nums
108
112
  }
113
+
114
+ export const dedupeStrs = (strs: string[]): string[] => {
115
+ return [...new Set(strs)]
116
+ }
117
+
118
+ export const parseIntWithFallback = <T>(
119
+ value: string | undefined,
120
+ fallback: T,
121
+ ): number | T => {
122
+ const parsed = parseInt(value || '', 10)
123
+ return isNaN(parsed) ? fallback : parsed
124
+ }
@@ -0,0 +1,89 @@
1
+ import { check } from '../src/index'
2
+ import { ZodError } from 'zod'
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
+ })
@@ -1,4 +1,4 @@
1
- import { graphemeLen, utf8Len } from '../src'
1
+ import { graphemeLen, parseLanguage, utf8Len, validateLanguage } from '../src'
2
2
 
3
3
  describe('string', () => {
4
4
  it('calculates utf8 string length', () => {
@@ -14,7 +14,7 @@ describe('string', () => {
14
14
  expect(utf8Len('👨‍👩‍👧‍👧')).toBe(25)
15
15
  })
16
16
 
17
- it('caluclates grapheme length', () => {
17
+ it('calculates grapheme length', () => {
18
18
  expect(graphemeLen('a')).toBe(1)
19
19
  expect(graphemeLen('~')).toBe(1)
20
20
  expect(graphemeLen('ö')).toBe(1)
@@ -27,4 +27,88 @@ describe('string', () => {
27
27
  expect(graphemeLen('👨‍👩‍👧‍👧')).toBe(1)
28
28
  expect(graphemeLen('a~öñ©⽘☎𓋓😀👨‍👩‍👧‍👧')).toBe(10)
29
29
  })
30
+
31
+ describe('languages', () => {
32
+ it('validates BCP 47', () => {
33
+ // valid
34
+ expect(validateLanguage('de')).toEqual(true)
35
+ expect(validateLanguage('de-CH')).toEqual(true)
36
+ expect(validateLanguage('de-DE-1901')).toEqual(true)
37
+ expect(validateLanguage('es-419')).toEqual(true)
38
+ expect(validateLanguage('sl-IT-nedis')).toEqual(true)
39
+ expect(validateLanguage('mn-Cyrl-MN')).toEqual(true)
40
+ expect(validateLanguage('x-fr-CH')).toEqual(true)
41
+ expect(
42
+ validateLanguage('en-GB-boont-r-extended-sequence-x-private'),
43
+ ).toEqual(true)
44
+ expect(validateLanguage('sr-Cyrl')).toEqual(true)
45
+ expect(validateLanguage('hy-Latn-IT-arevela')).toEqual(true)
46
+ expect(validateLanguage('i-klingon')).toEqual(true)
47
+ // invalid
48
+ expect(validateLanguage('')).toEqual(false)
49
+ expect(validateLanguage('x')).toEqual(false)
50
+ expect(validateLanguage('de-CH-')).toEqual(false)
51
+ expect(validateLanguage('i-bad-grandfathered')).toEqual(false)
52
+ })
53
+
54
+ it('parses BCP 47', () => {
55
+ // valid
56
+ expect(parseLanguage('de')).toEqual({
57
+ language: 'de',
58
+ })
59
+ expect(parseLanguage('de-CH')).toEqual({
60
+ language: 'de',
61
+ region: 'CH',
62
+ })
63
+ expect(parseLanguage('de-DE-1901')).toEqual({
64
+ language: 'de',
65
+ region: 'DE',
66
+ variant: '1901',
67
+ })
68
+ expect(parseLanguage('es-419')).toEqual({
69
+ language: 'es',
70
+ region: '419',
71
+ })
72
+ expect(parseLanguage('sl-IT-nedis')).toEqual({
73
+ language: 'sl',
74
+ region: 'IT',
75
+ variant: 'nedis',
76
+ })
77
+ expect(parseLanguage('mn-Cyrl-MN')).toEqual({
78
+ language: 'mn',
79
+ script: 'Cyrl',
80
+ region: 'MN',
81
+ })
82
+ expect(parseLanguage('x-fr-CH')).toEqual({
83
+ privateUse: 'x-fr-CH',
84
+ })
85
+ expect(
86
+ parseLanguage('en-GB-boont-r-extended-sequence-x-private'),
87
+ ).toEqual({
88
+ language: 'en',
89
+ region: 'GB',
90
+ variant: 'boont',
91
+ extension: 'r-extended-sequence',
92
+ privateUse: 'x-private',
93
+ })
94
+ expect(parseLanguage('sr-Cyrl')).toEqual({
95
+ language: 'sr',
96
+ script: 'Cyrl',
97
+ })
98
+ expect(parseLanguage('hy-Latn-IT-arevela')).toEqual({
99
+ language: 'hy',
100
+ script: 'Latn',
101
+ region: 'IT',
102
+ variant: 'arevela',
103
+ })
104
+ expect(parseLanguage('i-klingon')).toEqual({
105
+ grandfathered: 'i-klingon',
106
+ })
107
+ // invalid
108
+ expect(parseLanguage('')).toEqual(null)
109
+ expect(parseLanguage('x')).toEqual(null)
110
+ expect(parseLanguage('de-CH-')).toEqual(null)
111
+ expect(parseLanguage('i-bad-grandfathered')).toEqual(null)
112
+ })
113
+ })
30
114
  })
package/tests/tid.test.ts CHANGED
@@ -15,4 +15,122 @@ describe('TIDs', () => {
15
15
  expect(parsed.timestamp()).toEqual(tid.timestamp())
16
16
  expect(parsed.clockid()).toEqual(tid.clockid())
17
17
  })
18
+
19
+ it('throws if invalid tid passed', () => {
20
+ expect(() => new TID('')).toThrow('Poorly formatted TID: 0 length')
21
+ })
22
+
23
+ describe('nextStr', () => {
24
+ it('returns next tid as a string', () => {
25
+ const str = TID.nextStr()
26
+ expect(typeof str).toEqual('string')
27
+ expect(str.length).toEqual(13)
28
+ })
29
+ })
30
+
31
+ describe('newestFirst', () => {
32
+ it('sorts tids newest first', () => {
33
+ const oldest = TID.next()
34
+ const newest = TID.next()
35
+
36
+ const tids = [oldest, newest]
37
+
38
+ tids.sort(TID.newestFirst)
39
+
40
+ expect(tids).toEqual([newest, oldest])
41
+ })
42
+ })
43
+
44
+ describe('oldestFirst', () => {
45
+ it('sorts tids oldest first', () => {
46
+ const oldest = TID.next()
47
+ const newest = TID.next()
48
+
49
+ const tids = [newest, oldest]
50
+
51
+ tids.sort(TID.oldestFirst)
52
+
53
+ expect(tids).toEqual([oldest, newest])
54
+ })
55
+ })
56
+
57
+ describe('is', () => {
58
+ it('true for valid tids', () => {
59
+ const tid = TID.next()
60
+ const asStr = tid.toString()
61
+
62
+ expect(TID.is(asStr)).toBe(true)
63
+ })
64
+
65
+ it('false for invalid tids', () => {
66
+ expect(TID.is('')).toBe(false)
67
+ })
68
+ })
69
+
70
+ describe('equals', () => {
71
+ it('true when same tid', () => {
72
+ const tid = TID.next()
73
+ expect(tid.equals(tid)).toBe(true)
74
+ })
75
+
76
+ it('true when different instance, same tid', () => {
77
+ const tid0 = TID.next()
78
+ const tid1 = new TID(tid0.toString())
79
+
80
+ expect(tid0.equals(tid1)).toBe(true)
81
+ })
82
+
83
+ it('false when different tid', () => {
84
+ const tid0 = TID.next()
85
+ const tid1 = TID.next()
86
+
87
+ expect(tid0.equals(tid1)).toBe(false)
88
+ })
89
+ })
90
+
91
+ describe('newerThan', () => {
92
+ it('true for newer tid', () => {
93
+ const tid0 = TID.next()
94
+ const tid1 = TID.next()
95
+
96
+ expect(tid1.newerThan(tid0)).toBe(true)
97
+ })
98
+
99
+ it('false for older tid', () => {
100
+ const tid0 = TID.next()
101
+ const tid1 = TID.next()
102
+
103
+ expect(tid0.newerThan(tid1)).toBe(false)
104
+ })
105
+
106
+ it('false for identical tids', () => {
107
+ const tid0 = TID.next()
108
+ const tid1 = new TID(tid0.toString())
109
+
110
+ expect(tid0.newerThan(tid1)).toBe(false)
111
+ })
112
+ })
113
+
114
+ describe('olderThan', () => {
115
+ it('true for older tid', () => {
116
+ const tid0 = TID.next()
117
+ const tid1 = TID.next()
118
+
119
+ expect(tid0.olderThan(tid1)).toBe(true)
120
+ })
121
+
122
+ it('false for newer tid', () => {
123
+ const tid0 = TID.next()
124
+ const tid1 = TID.next()
125
+
126
+ expect(tid1.olderThan(tid0)).toBe(false)
127
+ })
128
+
129
+ it('false for identical tids', () => {
130
+ const tid0 = TID.next()
131
+ const tid1 = new TID(tid0.toString())
132
+
133
+ expect(tid0.olderThan(tid1)).toBe(false)
134
+ })
135
+ })
18
136
  })