cborg 4.4.1 → 4.5.1
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 +9 -0
- package/README.md +21 -0
- package/bench/README.md +115 -0
- package/bench/bench-comparative.js +133 -0
- package/bench/bench.js +414 -101
- package/bench/fixtures.js +558 -0
- package/bench/index.html +405 -0
- package/cborg.js +2 -1
- package/interface.ts +8 -2
- package/lib/0uint.js +11 -11
- package/lib/1negint.js +4 -4
- package/lib/2bytes.js +5 -5
- package/lib/3string.js +1 -1
- package/lib/4array.js +4 -4
- package/lib/5map.js +4 -4
- package/lib/6tag.js +4 -4
- package/lib/7float.js +10 -10
- package/lib/bl.js +46 -0
- package/lib/encode.js +40 -15
- package/lib/is.js +12 -31
- package/lib/json/encode.js +10 -10
- package/package.json +1 -1
- package/test/test-0uint.js +12 -1
- package/test/test-1negint.js +12 -1
- package/test/test-2bytes.js +11 -1
- package/test/test-3string.js +11 -1
- package/test/test-4array.js +11 -1
- package/test/test-5map.js +11 -3
- package/test/test-6tag.js +19 -1
- package/test/test-7float.js +11 -1
- package/test/test-cbor-vectors.js +13 -2
- package/test/test-encodeInto.js +246 -0
- package/types/cborg.d.ts +2 -1
- package/types/cborg.d.ts.map +1 -1
- package/types/interface.d.ts +7 -2
- package/types/interface.d.ts.map +1 -1
- package/types/lib/0uint.d.ts +6 -6
- package/types/lib/0uint.d.ts.map +1 -1
- package/types/lib/1negint.d.ts +4 -4
- package/types/lib/1negint.d.ts.map +1 -1
- package/types/lib/2bytes.d.ts +3 -3
- package/types/lib/2bytes.d.ts.map +1 -1
- package/types/lib/3string.d.ts +1 -1
- package/types/lib/3string.d.ts.map +1 -1
- package/types/lib/4array.d.ts +3 -3
- package/types/lib/4array.d.ts.map +1 -1
- package/types/lib/5map.d.ts +3 -3
- package/types/lib/5map.d.ts.map +1 -1
- package/types/lib/6tag.d.ts +4 -4
- package/types/lib/6tag.d.ts.map +1 -1
- package/types/lib/7float.d.ts +3 -3
- package/types/lib/7float.d.ts.map +1 -1
- package/types/lib/bl.d.ts +25 -0
- package/types/lib/bl.d.ts.map +1 -1
- package/types/lib/encode.d.ts +12 -1
- package/types/lib/encode.d.ts.map +1 -1
- package/types/lib/is.d.ts.map +1 -1
- package/types/lib/json/encode.d.ts +1 -1
- package/types/lib/json/encode.d.ts.map +1 -1
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic fixture generators for cborg benchmarks.
|
|
3
|
+
* Generates realistic IPLD/CBOR data shapes for both Filecoin-like (bytes-heavy)
|
|
4
|
+
* and Bluesky-like (string-heavy) workloads.
|
|
5
|
+
*
|
|
6
|
+
* Uses seeded PRNG for reproducibility across runs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* BenchCID - A lightweight CID-like class for benchmarking.
|
|
11
|
+
*
|
|
12
|
+
* This mimics the structure of real CIDs from multiformats so that
|
|
13
|
+
* cidEncoder's fast-path detection works correctly:
|
|
14
|
+
* - obj.asCID === obj (self-reference check)
|
|
15
|
+
* - obj['/'] === obj.bytes (legacy check)
|
|
16
|
+
*
|
|
17
|
+
* This allows benchmarks to capture the real overhead of CID encoding/decoding
|
|
18
|
+
* without depending on the multiformats package.
|
|
19
|
+
*/
|
|
20
|
+
export class BenchCID {
|
|
21
|
+
/**
|
|
22
|
+
* @param {Uint8Array} bytes - The raw CID bytes (36 bytes for CIDv1 + sha256)
|
|
23
|
+
*/
|
|
24
|
+
constructor (bytes) {
|
|
25
|
+
this.bytes = bytes
|
|
26
|
+
// Self-reference for CID detection (matches real CID behavior)
|
|
27
|
+
this.asCID = this
|
|
28
|
+
// Legacy IPLD detection path
|
|
29
|
+
this['/'] = bytes
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Mulberry32 - simple seeded PRNG
|
|
34
|
+
function mulberry32 (seed) {
|
|
35
|
+
return function () {
|
|
36
|
+
let t = seed += 0x6D2B79F5
|
|
37
|
+
t = Math.imul(t ^ t >>> 15, t | 1)
|
|
38
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61)
|
|
39
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Random utilities built on seeded PRNG
|
|
44
|
+
function createRandom (seed) {
|
|
45
|
+
const rand = mulberry32(seed)
|
|
46
|
+
|
|
47
|
+
const randInt = (min, max) => {
|
|
48
|
+
if (max === undefined) {
|
|
49
|
+
max = min
|
|
50
|
+
min = 0
|
|
51
|
+
}
|
|
52
|
+
return Math.floor(rand() * (max - min)) + min
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const randBytes = (len) => {
|
|
56
|
+
const b = new Uint8Array(len)
|
|
57
|
+
for (let i = 0; i < len; i++) b[i] = randInt(256)
|
|
58
|
+
return b
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const randBool = () => rand() > 0.5
|
|
62
|
+
|
|
63
|
+
const pick = (arr) => arr[randInt(arr.length)]
|
|
64
|
+
|
|
65
|
+
// Generate random string of given length from charset
|
|
66
|
+
const randString = (len, charset = 'abcdefghijklmnopqrstuvwxyz0123456789') => {
|
|
67
|
+
let s = ''
|
|
68
|
+
for (let i = 0; i < len; i++) s += charset[randInt(charset.length)]
|
|
69
|
+
return s
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { rand, randInt, randBytes, randBool, pick, randString }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Common character sets
|
|
76
|
+
const ALPHA_LOWER = 'abcdefghijklmnopqrstuvwxyz'
|
|
77
|
+
const ALPHA_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
78
|
+
const DIGITS = '0123456789'
|
|
79
|
+
const ALPHANUMERIC = ALPHA_LOWER + DIGITS
|
|
80
|
+
const BASE32 = 'abcdefghijklmnopqrstuvwxyz234567'
|
|
81
|
+
|
|
82
|
+
// Emoji set for realistic post content
|
|
83
|
+
const EMOJI = ['😀', '😂', '🎉', '❤️', '🔥', '👍', '🚀', '✨', '💯', '🙌', '👀', '💪', '🤔', '😎', '🌟']
|
|
84
|
+
|
|
85
|
+
// Words for generating realistic-ish text
|
|
86
|
+
const WORDS = [
|
|
87
|
+
'the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'I',
|
|
88
|
+
'it', 'for', 'not', 'on', 'with', 'he', 'as', 'you', 'do', 'at',
|
|
89
|
+
'this', 'but', 'his', 'by', 'from', 'they', 'we', 'say', 'her', 'she',
|
|
90
|
+
'or', 'an', 'will', 'my', 'one', 'all', 'would', 'there', 'their', 'what',
|
|
91
|
+
'so', 'up', 'out', 'if', 'about', 'who', 'get', 'which', 'go', 'me',
|
|
92
|
+
'just', 'know', 'take', 'people', 'into', 'year', 'your', 'good', 'some', 'could',
|
|
93
|
+
'them', 'see', 'other', 'than', 'then', 'now', 'look', 'only', 'come', 'its',
|
|
94
|
+
'think', 'also', 'back', 'after', 'use', 'how', 'our', 'work', 'first', 'well',
|
|
95
|
+
'new', 'want', 'because', 'any', 'these', 'give', 'day', 'most', 'us', 'great'
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generate a fake CID as a BenchCID instance (36 bytes for CIDv1 + sha256)
|
|
100
|
+
* @param {ReturnType<typeof createRandom>} r
|
|
101
|
+
* @returns {BenchCID}
|
|
102
|
+
*/
|
|
103
|
+
function generateCID (r) {
|
|
104
|
+
const bytes = new Uint8Array(36)
|
|
105
|
+
bytes[0] = 0x01 // CIDv1
|
|
106
|
+
bytes[1] = 0x71 // dag-cbor codec
|
|
107
|
+
bytes[2] = 0x12 // sha2-256
|
|
108
|
+
bytes[3] = 0x20 // 32 bytes digest
|
|
109
|
+
const digest = r.randBytes(32)
|
|
110
|
+
bytes.set(digest, 4)
|
|
111
|
+
return new BenchCID(bytes)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate a DID (did:plc:xxxx format)
|
|
116
|
+
*/
|
|
117
|
+
function generateDID (r) {
|
|
118
|
+
return `did:plc:${r.randString(24, BASE32)}`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Generate a handle (user.bsky.social format)
|
|
123
|
+
*/
|
|
124
|
+
function generateHandle (r) {
|
|
125
|
+
const username = r.randString(r.randInt(4, 16), ALPHA_LOWER + DIGITS)
|
|
126
|
+
const domains = ['bsky.social', 'bsky.app', 'example.com', 'test.dev']
|
|
127
|
+
return `${username}.${r.pick(domains)}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Generate an AT-URI
|
|
132
|
+
*/
|
|
133
|
+
function generateATUri (r, did, collection, rkey) {
|
|
134
|
+
did = did || generateDID(r)
|
|
135
|
+
collection = collection || r.pick([
|
|
136
|
+
'app.bsky.feed.post',
|
|
137
|
+
'app.bsky.feed.like',
|
|
138
|
+
'app.bsky.graph.follow',
|
|
139
|
+
'app.bsky.feed.repost'
|
|
140
|
+
])
|
|
141
|
+
rkey = rkey || r.randString(13, ALPHANUMERIC)
|
|
142
|
+
return `at://${did}/${collection}/${rkey}`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Generate an ISO 8601 timestamp
|
|
147
|
+
*/
|
|
148
|
+
function generateTimestamp (r) {
|
|
149
|
+
const year = r.randInt(2020, 2025)
|
|
150
|
+
const month = String(r.randInt(1, 13)).padStart(2, '0')
|
|
151
|
+
const day = String(r.randInt(1, 29)).padStart(2, '0')
|
|
152
|
+
const hour = String(r.randInt(0, 24)).padStart(2, '0')
|
|
153
|
+
const minute = String(r.randInt(0, 60)).padStart(2, '0')
|
|
154
|
+
const second = String(r.randInt(0, 60)).padStart(2, '0')
|
|
155
|
+
const ms = String(r.randInt(0, 1000)).padStart(3, '0')
|
|
156
|
+
return `${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}Z`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generate realistic post text with occasional emoji
|
|
161
|
+
*/
|
|
162
|
+
function generatePostText (r, minLen = 20, maxLen = 280) {
|
|
163
|
+
const targetLen = r.randInt(minLen, maxLen)
|
|
164
|
+
const words = []
|
|
165
|
+
let len = 0
|
|
166
|
+
|
|
167
|
+
while (len < targetLen) {
|
|
168
|
+
// Occasionally add emoji
|
|
169
|
+
if (r.rand() < 0.05) {
|
|
170
|
+
words.push(r.pick(EMOJI))
|
|
171
|
+
len += 2
|
|
172
|
+
} else {
|
|
173
|
+
const word = r.pick(WORDS)
|
|
174
|
+
words.push(word)
|
|
175
|
+
len += word.length + 1
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Capitalize first word
|
|
180
|
+
let text = words.join(' ')
|
|
181
|
+
text = text.charAt(0).toUpperCase() + text.slice(1)
|
|
182
|
+
|
|
183
|
+
// Occasionally add punctuation
|
|
184
|
+
if (r.rand() < 0.3) text += '!'
|
|
185
|
+
else if (r.rand() < 0.2) text += '?'
|
|
186
|
+
else text += '.'
|
|
187
|
+
|
|
188
|
+
return text.slice(0, maxLen)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// =============================================================================
|
|
192
|
+
// Bluesky-like fixtures (string-heavy)
|
|
193
|
+
// =============================================================================
|
|
194
|
+
|
|
195
|
+
function generateBskyPost (r) {
|
|
196
|
+
const did = generateDID(r)
|
|
197
|
+
const hasReply = r.rand() < 0.3
|
|
198
|
+
const hasEmbed = r.rand() < 0.2
|
|
199
|
+
const hasLangs = r.rand() < 0.8
|
|
200
|
+
|
|
201
|
+
const post = {
|
|
202
|
+
$type: 'app.bsky.feed.post',
|
|
203
|
+
text: generatePostText(r),
|
|
204
|
+
createdAt: generateTimestamp(r)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (hasLangs) {
|
|
208
|
+
post.langs = [r.pick(['en', 'ja', 'pt', 'es', 'de', 'fr'])]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (hasReply) {
|
|
212
|
+
const rootDid = generateDID(r)
|
|
213
|
+
const parentDid = r.rand() < 0.5 ? rootDid : generateDID(r)
|
|
214
|
+
post.reply = {
|
|
215
|
+
root: {
|
|
216
|
+
cid: generateCID(r),
|
|
217
|
+
uri: generateATUri(r, rootDid, 'app.bsky.feed.post')
|
|
218
|
+
},
|
|
219
|
+
parent: {
|
|
220
|
+
cid: generateCID(r),
|
|
221
|
+
uri: generateATUri(r, parentDid, 'app.bsky.feed.post')
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (hasEmbed) {
|
|
227
|
+
// Simple external embed
|
|
228
|
+
post.embed = {
|
|
229
|
+
$type: 'app.bsky.embed.external',
|
|
230
|
+
external: {
|
|
231
|
+
uri: `https://example.com/${r.randString(20, ALPHANUMERIC)}`,
|
|
232
|
+
title: generatePostText(r, 10, 50),
|
|
233
|
+
description: generatePostText(r, 20, 100)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return post
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function generateBskyFollow (r) {
|
|
242
|
+
return {
|
|
243
|
+
$type: 'app.bsky.graph.follow',
|
|
244
|
+
subject: generateDID(r),
|
|
245
|
+
createdAt: generateTimestamp(r)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function generateBskyLike (r) {
|
|
250
|
+
return {
|
|
251
|
+
$type: 'app.bsky.feed.like',
|
|
252
|
+
subject: {
|
|
253
|
+
cid: generateCID(r),
|
|
254
|
+
uri: generateATUri(r, null, 'app.bsky.feed.post')
|
|
255
|
+
},
|
|
256
|
+
createdAt: generateTimestamp(r)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function generateBskyRepost (r) {
|
|
261
|
+
return {
|
|
262
|
+
$type: 'app.bsky.feed.repost',
|
|
263
|
+
subject: {
|
|
264
|
+
cid: generateCID(r),
|
|
265
|
+
uri: generateATUri(r, null, 'app.bsky.feed.post')
|
|
266
|
+
},
|
|
267
|
+
createdAt: generateTimestamp(r)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function generateBskyProfile (r) {
|
|
272
|
+
return {
|
|
273
|
+
$type: 'app.bsky.actor.profile',
|
|
274
|
+
displayName: r.randString(r.randInt(5, 25), ALPHA_LOWER + ALPHA_UPPER + ' '),
|
|
275
|
+
description: generatePostText(r, 50, 200),
|
|
276
|
+
avatar: r.randBool() ? { cid: generateCID(r), mimeType: 'image/jpeg' } : undefined,
|
|
277
|
+
banner: r.randBool() ? { cid: generateCID(r), mimeType: 'image/jpeg' } : undefined
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Generate an MST (Merkle Search Tree) node - Bluesky's HAMT variant
|
|
283
|
+
*/
|
|
284
|
+
function generateMSTNode (r, entryCount) {
|
|
285
|
+
entryCount = entryCount || r.randInt(4, 32)
|
|
286
|
+
const hasLeft = r.rand() < 0.5
|
|
287
|
+
|
|
288
|
+
const entries = []
|
|
289
|
+
for (let i = 0; i < entryCount; i++) {
|
|
290
|
+
entries.push({
|
|
291
|
+
k: r.randBytes(r.randInt(8, 32)), // key bytes
|
|
292
|
+
v: generateCID(r), // value CID
|
|
293
|
+
p: r.randInt(0, 64), // prefix count
|
|
294
|
+
t: r.rand() < 0.3 ? generateCID(r) : null // optional subtree
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
l: hasLeft ? generateCID(r) : null,
|
|
300
|
+
e: entries
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// =============================================================================
|
|
305
|
+
// Filecoin-like fixtures (bytes-heavy)
|
|
306
|
+
// =============================================================================
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Generate a Filecoin-like address (f1/f3 style as bytes)
|
|
310
|
+
*/
|
|
311
|
+
function generateFilAddress (r) {
|
|
312
|
+
// f1 addresses are 21 bytes, f3 are 49 bytes
|
|
313
|
+
return r.rand() < 0.7 ? r.randBytes(21) : r.randBytes(49)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Generate a Filecoin-like message
|
|
318
|
+
*/
|
|
319
|
+
function generateFilMessage (r) {
|
|
320
|
+
return {
|
|
321
|
+
Version: 0,
|
|
322
|
+
To: generateFilAddress(r),
|
|
323
|
+
From: generateFilAddress(r),
|
|
324
|
+
Nonce: r.randInt(0, 10000000),
|
|
325
|
+
Value: r.randBytes(r.randInt(1, 16)), // BigInt as bytes
|
|
326
|
+
GasLimit: r.randInt(0, 10000000000),
|
|
327
|
+
GasFeeCap: r.randBytes(r.randInt(1, 12)),
|
|
328
|
+
GasPremium: r.randBytes(r.randInt(1, 12)),
|
|
329
|
+
Method: r.randInt(0, 30),
|
|
330
|
+
Params: r.randBytes(r.randInt(0, 1024))
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Generate a Filecoin-like block header
|
|
336
|
+
*/
|
|
337
|
+
function generateFilBlockHeader (r) {
|
|
338
|
+
const parentCount = r.randInt(1, 6)
|
|
339
|
+
return {
|
|
340
|
+
Miner: generateFilAddress(r),
|
|
341
|
+
Ticket: {
|
|
342
|
+
VRFProof: r.randBytes(96)
|
|
343
|
+
},
|
|
344
|
+
ElectionProof: {
|
|
345
|
+
WinCount: r.randInt(1, 10),
|
|
346
|
+
VRFProof: r.randBytes(96)
|
|
347
|
+
},
|
|
348
|
+
BeaconEntries: Array(r.randInt(1, 3)).fill(0).map(() => ({
|
|
349
|
+
Round: r.randInt(1000000, 9999999),
|
|
350
|
+
Data: r.randBytes(96)
|
|
351
|
+
})),
|
|
352
|
+
WinPoStProof: Array(r.randInt(1, 2)).fill(0).map(() => ({
|
|
353
|
+
PoStProof: r.randInt(0, 10),
|
|
354
|
+
ProofBytes: r.randBytes(192)
|
|
355
|
+
})),
|
|
356
|
+
Parents: Array(parentCount).fill(0).map(() => generateCID(r)),
|
|
357
|
+
ParentWeight: r.randBytes(r.randInt(4, 16)),
|
|
358
|
+
Height: r.randInt(1000000, 9999999),
|
|
359
|
+
ParentStateRoot: generateCID(r),
|
|
360
|
+
ParentMessageReceipts: generateCID(r),
|
|
361
|
+
Messages: generateCID(r),
|
|
362
|
+
BLSAggregate: {
|
|
363
|
+
Type: 2,
|
|
364
|
+
Data: r.randBytes(96)
|
|
365
|
+
},
|
|
366
|
+
Timestamp: r.randInt(1600000000, 1800000000),
|
|
367
|
+
BlockSig: {
|
|
368
|
+
Type: 2,
|
|
369
|
+
Data: r.randBytes(96)
|
|
370
|
+
},
|
|
371
|
+
ForkSignaling: 0,
|
|
372
|
+
ParentBaseFee: r.randBytes(r.randInt(4, 12))
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Generate a HAMT node (Filecoin-style)
|
|
378
|
+
*/
|
|
379
|
+
function generateHAMTNode (r, width) {
|
|
380
|
+
width = width || r.randInt(8, 32)
|
|
381
|
+
return {
|
|
382
|
+
bitfield: r.randBytes(32),
|
|
383
|
+
pointers: Array(width).fill(0).map(() =>
|
|
384
|
+
r.rand() < 0.6
|
|
385
|
+
? generateCID(r) // link to child node
|
|
386
|
+
: [{ key: r.randBytes(32), value: generateCID(r) }] // bucket entry
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Generate an AMT (Array Mapped Trie) node
|
|
393
|
+
*/
|
|
394
|
+
function generateAMTNode (r, width) {
|
|
395
|
+
width = width || r.randInt(4, 16)
|
|
396
|
+
return {
|
|
397
|
+
height: r.randInt(0, 5),
|
|
398
|
+
count: r.randInt(1, 10000),
|
|
399
|
+
node: {
|
|
400
|
+
bmap: r.randBytes(8),
|
|
401
|
+
links: Array(width).fill(0).map(() => generateCID(r)),
|
|
402
|
+
values: Array(r.randInt(0, 4)).fill(0).map(() => r.randBytes(r.randInt(32, 256)))
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Generate array of CIDs (common pattern - Parents[], Links[], etc.)
|
|
409
|
+
*/
|
|
410
|
+
function generateCIDArray (r, count) {
|
|
411
|
+
count = count || r.randInt(5, 50)
|
|
412
|
+
return Array(count).fill(0).map(() => generateCID(r))
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// =============================================================================
|
|
416
|
+
// Micro-benchmark fixtures (isolated patterns)
|
|
417
|
+
// =============================================================================
|
|
418
|
+
|
|
419
|
+
function generateMapWithKeys (r, keyCount, keyLen = 10) {
|
|
420
|
+
const obj = {}
|
|
421
|
+
for (let i = 0; i < keyCount; i++) {
|
|
422
|
+
obj[r.randString(keyLen, ALPHANUMERIC)] = r.randInt(0, 1000000)
|
|
423
|
+
}
|
|
424
|
+
return obj
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function generateNestedObject (r, depth, breadth = 3) {
|
|
428
|
+
if (depth === 0) {
|
|
429
|
+
return r.randInt(0, 1000000)
|
|
430
|
+
}
|
|
431
|
+
const obj = {}
|
|
432
|
+
for (let i = 0; i < breadth; i++) {
|
|
433
|
+
obj[`key${i}`] = generateNestedObject(r, depth - 1, breadth)
|
|
434
|
+
}
|
|
435
|
+
return obj
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function generateStringArray (r, count, minLen, maxLen) {
|
|
439
|
+
return Array(count).fill(0).map(() =>
|
|
440
|
+
r.randString(r.randInt(minLen, maxLen), ALPHANUMERIC + ' ')
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function generateIntegerArray (r, count, max) {
|
|
445
|
+
return Array(count).fill(0).map(() => r.randInt(0, max))
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function generateBytesArray (r, count, minLen, maxLen) {
|
|
449
|
+
return Array(count).fill(0).map(() => r.randBytes(r.randInt(minLen, maxLen)))
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// =============================================================================
|
|
453
|
+
// Main fixture generator
|
|
454
|
+
// =============================================================================
|
|
455
|
+
|
|
456
|
+
export function generateFixtures (seed = 12345, counts = {}) {
|
|
457
|
+
const r = createRandom(seed)
|
|
458
|
+
|
|
459
|
+
// Default counts for each fixture type
|
|
460
|
+
const c = Object.assign({
|
|
461
|
+
// Bluesky-like
|
|
462
|
+
bskyPosts: 200,
|
|
463
|
+
bskyFollows: 200,
|
|
464
|
+
bskyLikes: 200,
|
|
465
|
+
bskyReposts: 200,
|
|
466
|
+
bskyProfiles: 100,
|
|
467
|
+
mstNodes: 200,
|
|
468
|
+
|
|
469
|
+
// Filecoin-like
|
|
470
|
+
filMessages: 200,
|
|
471
|
+
filBlockHeaders: 50,
|
|
472
|
+
hamtNodes: 200,
|
|
473
|
+
amtNodes: 200,
|
|
474
|
+
cidArrays: 200,
|
|
475
|
+
|
|
476
|
+
// Micro-benchmarks
|
|
477
|
+
mapsSmall: 200, // 10 keys
|
|
478
|
+
mapsMedium: 100, // 50 keys
|
|
479
|
+
mapsLarge: 50, // 200 keys
|
|
480
|
+
nestedShallow: 100, // depth 3
|
|
481
|
+
nestedDeep: 50, // depth 10
|
|
482
|
+
stringsShort: 200, // arrays of short strings
|
|
483
|
+
stringsMedium: 200, // arrays of medium strings
|
|
484
|
+
stringsLong: 100, // arrays of long strings
|
|
485
|
+
integersSmall: 200, // 0-23 (single byte)
|
|
486
|
+
integersMedium: 200, // 0-65535
|
|
487
|
+
integersLarge: 200, // full 64-bit range
|
|
488
|
+
bytesSmall: 200, // <64 bytes
|
|
489
|
+
bytesMedium: 200, // 64-512 bytes
|
|
490
|
+
bytesLarge: 100 // 1KB+
|
|
491
|
+
}, counts)
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
// Bluesky-like (string-heavy)
|
|
495
|
+
bsky: {
|
|
496
|
+
posts: Array(c.bskyPosts).fill(0).map(() => generateBskyPost(r)),
|
|
497
|
+
follows: Array(c.bskyFollows).fill(0).map(() => generateBskyFollow(r)),
|
|
498
|
+
likes: Array(c.bskyLikes).fill(0).map(() => generateBskyLike(r)),
|
|
499
|
+
reposts: Array(c.bskyReposts).fill(0).map(() => generateBskyRepost(r)),
|
|
500
|
+
profiles: Array(c.bskyProfiles).fill(0).map(() => generateBskyProfile(r)),
|
|
501
|
+
mstNodes: Array(c.mstNodes).fill(0).map(() => generateMSTNode(r))
|
|
502
|
+
},
|
|
503
|
+
|
|
504
|
+
// Filecoin-like (bytes-heavy)
|
|
505
|
+
filecoin: {
|
|
506
|
+
messages: Array(c.filMessages).fill(0).map(() => generateFilMessage(r)),
|
|
507
|
+
blockHeaders: Array(c.filBlockHeaders).fill(0).map(() => generateFilBlockHeader(r)),
|
|
508
|
+
hamtNodes: Array(c.hamtNodes).fill(0).map(() => generateHAMTNode(r)),
|
|
509
|
+
amtNodes: Array(c.amtNodes).fill(0).map(() => generateAMTNode(r)),
|
|
510
|
+
cidArrays: Array(c.cidArrays).fill(0).map(() => generateCIDArray(r))
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
// Micro-benchmarks (isolated patterns)
|
|
514
|
+
micro: {
|
|
515
|
+
mapsSmall: Array(c.mapsSmall).fill(0).map(() => generateMapWithKeys(r, 10)),
|
|
516
|
+
mapsMedium: Array(c.mapsMedium).fill(0).map(() => generateMapWithKeys(r, 50)),
|
|
517
|
+
mapsLarge: Array(c.mapsLarge).fill(0).map(() => generateMapWithKeys(r, 200)),
|
|
518
|
+
nestedShallow: Array(c.nestedShallow).fill(0).map(() => generateNestedObject(r, 3, 5)),
|
|
519
|
+
nestedDeep: Array(c.nestedDeep).fill(0).map(() => generateNestedObject(r, 10, 2)),
|
|
520
|
+
stringsShort: Array(c.stringsShort).fill(0).map(() => generateStringArray(r, 50, 5, 20)),
|
|
521
|
+
stringsMedium: Array(c.stringsMedium).fill(0).map(() => generateStringArray(r, 30, 20, 100)),
|
|
522
|
+
stringsLong: Array(c.stringsLong).fill(0).map(() => generateStringArray(r, 10, 100, 500)),
|
|
523
|
+
integersSmall: Array(c.integersSmall).fill(0).map(() => generateIntegerArray(r, 100, 23)),
|
|
524
|
+
integersMedium: Array(c.integersMedium).fill(0).map(() => generateIntegerArray(r, 100, 65535)),
|
|
525
|
+
integersLarge: Array(c.integersLarge).fill(0).map(() => generateIntegerArray(r, 100, Number.MAX_SAFE_INTEGER)),
|
|
526
|
+
bytesSmall: Array(c.bytesSmall).fill(0).map(() => generateBytesArray(r, 20, 8, 64)),
|
|
527
|
+
bytesMedium: Array(c.bytesMedium).fill(0).map(() => generateBytesArray(r, 10, 64, 512)),
|
|
528
|
+
bytesLarge: Array(c.bytesLarge).fill(0).map(() => generateBytesArray(r, 5, 1024, 4096))
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export {
|
|
534
|
+
createRandom,
|
|
535
|
+
generateCID,
|
|
536
|
+
generateDID,
|
|
537
|
+
generateHandle,
|
|
538
|
+
generateATUri,
|
|
539
|
+
generateTimestamp,
|
|
540
|
+
generatePostText,
|
|
541
|
+
generateBskyPost,
|
|
542
|
+
generateBskyFollow,
|
|
543
|
+
generateBskyLike,
|
|
544
|
+
generateBskyRepost,
|
|
545
|
+
generateBskyProfile,
|
|
546
|
+
generateMSTNode,
|
|
547
|
+
generateFilAddress,
|
|
548
|
+
generateFilMessage,
|
|
549
|
+
generateFilBlockHeader,
|
|
550
|
+
generateHAMTNode,
|
|
551
|
+
generateAMTNode,
|
|
552
|
+
generateCIDArray,
|
|
553
|
+
generateMapWithKeys,
|
|
554
|
+
generateNestedObject,
|
|
555
|
+
generateStringArray,
|
|
556
|
+
generateIntegerArray,
|
|
557
|
+
generateBytesArray
|
|
558
|
+
}
|