@atproto/lex-data 0.0.3 → 0.0.5
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 +22 -0
- package/dist/blob.d.ts +6 -0
- package/dist/blob.d.ts.map +1 -1
- package/dist/blob.js +2 -2
- package/dist/blob.js.map +1 -1
- package/dist/cid.d.ts +2 -3
- package/dist/cid.d.ts.map +1 -1
- package/dist/cid.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/language.d.ts +2 -2
- package/dist/language.d.ts.map +1 -1
- package/dist/language.js +4 -4
- package/dist/language.js.map +1 -1
- package/dist/lex-error.d.ts +17 -0
- package/dist/lex-error.d.ts.map +1 -0
- package/dist/lex-error.js +26 -0
- package/dist/lex-error.js.map +1 -0
- package/dist/lex.d.ts.map +1 -1
- package/dist/lex.js +49 -48
- package/dist/lex.js.map +1 -1
- package/dist/lib/nodejs-buffer.d.ts +1 -0
- package/dist/lib/nodejs-buffer.d.ts.map +1 -1
- package/dist/lib/nodejs-buffer.js +1 -1
- package/dist/lib/nodejs-buffer.js.map +1 -1
- package/dist/object.d.ts +13 -1
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +15 -2
- package/dist/object.js.map +1 -1
- package/dist/uint8array-concat.d.ts +3 -0
- package/dist/uint8array-concat.d.ts.map +1 -0
- package/dist/uint8array-concat.js +24 -0
- package/dist/uint8array-concat.js.map +1 -0
- package/dist/uint8array-from-base64.d.ts.map +1 -1
- package/dist/uint8array-from-base64.js +1 -1
- package/dist/uint8array-from-base64.js.map +1 -1
- package/dist/uint8array.d.ts +1 -0
- package/dist/uint8array.d.ts.map +1 -1
- package/dist/uint8array.js +14 -3
- package/dist/uint8array.js.map +1 -1
- package/dist/utf8-grapheme-len.d.ts.map +1 -1
- package/dist/utf8-grapheme-len.js +2 -2
- package/dist/utf8-grapheme-len.js.map +1 -1
- package/dist/utf8-len.d.ts.map +1 -1
- package/dist/utf8-len.js +1 -1
- package/dist/utf8-len.js.map +1 -1
- package/package.json +5 -5
- package/src/blob.test.ts +83 -2
- package/src/blob.ts +8 -2
- package/src/cid.test.ts +126 -0
- package/src/cid.ts +7 -7
- package/src/index.ts +1 -0
- package/src/language.test.ts +34 -33
- package/src/language.ts +2 -2
- package/src/lex-equals.test.ts +30 -0
- package/src/lex-error.ts +34 -0
- package/src/lex.test.ts +156 -1
- package/src/lex.ts +50 -43
- package/src/lib/nodejs-buffer.ts +2 -1
- package/src/object.test.ts +2 -0
- package/src/object.ts +16 -4
- package/src/uint8array-concat.test.ts +197 -0
- package/src/uint8array-concat.ts +21 -0
- package/src/uint8array-from-base64.test.ts +4 -1
- package/src/uint8array-from-base64.ts +1 -1
- package/src/uint8array-to-base64.test.ts +1 -1
- package/src/uint8array.test.ts +484 -0
- package/src/uint8array.ts +14 -2
- package/src/utf8-grapheme-len.test.ts +1 -0
- package/src/utf8-grapheme-len.ts +2 -2
- package/src/utf8-len.test.ts +1 -0
- package/src/utf8-len.ts +1 -1
- package/tsconfig.tests.json +2 -2
package/src/language.test.ts
CHANGED
|
@@ -1,62 +1,63 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { isLanguageString, parseLanguageString } from './language'
|
|
2
3
|
|
|
3
4
|
describe('string', () => {
|
|
4
5
|
describe('languages', () => {
|
|
5
6
|
it('validates BCP 47', () => {
|
|
6
7
|
// valid
|
|
7
|
-
expect(
|
|
8
|
-
expect(
|
|
9
|
-
expect(
|
|
10
|
-
expect(
|
|
11
|
-
expect(
|
|
12
|
-
expect(
|
|
13
|
-
expect(
|
|
14
|
-
expect(
|
|
15
|
-
|
|
16
|
-
)
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
19
|
-
expect(
|
|
8
|
+
expect(isLanguageString('de')).toEqual(true)
|
|
9
|
+
expect(isLanguageString('de-CH')).toEqual(true)
|
|
10
|
+
expect(isLanguageString('de-DE-1901')).toEqual(true)
|
|
11
|
+
expect(isLanguageString('es-419')).toEqual(true)
|
|
12
|
+
expect(isLanguageString('sl-IT-nedis')).toEqual(true)
|
|
13
|
+
expect(isLanguageString('mn-Cyrl-MN')).toEqual(true)
|
|
14
|
+
expect(isLanguageString('x-fr-CH')).toEqual(true)
|
|
15
|
+
expect(
|
|
16
|
+
isLanguageString('en-GB-boont-r-extended-sequence-x-private'),
|
|
17
|
+
).toEqual(true)
|
|
18
|
+
expect(isLanguageString('sr-Cyrl')).toEqual(true)
|
|
19
|
+
expect(isLanguageString('hy-Latn-IT-arevela')).toEqual(true)
|
|
20
|
+
expect(isLanguageString('i-klingon')).toEqual(true)
|
|
20
21
|
// invalid
|
|
21
|
-
expect(
|
|
22
|
-
expect(
|
|
23
|
-
expect(
|
|
24
|
-
expect(
|
|
22
|
+
expect(isLanguageString('')).toEqual(false)
|
|
23
|
+
expect(isLanguageString('x')).toEqual(false)
|
|
24
|
+
expect(isLanguageString('de-CH-')).toEqual(false)
|
|
25
|
+
expect(isLanguageString('i-bad-grandfathered')).toEqual(false)
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
it('parses BCP 47', () => {
|
|
28
29
|
// valid
|
|
29
|
-
expect(
|
|
30
|
+
expect(parseLanguageString('de')).toEqual({
|
|
30
31
|
language: 'de',
|
|
31
32
|
})
|
|
32
|
-
expect(
|
|
33
|
+
expect(parseLanguageString('de-CH')).toEqual({
|
|
33
34
|
language: 'de',
|
|
34
35
|
region: 'CH',
|
|
35
36
|
})
|
|
36
|
-
expect(
|
|
37
|
+
expect(parseLanguageString('de-DE-1901')).toEqual({
|
|
37
38
|
language: 'de',
|
|
38
39
|
region: 'DE',
|
|
39
40
|
variant: '1901',
|
|
40
41
|
})
|
|
41
|
-
expect(
|
|
42
|
+
expect(parseLanguageString('es-419')).toEqual({
|
|
42
43
|
language: 'es',
|
|
43
44
|
region: '419',
|
|
44
45
|
})
|
|
45
|
-
expect(
|
|
46
|
+
expect(parseLanguageString('sl-IT-nedis')).toEqual({
|
|
46
47
|
language: 'sl',
|
|
47
48
|
region: 'IT',
|
|
48
49
|
variant: 'nedis',
|
|
49
50
|
})
|
|
50
|
-
expect(
|
|
51
|
+
expect(parseLanguageString('mn-Cyrl-MN')).toEqual({
|
|
51
52
|
language: 'mn',
|
|
52
53
|
script: 'Cyrl',
|
|
53
54
|
region: 'MN',
|
|
54
55
|
})
|
|
55
|
-
expect(
|
|
56
|
+
expect(parseLanguageString('x-fr-CH')).toEqual({
|
|
56
57
|
privateUse: 'x-fr-CH',
|
|
57
58
|
})
|
|
58
59
|
expect(
|
|
59
|
-
|
|
60
|
+
parseLanguageString('en-GB-boont-r-extended-sequence-x-private'),
|
|
60
61
|
).toEqual({
|
|
61
62
|
language: 'en',
|
|
62
63
|
region: 'GB',
|
|
@@ -64,24 +65,24 @@ describe('string', () => {
|
|
|
64
65
|
extension: 'r-extended-sequence',
|
|
65
66
|
privateUse: 'x-private',
|
|
66
67
|
})
|
|
67
|
-
expect(
|
|
68
|
+
expect(parseLanguageString('sr-Cyrl')).toEqual({
|
|
68
69
|
language: 'sr',
|
|
69
70
|
script: 'Cyrl',
|
|
70
71
|
})
|
|
71
|
-
expect(
|
|
72
|
+
expect(parseLanguageString('hy-Latn-IT-arevela')).toEqual({
|
|
72
73
|
language: 'hy',
|
|
73
74
|
script: 'Latn',
|
|
74
75
|
region: 'IT',
|
|
75
76
|
variant: 'arevela',
|
|
76
77
|
})
|
|
77
|
-
expect(
|
|
78
|
+
expect(parseLanguageString('i-klingon')).toEqual({
|
|
78
79
|
grandfathered: 'i-klingon',
|
|
79
80
|
})
|
|
80
81
|
// invalid
|
|
81
|
-
expect(
|
|
82
|
-
expect(
|
|
83
|
-
expect(
|
|
84
|
-
expect(
|
|
82
|
+
expect(parseLanguageString('')).toEqual(null)
|
|
83
|
+
expect(parseLanguageString('x')).toEqual(null)
|
|
84
|
+
expect(parseLanguageString('de-CH-')).toEqual(null)
|
|
85
|
+
expect(parseLanguageString('i-bad-grandfathered')).toEqual(null)
|
|
85
86
|
})
|
|
86
87
|
})
|
|
87
88
|
})
|
package/src/language.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type LanguageTag = {
|
|
|
12
12
|
privateUse?: string
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function
|
|
15
|
+
export function parseLanguageString(input: string): LanguageTag | null {
|
|
16
16
|
const parsed = input.match(BCP47_REGEXP)
|
|
17
17
|
if (!parsed?.groups) return null
|
|
18
18
|
|
|
@@ -34,6 +34,6 @@ export function parseLanguage(input: string): LanguageTag | null {
|
|
|
34
34
|
*
|
|
35
35
|
* @see {@link https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1}
|
|
36
36
|
*/
|
|
37
|
-
export function
|
|
37
|
+
export function isLanguageString(input: string): boolean {
|
|
38
38
|
return BCP47_REGEXP.test(input)
|
|
39
39
|
}
|
package/src/lex-equals.test.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
1
2
|
import { parseCid } from './cid.js'
|
|
2
3
|
import { lexEquals } from './lex-equals.js'
|
|
3
4
|
import { LexValue } from './lex.js'
|
|
@@ -38,6 +39,7 @@ describe('lexEquals', () => {
|
|
|
38
39
|
expectLexEqual([1, 2, 3], [1, 2, 4], false)
|
|
39
40
|
expectLexEqual([1, 2, 3], [1, 2], false)
|
|
40
41
|
expectLexEqual([1, 2, 3], 'not an array', false)
|
|
42
|
+
expectLexEqual([1, 2, 3], { 0: 1, 1: 2, 2: 3 }, false)
|
|
41
43
|
})
|
|
42
44
|
|
|
43
45
|
it('compares Uint8Arrays', () => {
|
|
@@ -59,10 +61,23 @@ describe('lexEquals', () => {
|
|
|
59
61
|
expectLexEqual(cid2, cid3, true)
|
|
60
62
|
|
|
61
63
|
expectLexEqual(cid1, cid1.toString(), false)
|
|
64
|
+
expectLexEqual(cid1, { not: 'a cid' }, false)
|
|
65
|
+
expectLexEqual(cid1, [], false)
|
|
66
|
+
expectLexEqual(cid1, cid1.bytes, false)
|
|
62
67
|
})
|
|
63
68
|
|
|
64
69
|
it('compares objects', () => {
|
|
65
70
|
expectLexEqual({ a: 1, b: 2 }, { a: 1, b: 2 }, true)
|
|
71
|
+
expectLexEqual(
|
|
72
|
+
{ a: 1, b: 2, c: undefined },
|
|
73
|
+
{ a: 1, b: 2, c: undefined },
|
|
74
|
+
true,
|
|
75
|
+
)
|
|
76
|
+
expectLexEqual(
|
|
77
|
+
{ a: 1, b: 2, c: { e: 1, d: undefined } },
|
|
78
|
+
{ a: 1, b: 2, c: { d: undefined, e: 1 } },
|
|
79
|
+
true,
|
|
80
|
+
)
|
|
66
81
|
expectLexEqual(
|
|
67
82
|
{ a: 1, b: { unicode: 'a~öñ©⽘☎𓋓😀👨👩👧👧' } },
|
|
68
83
|
{ a: 1, b: { unicode: 'a~öñ©⽘☎𓋓😀👨👩👧👧' } },
|
|
@@ -75,6 +90,21 @@ describe('lexEquals', () => {
|
|
|
75
90
|
expectLexEqual({ a: 1, b: 2 }, null, false)
|
|
76
91
|
})
|
|
77
92
|
|
|
93
|
+
it('accounts for undefined (but present) properties in objects', () => {
|
|
94
|
+
expectLexEqual({ a: 1, b: undefined }, { a: 1 }, false)
|
|
95
|
+
expectLexEqual(
|
|
96
|
+
{ a: 1, b: { c: undefined, d: 2 } },
|
|
97
|
+
{ a: 1, b: { d: 2 } },
|
|
98
|
+
false,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
expectLexEqual(
|
|
102
|
+
{ a: 1, b: { c: undefined, d: 2 } },
|
|
103
|
+
{ a: 1, b: { c: 3, d: 2 } },
|
|
104
|
+
false,
|
|
105
|
+
)
|
|
106
|
+
})
|
|
107
|
+
|
|
78
108
|
it('compares nested structures', () => {
|
|
79
109
|
const lex1 = {
|
|
80
110
|
foo: [1, 2, { bar: new Uint8Array([3, 4, 5]) }],
|
package/src/lex-error.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type LexErrorCode = string
|
|
2
|
+
|
|
3
|
+
export type LexErrorData<N extends LexErrorCode = LexErrorCode> = {
|
|
4
|
+
error: N
|
|
5
|
+
message?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class LexError<N extends LexErrorCode = LexErrorCode> extends Error {
|
|
9
|
+
name = 'LexError'
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
readonly error: N,
|
|
13
|
+
message?: string,
|
|
14
|
+
options?: ErrorOptions,
|
|
15
|
+
) {
|
|
16
|
+
super(message, options)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
toString(): string {
|
|
20
|
+
return `${this.name}: [${this.error}] ${this.message}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
toJSON(): LexErrorData<N> {
|
|
24
|
+
const { error, message } = this
|
|
25
|
+
return { error, message: message ?? undefined }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Translate into an HTTP response for downstream clients.
|
|
30
|
+
*/
|
|
31
|
+
toResponse(): Response {
|
|
32
|
+
return Response.json(this.toJSON(), { status: 400 })
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/lex.test.ts
CHANGED
|
@@ -1,4 +1,159 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { parseCid } from './cid.js'
|
|
3
|
+
import { isLexArray, isLexScalar, isLexValue, isTypedLexMap } from './lex.js'
|
|
4
|
+
|
|
5
|
+
describe('isLexScalar', () => {
|
|
6
|
+
for (const { note, value, expected } of [
|
|
7
|
+
{ note: 'string', value: 'hello', expected: true },
|
|
8
|
+
{ note: 'boolean', value: true, expected: true },
|
|
9
|
+
{ note: 'null', value: null, expected: true },
|
|
10
|
+
{ note: 'Uint8Array', value: new Uint8Array([1, 2, 3]), expected: true },
|
|
11
|
+
{
|
|
12
|
+
note: 'Cid',
|
|
13
|
+
value: parseCid(
|
|
14
|
+
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
15
|
+
),
|
|
16
|
+
expected: true,
|
|
17
|
+
},
|
|
18
|
+
{ note: 'number (integer)', value: 42, expected: true },
|
|
19
|
+
{ note: 'number (float)', value: 3.14, expected: false },
|
|
20
|
+
{ note: 'object', value: { a: 1 }, expected: false },
|
|
21
|
+
{ note: 'array', value: [1, 2, 3], expected: false },
|
|
22
|
+
{ note: 'undefined', value: undefined, expected: false },
|
|
23
|
+
{ note: 'function', value: () => {}, expected: false },
|
|
24
|
+
]) {
|
|
25
|
+
it(note, () => {
|
|
26
|
+
const result = isLexScalar(value)
|
|
27
|
+
expect(result).toBe(expected)
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('isLexArray', () => {
|
|
33
|
+
it('returns true for valid LexArray', () => {
|
|
34
|
+
const list = [123, 'blah', true, null, new Uint8Array([1, 2, 3]), { a: 1 }]
|
|
35
|
+
expect(isLexArray(list)).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('returns false for non-arrays', () => {
|
|
39
|
+
const values = [
|
|
40
|
+
123,
|
|
41
|
+
'blah',
|
|
42
|
+
true,
|
|
43
|
+
null,
|
|
44
|
+
new Uint8Array([1, 2, 3]),
|
|
45
|
+
{ a: 1 },
|
|
46
|
+
]
|
|
47
|
+
for (const value of values) {
|
|
48
|
+
expect(isLexArray(value)).toBe(false)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('returns false for arrays with non-Lex values', () => {
|
|
53
|
+
expect(isLexArray([123, 'blah', () => {}])).toBe(false)
|
|
54
|
+
expect(isLexArray([123, 'blah', undefined])).toBe(false)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('isLexValue', () => {
|
|
59
|
+
describe('valid values', () => {
|
|
60
|
+
for (const { note, value } of [
|
|
61
|
+
{ note: 'string', value: 'hello' },
|
|
62
|
+
{ note: 'boolean', value: true },
|
|
63
|
+
{ note: 'null', value: null },
|
|
64
|
+
{ note: 'Uint8Array', value: new Uint8Array([1, 2, 3]) },
|
|
65
|
+
{
|
|
66
|
+
note: 'Cid',
|
|
67
|
+
value: parseCid(
|
|
68
|
+
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
69
|
+
),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
note: 'record with Lex values',
|
|
73
|
+
value: {
|
|
74
|
+
a: 123,
|
|
75
|
+
b: 'blah',
|
|
76
|
+
c: true,
|
|
77
|
+
d: null,
|
|
78
|
+
e: new Uint8Array([1, 2, 3]),
|
|
79
|
+
f: {
|
|
80
|
+
nested: 'value',
|
|
81
|
+
},
|
|
82
|
+
g: [1, 2, 3],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
note: 'list with Lex values',
|
|
87
|
+
value: [
|
|
88
|
+
123,
|
|
89
|
+
'blah',
|
|
90
|
+
true,
|
|
91
|
+
null,
|
|
92
|
+
new Uint8Array([1, 2, 3]),
|
|
93
|
+
{
|
|
94
|
+
nested: 'value',
|
|
95
|
+
},
|
|
96
|
+
[1, 2, 3],
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
]) {
|
|
100
|
+
it(note, () => {
|
|
101
|
+
expect(isLexValue(value)).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('invalid values', () => {
|
|
107
|
+
for (const { note, value } of [
|
|
108
|
+
{ note: 'float', value: 123.456 },
|
|
109
|
+
{ note: 'undefined', value: undefined },
|
|
110
|
+
{ note: 'function', value: () => {} },
|
|
111
|
+
{ note: 'obj with fn', value: { a: 123, b: () => {} } },
|
|
112
|
+
{ note: 'list with non-Lex value', value: [123, 'blah', () => {}] },
|
|
113
|
+
{ note: 'Date object', value: new Date() },
|
|
114
|
+
{ note: 'Map object', value: new Map() },
|
|
115
|
+
{ note: 'Set object', value: new Set() },
|
|
116
|
+
{ note: 'class instance', value: new (class A {})() },
|
|
117
|
+
]) {
|
|
118
|
+
it(note, () => {
|
|
119
|
+
expect(isLexValue(value)).toBe(false)
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('handles cyclic structures', () => {
|
|
125
|
+
const record: any = {
|
|
126
|
+
a: 123,
|
|
127
|
+
b: 'blah',
|
|
128
|
+
}
|
|
129
|
+
record.c = record
|
|
130
|
+
|
|
131
|
+
expect(isLexValue(record)).toBe(false)
|
|
132
|
+
|
|
133
|
+
const list: any[] = [123, 'blah']
|
|
134
|
+
list.push(list)
|
|
135
|
+
|
|
136
|
+
expect(isLexValue(list)).toBe(false)
|
|
137
|
+
|
|
138
|
+
const complex: any = {
|
|
139
|
+
a: {
|
|
140
|
+
b: [1, 2, 3],
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
complex.a.b.push(complex)
|
|
144
|
+
|
|
145
|
+
expect(isLexValue(complex)).toBe(false)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('handles deeply nested structures', () => {
|
|
149
|
+
type Value = null | { nested: Value }
|
|
150
|
+
let value: Value = null
|
|
151
|
+
for (let i = 0; i < 1_000_000; i++) {
|
|
152
|
+
value = { nested: value }
|
|
153
|
+
}
|
|
154
|
+
expect(isLexValue(value)).toBe(true)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
2
157
|
|
|
3
158
|
describe('isLexMap', () => {
|
|
4
159
|
it('returns true for valid LexMap', () => {
|
package/src/lex.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Cid, isCid } from './cid.js'
|
|
2
|
-
import { isPlainObject } from './object.js'
|
|
3
|
-
|
|
4
|
-
// @NOTE BlobRef is just a special case of LexMap.
|
|
2
|
+
import { isPlainObject, isPlainProto } from './object.js'
|
|
5
3
|
|
|
6
4
|
export type LexScalar = number | string | boolean | null | Cid | Uint8Array
|
|
7
5
|
export type LexValue = LexScalar | LexValue[] | { [_ in string]?: LexValue }
|
|
@@ -9,65 +7,74 @@ export type LexMap = { [_ in string]?: LexValue }
|
|
|
9
7
|
export type LexArray = LexValue[]
|
|
10
8
|
|
|
11
9
|
export function isLexMap(value: unknown): value is LexMap {
|
|
12
|
-
|
|
13
|
-
for (const key in value) {
|
|
14
|
-
if (!isLexValue(value[key])) return false
|
|
15
|
-
}
|
|
16
|
-
return true
|
|
10
|
+
return isPlainObject(value) && Object.values(value).every(isLexValue)
|
|
17
11
|
}
|
|
18
12
|
|
|
19
13
|
export function isLexArray(value: unknown): value is LexArray {
|
|
20
|
-
|
|
21
|
-
for (let i = 0; i < value.length; i++) {
|
|
22
|
-
if (!isLexValue(value[i])) return false
|
|
23
|
-
}
|
|
24
|
-
return true
|
|
14
|
+
return Array.isArray(value) && value.every(isLexValue)
|
|
25
15
|
}
|
|
26
16
|
|
|
27
17
|
export function isLexScalar(value: unknown): value is LexScalar {
|
|
28
18
|
switch (typeof value) {
|
|
29
19
|
case 'object':
|
|
30
|
-
|
|
31
|
-
return value instanceof Uint8Array || isCid(value)
|
|
20
|
+
return value === null || value instanceof Uint8Array || isCid(value)
|
|
32
21
|
case 'string':
|
|
33
22
|
case 'boolean':
|
|
34
23
|
return true
|
|
35
24
|
case 'number':
|
|
36
25
|
if (Number.isInteger(value)) return true
|
|
37
|
-
|
|
26
|
+
// fallthrough
|
|
38
27
|
default:
|
|
39
|
-
|
|
28
|
+
return false
|
|
40
29
|
}
|
|
41
30
|
}
|
|
42
31
|
|
|
43
32
|
export function isLexValue(value: unknown): value is LexValue {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
33
|
+
// Using a stack to avoid recursion depth issues.
|
|
34
|
+
const stack: unknown[] = [value]
|
|
35
|
+
// Cyclic structures are not valid LexValues as they cannot be serialized to
|
|
36
|
+
// JSON or CBOR. This also allows us to avoid infinite loops when traversing
|
|
37
|
+
// the structure.
|
|
38
|
+
const visited = new Set<object>()
|
|
39
|
+
|
|
40
|
+
do {
|
|
41
|
+
const value = stack.pop()!
|
|
42
|
+
|
|
43
|
+
// Optimization: we are not using `isLexScalar` here to avoid extra function
|
|
44
|
+
// calls, and to avoid computing `typeof value` multiple times.
|
|
45
|
+
switch (typeof value) {
|
|
46
|
+
case 'object':
|
|
47
|
+
if (value === null) {
|
|
48
|
+
// LexScalar
|
|
49
|
+
} else if (isPlainProto(value)) {
|
|
50
|
+
if (visited.has(value)) return false
|
|
51
|
+
visited.add(value)
|
|
52
|
+
stack.push(...Object.values(value))
|
|
53
|
+
} else if (Array.isArray(value)) {
|
|
54
|
+
if (visited.has(value)) return false
|
|
55
|
+
visited.add(value)
|
|
56
|
+
stack.push(...value)
|
|
57
|
+
} else if (value instanceof Uint8Array || isCid(value)) {
|
|
58
|
+
// LexScalar
|
|
59
|
+
} else {
|
|
60
|
+
return false
|
|
62
61
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
break
|
|
63
|
+
case 'string':
|
|
64
|
+
case 'boolean':
|
|
65
|
+
break
|
|
66
|
+
case 'number':
|
|
67
|
+
if (Number.isInteger(value)) break
|
|
68
|
+
// fallthrough
|
|
69
|
+
default:
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
} while (stack.length > 0)
|
|
73
|
+
|
|
74
|
+
// Optimization: ease GC's work
|
|
75
|
+
visited.clear()
|
|
76
|
+
|
|
77
|
+
return true
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
export type TypedLexMap = LexMap & { $type: string }
|
package/src/lib/nodejs-buffer.ts
CHANGED
|
@@ -12,6 +12,7 @@ interface NodeJSBufferConstructor {
|
|
|
12
12
|
input: Uint8Array | ArrayBuffer | ArrayBufferView,
|
|
13
13
|
): NodeJSBuffer<ArrayBuffer>
|
|
14
14
|
from(input: string, encoding?: Encoding): NodeJSBuffer<ArrayBuffer>
|
|
15
|
+
concat(list: readonly Uint8Array[], totalLength?: number): NodeJSBuffer
|
|
15
16
|
byteLength(input: string, encoding?: Encoding): number
|
|
16
17
|
prototype: NodeJSBuffer
|
|
17
18
|
}
|
|
@@ -24,4 +25,4 @@ export const NodeJSBuffer: NodeJSBufferConstructor | null =
|
|
|
24
25
|
(globalThis as any)?.[BUFFER]?.prototype instanceof Uint8Array &&
|
|
25
26
|
'byteLength' in (globalThis as any)[BUFFER]
|
|
26
27
|
? ((globalThis as any)[BUFFER] as NodeJSBufferConstructor)
|
|
27
|
-
: null
|
|
28
|
+
: /* v8 ignore next -- @preserve */ null
|
package/src/object.test.ts
CHANGED
package/src/object.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether the input is an object (not null).
|
|
3
|
+
*/
|
|
1
4
|
export function isObject(input: unknown): input is object {
|
|
2
5
|
return input != null && typeof input === 'object'
|
|
3
6
|
}
|
|
@@ -5,10 +8,19 @@ export function isObject(input: unknown): input is object {
|
|
|
5
8
|
const ObjectProto = Object.prototype
|
|
6
9
|
const ObjectToString = Object.prototype.toString
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Checks whether the input is an object (not null) whose prototype is either
|
|
13
|
+
* null or `Object.prototype`.
|
|
14
|
+
*/
|
|
15
|
+
export function isPlainObject(input: unknown) {
|
|
16
|
+
return isObject(input) && isPlainProto(input)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Checks whether the prototype of the input object is either null or
|
|
21
|
+
* `Object.prototype`.
|
|
22
|
+
*/
|
|
23
|
+
export function isPlainProto(input: object): input is Record<string, unknown> {
|
|
12
24
|
const proto = Object.getPrototypeOf(input)
|
|
13
25
|
if (proto === null) return true
|
|
14
26
|
return (
|