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.
@@ -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
+ }