@atproto/lex-data 0.1.3 → 0.1.4
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 +8 -0
- package/dist/blob.d.ts +2 -2
- package/dist/blob.d.ts.map +1 -1
- package/dist/blob.js +1 -1
- package/dist/blob.js.map +1 -1
- package/dist/lex-equals.d.ts +1 -1
- package/dist/lex-equals.d.ts.map +1 -1
- package/dist/lex-equals.js.map +1 -1
- package/dist/lex.d.ts +1 -1
- package/dist/lex.d.ts.map +1 -1
- package/dist/lex.js.map +1 -1
- package/dist/lib/nodejs-buffer.js.map +1 -1
- package/dist/uint8array-from-base64.d.ts +1 -1
- package/dist/uint8array-from-base64.d.ts.map +1 -1
- package/dist/uint8array-from-base64.js.map +1 -1
- package/dist/uint8array-to-base64.d.ts +1 -1
- package/dist/uint8array-to-base64.d.ts.map +1 -1
- package/dist/uint8array-to-base64.js.map +1 -1
- package/dist/uint8array.d.ts +1 -1
- package/dist/uint8array.d.ts.map +1 -1
- package/dist/uint8array.js.map +1 -1
- package/dist/utf8-from-base64.d.ts +1 -1
- package/dist/utf8-from-base64.d.ts.map +1 -1
- package/dist/utf8-from-base64.js.map +1 -1
- package/dist/utf8-to-base64.d.ts +1 -1
- package/dist/utf8-to-base64.d.ts.map +1 -1
- package/dist/utf8-to-base64.js.map +1 -1
- package/dist/utf8.d.ts +1 -1
- package/dist/utf8.d.ts.map +1 -1
- package/dist/utf8.js.map +1 -1
- package/package.json +4 -8
- package/src/blob.test.ts +0 -405
- package/src/blob.ts +0 -478
- package/src/cid-implementation.test.ts +0 -129
- package/src/cid.test.ts +0 -350
- package/src/cid.ts +0 -603
- package/src/core-js.d.ts +0 -2
- package/src/index.ts +0 -8
- package/src/lex-equals.test.ts +0 -183
- package/src/lex-equals.ts +0 -123
- package/src/lex-error.test.ts +0 -54
- package/src/lex-error.ts +0 -83
- package/src/lex.test.ts +0 -279
- package/src/lex.ts +0 -253
- package/src/lib/nodejs-buffer.ts +0 -46
- package/src/lib/util.test.ts +0 -49
- package/src/lib/util.ts +0 -7
- package/src/object.test.ts +0 -80
- package/src/object.ts +0 -83
- package/src/uint8array-base64.ts +0 -2
- package/src/uint8array-concat.test.ts +0 -197
- package/src/uint8array-concat.ts +0 -25
- package/src/uint8array-from-base64.test.ts +0 -130
- package/src/uint8array-from-base64.ts +0 -98
- package/src/uint8array-to-base64.test.ts +0 -170
- package/src/uint8array-to-base64.ts +0 -55
- package/src/uint8array.test.ts +0 -503
- package/src/uint8array.ts +0 -197
- package/src/utf8-from-base64.test.ts +0 -39
- package/src/utf8-from-base64.ts +0 -23
- package/src/utf8-from-bytes.test.ts +0 -43
- package/src/utf8-from-bytes.ts +0 -21
- package/src/utf8-grapheme-len.test.ts +0 -38
- package/src/utf8-grapheme-len.ts +0 -21
- package/src/utf8-len.test.ts +0 -21
- package/src/utf8-len.ts +0 -51
- package/src/utf8-to-base64.test.ts +0 -35
- package/src/utf8-to-base64.ts +0 -22
- package/src/utf8.ts +0 -128
- package/tsconfig.build.json +0 -12
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -8
package/src/lex.test.ts
DELETED
|
@@ -1,279 +0,0 @@
|
|
|
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
|
-
})
|
|
157
|
-
|
|
158
|
-
describe('isLexMap', () => {
|
|
159
|
-
it('returns true for valid LexMap', () => {
|
|
160
|
-
const record = {
|
|
161
|
-
a: 123,
|
|
162
|
-
b: 'blah',
|
|
163
|
-
c: true,
|
|
164
|
-
d: null,
|
|
165
|
-
e: new Uint8Array([1, 2, 3]),
|
|
166
|
-
f: {
|
|
167
|
-
nested: 'value',
|
|
168
|
-
},
|
|
169
|
-
g: [1, 2, 3],
|
|
170
|
-
}
|
|
171
|
-
expect(isTypedLexMap(record)).toBe(false)
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('returns false for non-records', () => {
|
|
175
|
-
const values = [
|
|
176
|
-
123,
|
|
177
|
-
'blah',
|
|
178
|
-
true,
|
|
179
|
-
null,
|
|
180
|
-
new Uint8Array([1, 2, 3]),
|
|
181
|
-
[1, 2, 3],
|
|
182
|
-
]
|
|
183
|
-
for (const value of values) {
|
|
184
|
-
expect(isTypedLexMap(value)).toBe(false)
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
it('returns false for records with non-Lex values', () => {
|
|
189
|
-
expect(
|
|
190
|
-
// @ts-expect-error
|
|
191
|
-
isTypedLexMap({
|
|
192
|
-
a: 123,
|
|
193
|
-
b: () => {},
|
|
194
|
-
}),
|
|
195
|
-
).toBe(false)
|
|
196
|
-
expect(
|
|
197
|
-
isTypedLexMap({
|
|
198
|
-
a: 123,
|
|
199
|
-
b: undefined,
|
|
200
|
-
}),
|
|
201
|
-
).toBe(false)
|
|
202
|
-
})
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
describe('isTypedLexMap', () => {
|
|
206
|
-
describe('valid records', () => {
|
|
207
|
-
for (const { note, json } of [
|
|
208
|
-
{
|
|
209
|
-
note: 'trivial record',
|
|
210
|
-
json: {
|
|
211
|
-
$type: 'com.example.blah',
|
|
212
|
-
a: 123,
|
|
213
|
-
b: 'blah',
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
note: 'float, but integer-like',
|
|
218
|
-
json: {
|
|
219
|
-
$type: 'com.example.blah',
|
|
220
|
-
a: 123.0,
|
|
221
|
-
b: 'blah',
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
note: 'empty list and object',
|
|
226
|
-
json: {
|
|
227
|
-
$type: 'com.example.blah',
|
|
228
|
-
a: [],
|
|
229
|
-
b: {},
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
]) {
|
|
233
|
-
it(note, () => {
|
|
234
|
-
expect(isTypedLexMap(json)).toBe(true)
|
|
235
|
-
})
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
describe('invalid records', () => {
|
|
240
|
-
for (const { note, json } of [
|
|
241
|
-
{
|
|
242
|
-
note: 'float',
|
|
243
|
-
json: {
|
|
244
|
-
$type: 'com.example.blah',
|
|
245
|
-
a: 123.456,
|
|
246
|
-
b: 'blah',
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
note: 'record with $type null',
|
|
251
|
-
json: {
|
|
252
|
-
$type: null,
|
|
253
|
-
a: 123,
|
|
254
|
-
b: 'blah',
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
note: 'record with $type wrong type',
|
|
259
|
-
json: {
|
|
260
|
-
$type: 123,
|
|
261
|
-
a: 123,
|
|
262
|
-
b: 'blah',
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
note: 'record with empty $type string',
|
|
267
|
-
json: {
|
|
268
|
-
$type: '',
|
|
269
|
-
a: 123,
|
|
270
|
-
b: 'blah',
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
]) {
|
|
274
|
-
it(note, () => {
|
|
275
|
-
expect(isTypedLexMap(json)).toBe(false)
|
|
276
|
-
})
|
|
277
|
-
}
|
|
278
|
-
})
|
|
279
|
-
})
|
package/src/lex.ts
DELETED
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
import { Cid, isCid } from './cid.js'
|
|
2
|
-
import { isPlainObject } from './object.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Primitive values in the Lexicon data model.
|
|
6
|
-
*
|
|
7
|
-
* Represents the basic scalar types that can appear in AT Protocol data:
|
|
8
|
-
* - `number` - Integer values only (no floats)
|
|
9
|
-
* - `string` - UTF-8 text
|
|
10
|
-
* - `boolean` - true or false
|
|
11
|
-
* - `null` - Explicit null value
|
|
12
|
-
* - `Cid` - Content Identifier (link by hash)
|
|
13
|
-
* - `Uint8Array` - Binary data (bytes)
|
|
14
|
-
*
|
|
15
|
-
* @see {@link LexValue} for the complete recursive value type
|
|
16
|
-
*/
|
|
17
|
-
export type LexScalar = number | string | boolean | null | Cid | Uint8Array
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Any valid Lexicon value (recursive type).
|
|
21
|
-
*
|
|
22
|
-
* This is the union of all types that can appear in AT Protocol Lexicon data:
|
|
23
|
-
* - {@link LexScalar} - Primitive values (number, string, boolean, null, Cid, Uint8Array)
|
|
24
|
-
* - `LexValue[]` - Arrays of LexValues
|
|
25
|
-
* - `{ [key: string]?: LexValue }` - Objects with string keys and LexValue values
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```typescript
|
|
29
|
-
* import type { LexValue } from '@atproto/lex'
|
|
30
|
-
*
|
|
31
|
-
* const scalar: LexValue = 'hello'
|
|
32
|
-
* const array: LexValue = [1, 2, 3]
|
|
33
|
-
* const object: LexValue = { name: 'Alice', age: 30 }
|
|
34
|
-
* ```
|
|
35
|
-
*
|
|
36
|
-
* @see {@link LexScalar} for primitive value types
|
|
37
|
-
* @see {@link LexMap} for object types
|
|
38
|
-
* @see {@link LexArray} for array types
|
|
39
|
-
*/
|
|
40
|
-
export type LexValue = LexScalar | LexValue[] | { [_ in string]?: LexValue }
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Object with string keys and LexValue values.
|
|
44
|
-
*
|
|
45
|
-
* Represents a plain object in the Lexicon data model where all values
|
|
46
|
-
* must be valid {@link LexValue} types.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```typescript
|
|
50
|
-
* import type { LexMap } from '@atproto/lex'
|
|
51
|
-
*
|
|
52
|
-
* const user: LexMap = {
|
|
53
|
-
* name: 'Alice',
|
|
54
|
-
* age: 30,
|
|
55
|
-
* tags: ['admin', 'user']
|
|
56
|
-
* }
|
|
57
|
-
* ```
|
|
58
|
-
*
|
|
59
|
-
* @see {@link TypedLexMap} for objects with a required `$type` property
|
|
60
|
-
*/
|
|
61
|
-
export type LexMap = { [_ in string]?: LexValue }
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Array of {@link LexValue} elements.
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```typescript
|
|
68
|
-
* import type { LexArray } from '@atproto/lex'
|
|
69
|
-
*
|
|
70
|
-
* const items: LexArray = [1, 'two', { three: 3 }]
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
export type LexArray = LexValue[]
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Type guard to check if a value is a valid {@link LexMap}.
|
|
77
|
-
*
|
|
78
|
-
* Returns true if the value is a plain object where all values are valid
|
|
79
|
-
* {@link LexValue} types.
|
|
80
|
-
*
|
|
81
|
-
* @param value - The value to check
|
|
82
|
-
* @returns `true` if the value is a valid LexMap
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```typescript
|
|
86
|
-
* import { isLexMap } from '@atproto/lex'
|
|
87
|
-
*
|
|
88
|
-
* if (isLexMap(data)) {
|
|
89
|
-
* // data is narrowed to LexMap
|
|
90
|
-
* console.log(Object.keys(data))
|
|
91
|
-
* }
|
|
92
|
-
* ```
|
|
93
|
-
*/
|
|
94
|
-
export function isLexMap(value: unknown): value is LexMap {
|
|
95
|
-
return isPlainObject(value) && Object.values(value).every(isLexValue)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Type guard to check if a value is a valid {@link LexArray}.
|
|
100
|
-
*
|
|
101
|
-
* Returns true if the value is an array where all elements are valid
|
|
102
|
-
* {@link LexValue} types.
|
|
103
|
-
*
|
|
104
|
-
* @param value - The value to check
|
|
105
|
-
* @returns `true` if the value is a valid LexArray
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* ```typescript
|
|
109
|
-
* import { isLexArray } from '@atproto/lex'
|
|
110
|
-
*
|
|
111
|
-
* if (isLexArray(data)) {
|
|
112
|
-
* // data is narrowed to LexArray
|
|
113
|
-
* data.forEach(item => console.log(item))
|
|
114
|
-
* }
|
|
115
|
-
* ```
|
|
116
|
-
*/
|
|
117
|
-
export function isLexArray(value: unknown): value is LexArray {
|
|
118
|
-
return Array.isArray(value) && value.every(isLexValue)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Type guard to check if a value is a valid {@link LexScalar}.
|
|
123
|
-
*
|
|
124
|
-
* Returns true if the value is one of the primitive Lexicon types:
|
|
125
|
-
* number (integer only), string, boolean, null, Cid, or Uint8Array.
|
|
126
|
-
*
|
|
127
|
-
* @param value - The value to check
|
|
128
|
-
* @returns `true` if the value is a valid LexScalar
|
|
129
|
-
*
|
|
130
|
-
* @example
|
|
131
|
-
* ```typescript
|
|
132
|
-
* import { isLexScalar } from '@atproto/lex'
|
|
133
|
-
*
|
|
134
|
-
* isLexScalar('hello') // true
|
|
135
|
-
* isLexScalar(42) // true
|
|
136
|
-
* isLexScalar(3.14) // false (floats not allowed)
|
|
137
|
-
* isLexScalar([1, 2]) // false (arrays are not scalars)
|
|
138
|
-
* ```
|
|
139
|
-
*/
|
|
140
|
-
export function isLexScalar(value: unknown): value is LexScalar {
|
|
141
|
-
switch (typeof value) {
|
|
142
|
-
case 'object':
|
|
143
|
-
return value === null || value instanceof Uint8Array || isCid(value)
|
|
144
|
-
case 'string':
|
|
145
|
-
case 'boolean':
|
|
146
|
-
return true
|
|
147
|
-
case 'number':
|
|
148
|
-
if (Number.isInteger(value)) return true
|
|
149
|
-
// fallthrough
|
|
150
|
-
default:
|
|
151
|
-
return false
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Type guard to check if a value is a valid {@link LexValue}.
|
|
157
|
-
*
|
|
158
|
-
* Performs a deep check to validate that the value (and all nested values)
|
|
159
|
-
* conform to the Lexicon data model. This includes checking for:
|
|
160
|
-
* - Valid scalar types (number, string, boolean, null, Cid, Uint8Array)
|
|
161
|
-
* - Arrays containing only valid LexValues
|
|
162
|
-
* - Plain objects with string keys and valid LexValue values
|
|
163
|
-
* - No cyclic references (which cannot be serialized to JSON or CBOR)
|
|
164
|
-
*
|
|
165
|
-
* @param value - The value to check
|
|
166
|
-
* @returns `true` if the value is a valid LexValue
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* ```typescript
|
|
170
|
-
* import { isLexValue } from '@atproto/lex'
|
|
171
|
-
*
|
|
172
|
-
* isLexValue({ name: 'Alice', tags: ['admin'] }) // true
|
|
173
|
-
* isLexValue(new Date()) // false (not a plain object)
|
|
174
|
-
* isLexValue({ fn: () => {} }) // false (functions not allowed)
|
|
175
|
-
* ```
|
|
176
|
-
*/
|
|
177
|
-
export function isLexValue(value: unknown): value is LexValue {
|
|
178
|
-
// Using a stack to avoid recursion depth issues.
|
|
179
|
-
const stack: unknown[] = [value]
|
|
180
|
-
// Cyclic structures are not valid LexValues as they cannot be serialized to
|
|
181
|
-
// JSON or CBOR. This also allows us to avoid infinite loops when traversing
|
|
182
|
-
// the structure.
|
|
183
|
-
const visited = new Set<object>()
|
|
184
|
-
|
|
185
|
-
do {
|
|
186
|
-
const value = stack.pop()!
|
|
187
|
-
|
|
188
|
-
if (isPlainObject(value)) {
|
|
189
|
-
if (visited.has(value)) return false
|
|
190
|
-
visited.add(value)
|
|
191
|
-
stack.push(...Object.values(value))
|
|
192
|
-
} else if (Array.isArray(value)) {
|
|
193
|
-
if (visited.has(value)) return false
|
|
194
|
-
visited.add(value)
|
|
195
|
-
stack.push(...value)
|
|
196
|
-
} else {
|
|
197
|
-
if (!isLexScalar(value)) return false
|
|
198
|
-
}
|
|
199
|
-
} while (stack.length > 0)
|
|
200
|
-
|
|
201
|
-
// Optimization: ease GC's work
|
|
202
|
-
visited.clear()
|
|
203
|
-
|
|
204
|
-
return true
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* A {@link LexMap} with a required `$type` property.
|
|
209
|
-
*
|
|
210
|
-
* Used to represent typed objects in the Lexicon data model, where the
|
|
211
|
-
* `$type` property identifies the Lexicon schema that defines the object's
|
|
212
|
-
* structure.
|
|
213
|
-
*
|
|
214
|
-
* @example
|
|
215
|
-
* ```typescript
|
|
216
|
-
* import type { TypedLexMap } from '@atproto/lex'
|
|
217
|
-
*
|
|
218
|
-
* const post: TypedLexMap = {
|
|
219
|
-
* $type: 'app.bsky.feed.post',
|
|
220
|
-
* text: 'Hello world!',
|
|
221
|
-
* createdAt: '2024-01-01T00:00:00Z'
|
|
222
|
-
* }
|
|
223
|
-
* ```
|
|
224
|
-
*
|
|
225
|
-
* @see {@link isTypedLexMap} to check if a value is a TypedLexMap
|
|
226
|
-
*/
|
|
227
|
-
export type TypedLexMap<T extends string = string> = LexMap & { $type: T }
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Type guard to check if a value is a {@link TypedLexMap}.
|
|
231
|
-
*
|
|
232
|
-
* Returns true if the value is a valid {@link LexMap} with a non-empty
|
|
233
|
-
* `$type` string property.
|
|
234
|
-
*
|
|
235
|
-
* @param value - The LexValue to check
|
|
236
|
-
* @returns `true` if the value is a TypedLexMap
|
|
237
|
-
*
|
|
238
|
-
* @example
|
|
239
|
-
* ```typescript
|
|
240
|
-
* import { isTypedLexMap } from '@atproto/lex'
|
|
241
|
-
*
|
|
242
|
-
* const data = { $type: 'app.bsky.feed.post', text: 'Hello' }
|
|
243
|
-
*
|
|
244
|
-
* if (isTypedLexMap(data)) {
|
|
245
|
-
* console.log(data.$type) // 'app.bsky.feed.post'
|
|
246
|
-
* }
|
|
247
|
-
* ```
|
|
248
|
-
*/
|
|
249
|
-
export function isTypedLexMap(value: LexValue): value is TypedLexMap {
|
|
250
|
-
return (
|
|
251
|
-
isLexMap(value) && typeof value.$type === 'string' && value.$type.length > 0
|
|
252
|
-
)
|
|
253
|
-
}
|
package/src/lib/nodejs-buffer.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
type Encoding = 'utf8' | 'base64' | 'base64url'
|
|
2
|
-
|
|
3
|
-
// Node's buffer module declares this type internally, but referencing it here
|
|
4
|
-
// would couple this file to @types/node. Local copy keeps this module
|
|
5
|
-
// standalone so it compiles in any environment (see tsconfig/isomorphic.json).
|
|
6
|
-
type WithImplicitCoercion<T> = T | { valueOf(): T }
|
|
7
|
-
|
|
8
|
-
interface NodeJSBuffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
|
|
9
|
-
extends Uint8Array<TArrayBuffer> {
|
|
10
|
-
byteLength: number
|
|
11
|
-
toString(encoding?: Encoding): string
|
|
12
|
-
slice(start?: number, end?: number): NodeJSBuffer<ArrayBuffer>
|
|
13
|
-
subarray(start?: number, end?: number): NodeJSBuffer<TArrayBuffer>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface NodeJSBufferConstructor {
|
|
17
|
-
new (input: string, encoding?: Encoding): NodeJSBuffer
|
|
18
|
-
from(
|
|
19
|
-
string: WithImplicitCoercion<string>,
|
|
20
|
-
encoding?: BufferEncoding,
|
|
21
|
-
): NodeJSBuffer<ArrayBuffer>
|
|
22
|
-
from(
|
|
23
|
-
arrayOrString: WithImplicitCoercion<ArrayLike<number> | string>,
|
|
24
|
-
): NodeJSBuffer<ArrayBuffer>
|
|
25
|
-
from<TArrayBuffer extends ArrayBufferLike>(
|
|
26
|
-
arrayBuffer: WithImplicitCoercion<TArrayBuffer>,
|
|
27
|
-
byteOffset?: number,
|
|
28
|
-
length?: number,
|
|
29
|
-
): NodeJSBuffer<TArrayBuffer>
|
|
30
|
-
concat(
|
|
31
|
-
list: readonly Uint8Array[],
|
|
32
|
-
totalLength?: number,
|
|
33
|
-
): NodeJSBuffer<ArrayBuffer>
|
|
34
|
-
byteLength(input: string, encoding?: Encoding): number
|
|
35
|
-
prototype: NodeJSBuffer
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Avoids a direct reference to Node.js Buffer, which might not exist in some
|
|
39
|
-
// environments (e.g. browsers, Deno, Bun) to prevent bundlers from trying to
|
|
40
|
-
// include polyfills.
|
|
41
|
-
const BUFFER = /*#__PURE__*/ (() => 'Bu' + 'f'.repeat(2) + 'er')() as 'Buffer'
|
|
42
|
-
export const NodeJSBuffer: NodeJSBufferConstructor | null =
|
|
43
|
-
(globalThis as any)?.[BUFFER]?.prototype instanceof Uint8Array &&
|
|
44
|
-
'byteLength' in (globalThis as any)[BUFFER]
|
|
45
|
-
? ((globalThis as any)[BUFFER] as NodeJSBufferConstructor)
|
|
46
|
-
: /* v8 ignore next -- @preserve */ null
|
package/src/lib/util.test.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { isUint8, toHexString } from './util.js'
|
|
3
|
-
|
|
4
|
-
describe(toHexString, () => {
|
|
5
|
-
it('converts 0 to 0x00', () => {
|
|
6
|
-
expect(toHexString(0)).toBe('0x00')
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
it('converts single-digit hex values with padding', () => {
|
|
10
|
-
expect(toHexString(1)).toBe('0x01')
|
|
11
|
-
expect(toHexString(15)).toBe('0x0f')
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('converts two-digit hex values without extra padding', () => {
|
|
15
|
-
expect(toHexString(16)).toBe('0x10')
|
|
16
|
-
expect(toHexString(255)).toBe('0xff')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('converts larger numbers', () => {
|
|
20
|
-
expect(toHexString(256)).toBe('0x100')
|
|
21
|
-
expect(toHexString(4096)).toBe('0x1000')
|
|
22
|
-
})
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
describe(isUint8, () => {
|
|
26
|
-
it('returns true for valid uint8 values', () => {
|
|
27
|
-
expect(isUint8(0)).toBe(true)
|
|
28
|
-
expect(isUint8(1)).toBe(true)
|
|
29
|
-
expect(isUint8(127)).toBe(true)
|
|
30
|
-
expect(isUint8(255)).toBe(true)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('returns false for values outside uint8 range', () => {
|
|
34
|
-
expect(isUint8(-1)).toBe(false)
|
|
35
|
-
expect(isUint8(256)).toBe(false)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('returns false for non-integer numbers', () => {
|
|
39
|
-
expect(isUint8(1.5)).toBe(false)
|
|
40
|
-
expect(isUint8(0.1)).toBe(false)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('returns false for non-number types', () => {
|
|
44
|
-
expect(isUint8('0')).toBe(false)
|
|
45
|
-
expect(isUint8(null)).toBe(false)
|
|
46
|
-
expect(isUint8(undefined)).toBe(false)
|
|
47
|
-
expect(isUint8(true)).toBe(false)
|
|
48
|
-
})
|
|
49
|
-
})
|
package/src/lib/util.ts
DELETED
package/src/object.test.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { parseCid } from './cid.js'
|
|
3
|
-
import { isObject, isPlainObject } from './object.js'
|
|
4
|
-
|
|
5
|
-
describe('isObject', () => {
|
|
6
|
-
it('returns true for plain objects', () => {
|
|
7
|
-
expect(isObject({})).toBe(true)
|
|
8
|
-
expect(isObject({ a: 1 })).toBe(true)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('returns true for CIDs', () => {
|
|
12
|
-
const cid = parseCid(
|
|
13
|
-
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
14
|
-
)
|
|
15
|
-
expect(isObject(cid)).toBe(true)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('returns true for class instances', () => {
|
|
19
|
-
class MyClass {}
|
|
20
|
-
expect(isObject(new MyClass())).toBe(true)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('returns true for arrays', () => {
|
|
24
|
-
expect(isObject([])).toBe(true)
|
|
25
|
-
expect(isObject([1, 2, 3])).toBe(true)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('returns false for null', () => {
|
|
29
|
-
expect(isObject(null)).toBe(false)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('returns false for non-objects', () => {
|
|
33
|
-
expect(isObject(42)).toBe(false)
|
|
34
|
-
expect(isObject('string')).toBe(false)
|
|
35
|
-
expect(isObject(undefined)).toBe(false)
|
|
36
|
-
expect(isObject(true)).toBe(false)
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe('isPlainObject', () => {
|
|
41
|
-
it('returns true for plain objects', () => {
|
|
42
|
-
expect(isPlainObject({})).toBe(true)
|
|
43
|
-
expect(isPlainObject({ a: 1 })).toBe(true)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('returns true for objects with null prototype', () => {
|
|
47
|
-
const obj = Object.create(null)
|
|
48
|
-
obj.a = 1
|
|
49
|
-
expect(isPlainObject(obj)).toBe(true)
|
|
50
|
-
expect(isPlainObject({ __proto__: null, foo: 'bar' })).toBe(true)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('returns false for class instances', () => {
|
|
54
|
-
class MyClass {}
|
|
55
|
-
expect(isPlainObject(new MyClass())).toBe(false)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('returns false for CIDs', () => {
|
|
59
|
-
const cid = parseCid(
|
|
60
|
-
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
61
|
-
)
|
|
62
|
-
expect(isPlainObject(cid)).toBe(false)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('returns false for arrays', () => {
|
|
66
|
-
expect(isPlainObject([])).toBe(false)
|
|
67
|
-
expect(isPlainObject([1, 2, 3])).toBe(false)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('returns false for null', () => {
|
|
71
|
-
expect(isPlainObject(null)).toBe(false)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('returns false for non-objects', () => {
|
|
75
|
-
expect(isPlainObject(42)).toBe(false)
|
|
76
|
-
expect(isPlainObject('string')).toBe(false)
|
|
77
|
-
expect(isPlainObject(undefined)).toBe(false)
|
|
78
|
-
expect(isPlainObject(true)).toBe(false)
|
|
79
|
-
})
|
|
80
|
-
})
|