wao 0.27.2 → 0.27.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/cjs/collect-body-keys.js +430 -0
- package/cjs/encode-array-item.js +110 -0
- package/cjs/encode-utils.js +241 -0
- package/cjs/encode.js +1183 -1541
- package/esm/collect-body-keys.js +401 -0
- package/esm/encode-array-item.js +112 -0
- package/esm/encode-utils.js +185 -0
- package/esm/encode.js +880 -1207
- package/package.json +1 -1
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasNonAscii,
|
|
3
|
+
sha256,
|
|
4
|
+
hasNewline,
|
|
5
|
+
isBytes,
|
|
6
|
+
isPojo,
|
|
7
|
+
} from "./encode-utils.js"
|
|
8
|
+
|
|
9
|
+
// Helper functions
|
|
10
|
+
const isEmpty = value => {
|
|
11
|
+
if (typeof value === "string") return value === ""
|
|
12
|
+
if (Array.isArray(value)) return value.length === 0
|
|
13
|
+
if (isPojo(value)) return Object.keys(value).length === 0
|
|
14
|
+
if (isBytes(value)) return value.length === 0 || value.byteLength === 0
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const hasOnlyEmptyValues = obj => {
|
|
19
|
+
if (!isPojo(obj)) return false
|
|
20
|
+
return Object.values(obj).every(
|
|
21
|
+
v =>
|
|
22
|
+
(typeof v === "string" && v === "") ||
|
|
23
|
+
(Array.isArray(v) && v.length === 0) ||
|
|
24
|
+
(isPojo(v) && Object.keys(v).length === 0)
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isSimpleValue = value => {
|
|
29
|
+
return (
|
|
30
|
+
typeof value === "string" ||
|
|
31
|
+
typeof value === "number" ||
|
|
32
|
+
typeof value === "boolean" ||
|
|
33
|
+
value === null ||
|
|
34
|
+
value === undefined ||
|
|
35
|
+
typeof value === "symbol"
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const getValueByPath = (obj, path) => {
|
|
40
|
+
const parts = path.split("/")
|
|
41
|
+
let value = obj
|
|
42
|
+
for (const part of parts) {
|
|
43
|
+
if (/^\d+$/.test(part)) {
|
|
44
|
+
value = value[parseInt(part) - 1]
|
|
45
|
+
} else {
|
|
46
|
+
value = value[part]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return value
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Array analysis helper
|
|
53
|
+
const analyzeArray = array => {
|
|
54
|
+
const analysis = {
|
|
55
|
+
hasObjects: false,
|
|
56
|
+
hasArrays: false,
|
|
57
|
+
hasNonObjects: false,
|
|
58
|
+
hasArraysOfObjects: false,
|
|
59
|
+
hasEmptyStrings: false,
|
|
60
|
+
hasEmptyObjects: false,
|
|
61
|
+
hasNonEmptyObjects: false,
|
|
62
|
+
hasObjectsWithOnlyEmptyValues: false,
|
|
63
|
+
hasOnlyEmptyElements: true,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const item of array) {
|
|
67
|
+
if (isPojo(item)) {
|
|
68
|
+
analysis.hasObjects = true
|
|
69
|
+
if (Object.keys(item).length === 0) {
|
|
70
|
+
analysis.hasEmptyObjects = true
|
|
71
|
+
} else {
|
|
72
|
+
analysis.hasNonEmptyObjects = true
|
|
73
|
+
if (hasOnlyEmptyValues(item)) {
|
|
74
|
+
analysis.hasObjectsWithOnlyEmptyValues = true
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else if (Array.isArray(item)) {
|
|
78
|
+
analysis.hasArrays = true
|
|
79
|
+
if (item.some(subItem => isPojo(subItem))) {
|
|
80
|
+
analysis.hasArraysOfObjects = true
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
analysis.hasNonObjects = true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (typeof item === "string" && item === "") {
|
|
87
|
+
analysis.hasEmptyStrings = true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!isEmpty(item)) {
|
|
91
|
+
analysis.hasOnlyEmptyElements = false
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return analysis
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Body key collector class
|
|
99
|
+
class BodyKeyCollector {
|
|
100
|
+
constructor(obj) {
|
|
101
|
+
this.obj = obj
|
|
102
|
+
this.keys = []
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
collect() {
|
|
106
|
+
this.processRootObject()
|
|
107
|
+
return this.deduplicateAndFilter()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Process top-level object
|
|
111
|
+
processRootObject() {
|
|
112
|
+
const objKeys = Object.keys(this.obj)
|
|
113
|
+
|
|
114
|
+
for (const [key, value] of Object.entries(this.obj)) {
|
|
115
|
+
if (this.isSpecialDataBodyField(key, value, objKeys)) {
|
|
116
|
+
this.keys.push(key)
|
|
117
|
+
} else if (Array.isArray(value) && value.length > 0) {
|
|
118
|
+
this.processRootArray(key, value)
|
|
119
|
+
} else if (isPojo(value)) {
|
|
120
|
+
this.processRootNestedObject(key, value)
|
|
121
|
+
} else if (this.needsBodyKey(key, value)) {
|
|
122
|
+
this.keys.push(key)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if field is special data/body field
|
|
128
|
+
isSpecialDataBodyField(key, value, objKeys) {
|
|
129
|
+
if (
|
|
130
|
+
(key === "data" || key === "body") &&
|
|
131
|
+
isSimpleValue(value) &&
|
|
132
|
+
objKeys.length > 1
|
|
133
|
+
) {
|
|
134
|
+
const otherKey = key === "data" ? "body" : "data"
|
|
135
|
+
const otherValue = this.obj[otherKey]
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
otherValue &&
|
|
139
|
+
isPojo(otherValue) &&
|
|
140
|
+
Object.keys(otherValue).length > 0
|
|
141
|
+
) {
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if value needs a body key
|
|
150
|
+
needsBodyKey(key, value) {
|
|
151
|
+
return (
|
|
152
|
+
(isBytes(value) && value.length > 0) ||
|
|
153
|
+
(typeof value === "string" && value.includes("\n")) ||
|
|
154
|
+
(typeof value === "string" && hasNonAscii(value))
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Process root-level arrays
|
|
159
|
+
processRootArray(key, array) {
|
|
160
|
+
const analysis = analyzeArray(array)
|
|
161
|
+
let bodyPartCounter = 1
|
|
162
|
+
|
|
163
|
+
if (analysis.hasArraysOfObjects) {
|
|
164
|
+
// Handle arrays of arrays containing objects
|
|
165
|
+
array.forEach((item, index) => {
|
|
166
|
+
if (Array.isArray(item)) {
|
|
167
|
+
item.forEach((subItem, subIndex) => {
|
|
168
|
+
if (isPojo(subItem)) {
|
|
169
|
+
this.keys.push(`${key}/${index + 1}/${subIndex + 1}`)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
this.keys.push(key)
|
|
175
|
+
} else if (
|
|
176
|
+
analysis.hasObjects &&
|
|
177
|
+
(analysis.hasEmptyStrings || analysis.hasEmptyObjects) &&
|
|
178
|
+
!analysis.hasObjectsWithOnlyEmptyValues
|
|
179
|
+
) {
|
|
180
|
+
// Mixed array: only non-empty objects get parts
|
|
181
|
+
array.forEach(item => {
|
|
182
|
+
if (isPojo(item) && Object.keys(item).length > 0) {
|
|
183
|
+
const path = `${key}/${bodyPartCounter}`
|
|
184
|
+
this.keys.push(path)
|
|
185
|
+
this.addNestedObjectPaths(item, path)
|
|
186
|
+
}
|
|
187
|
+
bodyPartCounter++
|
|
188
|
+
})
|
|
189
|
+
this.keys.push(key)
|
|
190
|
+
} else if (analysis.hasObjects) {
|
|
191
|
+
// Regular array with objects
|
|
192
|
+
const skipEmptyObjects = analysis.hasOnlyEmptyElements
|
|
193
|
+
|
|
194
|
+
array.forEach(item => {
|
|
195
|
+
if (isPojo(item)) {
|
|
196
|
+
if (!(skipEmptyObjects && Object.keys(item).length === 0)) {
|
|
197
|
+
const path = `${key}/${bodyPartCounter}`
|
|
198
|
+
this.keys.push(path)
|
|
199
|
+
if (Object.keys(item).length > 0) {
|
|
200
|
+
this.addNestedObjectPaths(item, path)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} else if (typeof item === "string" && item === "") {
|
|
204
|
+
this.keys.push(`${key}/${bodyPartCounter}`)
|
|
205
|
+
}
|
|
206
|
+
bodyPartCounter++
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// Add main array key conditionally
|
|
210
|
+
if (!analysis.hasObjectsWithOnlyEmptyValues || analysis.hasNonObjects) {
|
|
211
|
+
if (!analysis.hasOnlyEmptyElements) {
|
|
212
|
+
this.keys.push(key)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
// Simple array without objects
|
|
217
|
+
const hasOnlyEmptyArraysOrObjects = array.every(
|
|
218
|
+
item =>
|
|
219
|
+
isEmpty(item) &&
|
|
220
|
+
(Array.isArray(item) || isPojo(item) || typeof item === "string")
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if (hasOnlyEmptyArraysOrObjects || !array.every(isEmpty)) {
|
|
224
|
+
this.keys.push(key)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Add paths for nested objects within an object
|
|
230
|
+
addNestedObjectPaths(obj, basePath) {
|
|
231
|
+
for (const [nestedKey, nestedValue] of Object.entries(obj)) {
|
|
232
|
+
if (isPojo(nestedValue) && Object.keys(nestedValue).length > 0) {
|
|
233
|
+
this.keys.push(`${basePath}/${nestedKey}`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Process root-level nested object
|
|
239
|
+
processRootNestedObject(key, obj) {
|
|
240
|
+
// Check for arrays with only empty elements
|
|
241
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
242
|
+
if (Array.isArray(v) && v.length > 0 && v.every(isEmpty)) {
|
|
243
|
+
this.keys.push(`${key}/${k}`)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Traverse the object
|
|
248
|
+
this.traverse(obj, key)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Traverse nested structures
|
|
252
|
+
traverse(current, path) {
|
|
253
|
+
let hasSimpleFields = false
|
|
254
|
+
const nestedPaths = []
|
|
255
|
+
let hasArraysWithObjects = false
|
|
256
|
+
|
|
257
|
+
// First pass: analyze structure
|
|
258
|
+
for (const [key, value] of Object.entries(current)) {
|
|
259
|
+
const fullPath = path ? `${path}/${key}` : key
|
|
260
|
+
const result = this.analyzeFieldInTraversal(value, fullPath, nestedPaths)
|
|
261
|
+
|
|
262
|
+
hasSimpleFields = hasSimpleFields || result.hasSimpleFields
|
|
263
|
+
hasArraysWithObjects = hasArraysWithObjects || result.hasArraysWithObjects
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Add current path if needed
|
|
267
|
+
if (hasSimpleFields || (hasArraysWithObjects && path)) {
|
|
268
|
+
this.keys.push(path)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Second pass: check for arrays with only empty elements
|
|
272
|
+
for (const [key, value] of Object.entries(current)) {
|
|
273
|
+
const fullPath = path ? `${path}/${key}` : key
|
|
274
|
+
if (Array.isArray(value) && value.length > 0 && value.every(isEmpty)) {
|
|
275
|
+
this.keys.push(fullPath)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Traverse nested objects
|
|
280
|
+
for (const nestedPath of nestedPaths) {
|
|
281
|
+
const nestedObj = getValueByPath(this.obj, nestedPath)
|
|
282
|
+
if (isPojo(nestedObj)) {
|
|
283
|
+
this.traverse(nestedObj, nestedPath)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Analyze a field during traversal
|
|
289
|
+
analyzeFieldInTraversal(value, fullPath, nestedPaths) {
|
|
290
|
+
let hasSimpleFields = false
|
|
291
|
+
let hasArraysWithObjects = false
|
|
292
|
+
|
|
293
|
+
if (Array.isArray(value)) {
|
|
294
|
+
if (value.length === 0) {
|
|
295
|
+
hasSimpleFields = true
|
|
296
|
+
} else {
|
|
297
|
+
const analysis = analyzeArray(value)
|
|
298
|
+
|
|
299
|
+
if (analysis.hasObjects) {
|
|
300
|
+
hasArraysWithObjects = true
|
|
301
|
+
this.processArrayInTraversal(value, fullPath, nestedPaths, analysis)
|
|
302
|
+
|
|
303
|
+
if (analysis.hasNonObjects) {
|
|
304
|
+
hasSimpleFields = true
|
|
305
|
+
this.keys.push(fullPath)
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
hasSimpleFields = true
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} else if (isPojo(value)) {
|
|
312
|
+
if (Object.keys(value).length === 0) {
|
|
313
|
+
hasSimpleFields = true
|
|
314
|
+
} else if (hasOnlyEmptyValues(value)) {
|
|
315
|
+
this.keys.push(fullPath)
|
|
316
|
+
} else {
|
|
317
|
+
const hasArraysWithOnlyEmptyElements = Object.entries(value).some(
|
|
318
|
+
([k, v]) => Array.isArray(v) && v.length > 0 && v.every(isEmpty)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if (hasArraysWithOnlyEmptyElements) {
|
|
322
|
+
this.keys.push(fullPath)
|
|
323
|
+
}
|
|
324
|
+
nestedPaths.push(fullPath)
|
|
325
|
+
}
|
|
326
|
+
} else if ((isBytes(value) && value.length > 0) || isSimpleValue(value)) {
|
|
327
|
+
hasSimpleFields = true
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { hasSimpleFields, hasArraysWithObjects }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Process arrays found during traversal
|
|
334
|
+
processArrayInTraversal(array, fullPath, nestedPaths, analysis) {
|
|
335
|
+
if (
|
|
336
|
+
(analysis.hasEmptyStrings || analysis.hasEmptyObjects) &&
|
|
337
|
+
analysis.hasNonEmptyObjects
|
|
338
|
+
) {
|
|
339
|
+
// Special case: only non-empty objects get parts
|
|
340
|
+
array.forEach((item, index) => {
|
|
341
|
+
if (isPojo(item) && Object.keys(item).length > 0) {
|
|
342
|
+
const itemPath = `${fullPath}/${index + 1}`
|
|
343
|
+
this.keys.push(itemPath)
|
|
344
|
+
nestedPaths.push(itemPath)
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
} else if (
|
|
348
|
+
analysis.hasObjectsWithOnlyEmptyValues &&
|
|
349
|
+
!analysis.hasNonObjects
|
|
350
|
+
) {
|
|
351
|
+
// Objects with only empty values
|
|
352
|
+
array.forEach((item, index) => {
|
|
353
|
+
if (isPojo(item)) {
|
|
354
|
+
const itemPath = `${fullPath}/${index + 1}`
|
|
355
|
+
this.keys.push(itemPath)
|
|
356
|
+
if (Object.keys(item).length > 0) {
|
|
357
|
+
nestedPaths.push(itemPath)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
} else {
|
|
362
|
+
// Normal case
|
|
363
|
+
array.forEach((item, index) => {
|
|
364
|
+
if (isPojo(item)) {
|
|
365
|
+
const itemPath = `${fullPath}/${index + 1}`
|
|
366
|
+
this.keys.push(itemPath)
|
|
367
|
+
if (Object.keys(item).length > 0) {
|
|
368
|
+
nestedPaths.push(itemPath)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Deduplicate and filter results
|
|
376
|
+
deduplicateAndFilter() {
|
|
377
|
+
const uniqueKeys = [...new Set(this.keys)]
|
|
378
|
+
|
|
379
|
+
return uniqueKeys.filter(k => {
|
|
380
|
+
if (!k) return false
|
|
381
|
+
|
|
382
|
+
// Check if this is a path to an element inside an array with only empty elements
|
|
383
|
+
const parts = k.split("/")
|
|
384
|
+
if (parts.length >= 2 && /^\d+$/.test(parts[parts.length - 1])) {
|
|
385
|
+
const arrayPath = parts.slice(0, -1).join("/")
|
|
386
|
+
const arrayValue = getValueByPath(this.obj, arrayPath)
|
|
387
|
+
|
|
388
|
+
if (Array.isArray(arrayValue) && arrayValue.every(isEmpty)) {
|
|
389
|
+
return false
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return true
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export default function collectBodyKeys(obj, prefix = "") {
|
|
399
|
+
const collector = new BodyKeyCollector(obj)
|
|
400
|
+
return collector.collect()
|
|
401
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { toBuffer, formatFloat, isBytes, isPojo } from "./encode-utils.js"
|
|
2
|
+
|
|
3
|
+
// Helper to generate the correct number of backslashes for a given nesting level
|
|
4
|
+
function getBackslashes(level) {
|
|
5
|
+
return "\\".repeat(Math.pow(2, level) - 1)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Helper to encode primitive values at a specific nesting level
|
|
9
|
+
function encodePrimitiveAtLevel(value, level) {
|
|
10
|
+
const bs = getBackslashes(level)
|
|
11
|
+
|
|
12
|
+
if (typeof value === "number") {
|
|
13
|
+
if (Number.isInteger(value)) {
|
|
14
|
+
return `${bs}"(ao-type-integer) ${value}${bs}"`
|
|
15
|
+
} else {
|
|
16
|
+
return `${bs}"(ao-type-float) ${formatFloat(value)}${bs}"`
|
|
17
|
+
}
|
|
18
|
+
} else if (typeof value === "string") {
|
|
19
|
+
return `${bs}"${value}${bs}"`
|
|
20
|
+
} else if (value === null) {
|
|
21
|
+
const innerBs = getBackslashes(level + 1)
|
|
22
|
+
return `${bs}"(ao-type-atom) ${innerBs}"null${innerBs}"${bs}"`
|
|
23
|
+
} else if (value === undefined) {
|
|
24
|
+
const innerBs = getBackslashes(level + 1)
|
|
25
|
+
return `${bs}"(ao-type-atom) ${innerBs}"undefined${innerBs}"${bs}"`
|
|
26
|
+
} else if (typeof value === "symbol") {
|
|
27
|
+
const desc = value.description || "Symbol.for()"
|
|
28
|
+
const innerBs = getBackslashes(level + 1)
|
|
29
|
+
return `${bs}"(ao-type-atom) ${innerBs}"${desc}${innerBs}"${bs}"`
|
|
30
|
+
} else if (typeof value === "boolean") {
|
|
31
|
+
const innerBs = getBackslashes(level + 1)
|
|
32
|
+
return `${bs}"(ao-type-atom) ${innerBs}"${value}${innerBs}"${bs}"`
|
|
33
|
+
} else {
|
|
34
|
+
return `${bs}"${String(value)}${bs}"`
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Recursive function to handle arrays at any nesting level
|
|
39
|
+
function encodeArrayAtLevel(items, level) {
|
|
40
|
+
// The original code only goes 3 levels deep for arrays
|
|
41
|
+
if (level >= 3) {
|
|
42
|
+
const bs = getBackslashes(level)
|
|
43
|
+
const stringItems = items
|
|
44
|
+
.map(item => {
|
|
45
|
+
if (typeof item === "number") {
|
|
46
|
+
if (Number.isInteger(item)) {
|
|
47
|
+
return `${bs}"(ao-type-integer) ${item}${bs}"`
|
|
48
|
+
} else {
|
|
49
|
+
return `${bs}"(ao-type-float) ${formatFloat(item)}${bs}"`
|
|
50
|
+
}
|
|
51
|
+
} else if (typeof item === "string") {
|
|
52
|
+
return `${bs}"${item}${bs}"`
|
|
53
|
+
} else {
|
|
54
|
+
return `${bs}"${String(item)}${bs}"`
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
.join(", ")
|
|
58
|
+
return `${getBackslashes(level - 1)}"(ao-type-list) ${stringItems}${getBackslashes(level - 1)}"`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const encodedItems = items
|
|
62
|
+
.map(item => {
|
|
63
|
+
if (Array.isArray(item)) {
|
|
64
|
+
return encodeArrayAtLevel(item, level + 1)
|
|
65
|
+
} else {
|
|
66
|
+
return encodePrimitiveAtLevel(item, level)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
.join(", ")
|
|
70
|
+
|
|
71
|
+
if (level === 0) {
|
|
72
|
+
return `"(ao-type-list) ${encodedItems}"`
|
|
73
|
+
} else {
|
|
74
|
+
const bs = getBackslashes(level - 1)
|
|
75
|
+
return `${bs}"(ao-type-list) ${encodedItems}${bs}"`
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default function encodeArrayItem(item) {
|
|
80
|
+
if (typeof item === "number") {
|
|
81
|
+
if (Number.isInteger(item)) {
|
|
82
|
+
return `"(ao-type-integer) ${item}"`
|
|
83
|
+
} else {
|
|
84
|
+
return `"(ao-type-float) ${formatFloat(item)}"`
|
|
85
|
+
}
|
|
86
|
+
} else if (typeof item === "string") {
|
|
87
|
+
return `"${item}"`
|
|
88
|
+
} else if (item === null) {
|
|
89
|
+
return `"(ao-type-atom) \\"null\\""`
|
|
90
|
+
} else if (item === undefined) {
|
|
91
|
+
return `"(ao-type-atom) \\"undefined\\""`
|
|
92
|
+
} else if (typeof item === "symbol") {
|
|
93
|
+
const desc = item.description || "Symbol.for()"
|
|
94
|
+
return `"(ao-type-atom) \\"${desc}\\""`
|
|
95
|
+
} else if (typeof item === "boolean") {
|
|
96
|
+
return `"(ao-type-atom) \\"${item}\\""`
|
|
97
|
+
} else if (Array.isArray(item)) {
|
|
98
|
+
return encodeArrayAtLevel(item, 1)
|
|
99
|
+
} else if (isBytes(item)) {
|
|
100
|
+
const buffer = toBuffer(item)
|
|
101
|
+
if (buffer.length === 0 || buffer.byteLength === 0) {
|
|
102
|
+
return `""`
|
|
103
|
+
}
|
|
104
|
+
return `"(ao-type-binary)"`
|
|
105
|
+
} else if (isPojo(item)) {
|
|
106
|
+
const json = JSON.stringify(item)
|
|
107
|
+
const escaped = json.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
108
|
+
return `"(ao-type-map) ${escaped}"`
|
|
109
|
+
} else {
|
|
110
|
+
return `"${String(item)}"`
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { hash } from "fast-sha256"
|
|
2
|
+
|
|
3
|
+
export function isBytes(value) {
|
|
4
|
+
return (
|
|
5
|
+
value instanceof ArrayBuffer ||
|
|
6
|
+
ArrayBuffer.isView(value) ||
|
|
7
|
+
Buffer.isBuffer(value) ||
|
|
8
|
+
(value &&
|
|
9
|
+
typeof value === "object" &&
|
|
10
|
+
value.type === "Buffer" &&
|
|
11
|
+
Array.isArray(value.data))
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isPojo(value) {
|
|
16
|
+
return (
|
|
17
|
+
!isBytes(value) &&
|
|
18
|
+
!Array.isArray(value) &&
|
|
19
|
+
!(value instanceof Blob) &&
|
|
20
|
+
typeof value === "object" &&
|
|
21
|
+
value !== null
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function hasNewline(value) {
|
|
26
|
+
if (typeof value === "string") return value.includes("\n")
|
|
27
|
+
if (value instanceof Blob) {
|
|
28
|
+
value = await value.text()
|
|
29
|
+
return value.includes("\n")
|
|
30
|
+
}
|
|
31
|
+
if (isBytes(value)) return Buffer.from(value).includes("\n")
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function sha256(data) {
|
|
36
|
+
let uint8Array
|
|
37
|
+
if (data instanceof ArrayBuffer) {
|
|
38
|
+
uint8Array = new Uint8Array(data)
|
|
39
|
+
} else if (data instanceof Uint8Array) {
|
|
40
|
+
uint8Array = data
|
|
41
|
+
} else if (ArrayBuffer.isView(data)) {
|
|
42
|
+
uint8Array = new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
43
|
+
} else {
|
|
44
|
+
throw new Error("sha256 expects ArrayBuffer or ArrayBufferView")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hashResult = hash(uint8Array)
|
|
48
|
+
return hashResult.buffer.slice(
|
|
49
|
+
hashResult.byteOffset,
|
|
50
|
+
hashResult.byteOffset + hashResult.byteLength
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function formatFloat(num) {
|
|
55
|
+
let exp = num.toExponential(20)
|
|
56
|
+
exp = exp.replace(/e\+(\d)$/, "e+0$1")
|
|
57
|
+
exp = exp.replace(/e-(\d)$/, "e-0$1")
|
|
58
|
+
return exp
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function hasNonAscii(str) {
|
|
62
|
+
return /[^\x00-\x7F]/.test(str)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function toBuffer(value) {
|
|
66
|
+
if (Buffer.isBuffer(value)) {
|
|
67
|
+
return value
|
|
68
|
+
} else if (
|
|
69
|
+
value &&
|
|
70
|
+
typeof value === "object" &&
|
|
71
|
+
value.type === "Buffer" &&
|
|
72
|
+
Array.isArray(value.data)
|
|
73
|
+
) {
|
|
74
|
+
return Buffer.from(value.data)
|
|
75
|
+
} else if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
|
76
|
+
return Buffer.from(value)
|
|
77
|
+
} else {
|
|
78
|
+
return Buffer.from(value)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Navigate through a path to get a value from nested object/array
|
|
83
|
+
export function getValueByPath(obj, path) {
|
|
84
|
+
const pathParts = path.split("/")
|
|
85
|
+
let value = obj
|
|
86
|
+
for (const part of pathParts) {
|
|
87
|
+
if (/^\d+$/.test(part)) {
|
|
88
|
+
value = value[parseInt(part) - 1]
|
|
89
|
+
} else {
|
|
90
|
+
value = value[part]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return value
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Get the ao-type for a value
|
|
97
|
+
export function getAoType(value) {
|
|
98
|
+
if (
|
|
99
|
+
typeof value === "boolean" ||
|
|
100
|
+
value === null ||
|
|
101
|
+
value === undefined ||
|
|
102
|
+
typeof value === "symbol"
|
|
103
|
+
) {
|
|
104
|
+
return "atom"
|
|
105
|
+
} else if (typeof value === "number") {
|
|
106
|
+
return Number.isInteger(value) ? "integer" : "float"
|
|
107
|
+
} else if (typeof value === "string" && value.length === 0) {
|
|
108
|
+
return "empty-binary"
|
|
109
|
+
} else if (isBytes(value) && (value.length === 0 || value.byteLength === 0)) {
|
|
110
|
+
return "empty-binary"
|
|
111
|
+
} else if (Array.isArray(value) && value.length === 0) {
|
|
112
|
+
return "empty-list"
|
|
113
|
+
} else if (Array.isArray(value)) {
|
|
114
|
+
return "list"
|
|
115
|
+
} else if (isPojo(value) && Object.keys(value).length === 0) {
|
|
116
|
+
return "empty-message"
|
|
117
|
+
}
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if a value is empty
|
|
122
|
+
export function isEmpty(value) {
|
|
123
|
+
return (
|
|
124
|
+
(Array.isArray(value) && value.length === 0) ||
|
|
125
|
+
(isPojo(value) && Object.keys(value).length === 0) ||
|
|
126
|
+
(isBytes(value) && (value.length === 0 || value.byteLength === 0)) ||
|
|
127
|
+
(typeof value === "string" && value.length === 0)
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Convert primitive values to their encoded string representation
|
|
132
|
+
export function encodePrimitiveContent(value) {
|
|
133
|
+
if (typeof value === "string") {
|
|
134
|
+
return value
|
|
135
|
+
} else if (typeof value === "boolean") {
|
|
136
|
+
return `"${value}"`
|
|
137
|
+
} else if (typeof value === "number") {
|
|
138
|
+
return String(value)
|
|
139
|
+
} else if (value === null) {
|
|
140
|
+
return '"null"'
|
|
141
|
+
} else if (value === undefined) {
|
|
142
|
+
return '"undefined"'
|
|
143
|
+
} else if (typeof value === "symbol") {
|
|
144
|
+
return `"${value.description || "Symbol.for()"}"`
|
|
145
|
+
}
|
|
146
|
+
return value
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Sort type annotations by their numeric prefix
|
|
150
|
+
export function sortTypeAnnotations(types) {
|
|
151
|
+
return types.sort((a, b) => {
|
|
152
|
+
const aNum = parseInt(a.split("=")[0])
|
|
153
|
+
const bNum = parseInt(b.split("=")[0])
|
|
154
|
+
return aNum - bNum
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Analyze array contents
|
|
159
|
+
export function analyzeArray(array) {
|
|
160
|
+
return {
|
|
161
|
+
hasObjects: array.some(item => isPojo(item)),
|
|
162
|
+
hasArrays: array.some(item => Array.isArray(item)),
|
|
163
|
+
hasEmptyStrings: array.some(
|
|
164
|
+
item => typeof item === "string" && item === ""
|
|
165
|
+
),
|
|
166
|
+
hasEmptyObjects: array.some(
|
|
167
|
+
item => isPojo(item) && Object.keys(item).length === 0
|
|
168
|
+
),
|
|
169
|
+
hasOnlyEmptyElements:
|
|
170
|
+
array.length > 0 &&
|
|
171
|
+
array.every(
|
|
172
|
+
item =>
|
|
173
|
+
(Array.isArray(item) && item.length === 0) ||
|
|
174
|
+
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
175
|
+
(typeof item === "string" && item === "")
|
|
176
|
+
),
|
|
177
|
+
hasOnlyNonEmptyObjects:
|
|
178
|
+
array.length > 0 &&
|
|
179
|
+
array.every(item => isPojo(item) && Object.keys(item).length > 0),
|
|
180
|
+
hasObjectsWithOnlyEmptyValues: array.some(item => {
|
|
181
|
+
if (!isPojo(item) || Object.keys(item).length === 0) return false
|
|
182
|
+
return Object.values(item).every(v => isEmpty(v))
|
|
183
|
+
}),
|
|
184
|
+
}
|
|
185
|
+
}
|