wao 0.27.3 → 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 +1182 -1921
- 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 +856 -1521
- package/package.json +1 -1
package/esm/encode.js
CHANGED
|
@@ -1,739 +1,39 @@
|
|
|
1
1
|
import base64url from "base64url"
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
!Array.isArray(value) &&
|
|
20
|
-
!(value instanceof Blob) &&
|
|
21
|
-
typeof value === "object" &&
|
|
22
|
-
value !== null
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
2
|
+
import {
|
|
3
|
+
getValueByPath,
|
|
4
|
+
getAoType,
|
|
5
|
+
isEmpty,
|
|
6
|
+
encodePrimitiveContent,
|
|
7
|
+
sortTypeAnnotations,
|
|
8
|
+
analyzeArray,
|
|
9
|
+
toBuffer,
|
|
10
|
+
formatFloat,
|
|
11
|
+
hasNonAscii,
|
|
12
|
+
sha256,
|
|
13
|
+
hasNewline,
|
|
14
|
+
isBytes,
|
|
15
|
+
isPojo,
|
|
16
|
+
} from "./encode-utils.js"
|
|
17
|
+
import encodeArrayItem from "./encode-array-item.js"
|
|
18
|
+
import collectBodyKeys from "./collect-body-keys.js"
|
|
26
19
|
const MAX_HEADER_LENGTH = 4096
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return value.includes("\n")
|
|
33
|
-
}
|
|
34
|
-
if (isBytes(value)) return Buffer.from(value).includes("\n")
|
|
35
|
-
return false
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function sha256(data) {
|
|
39
|
-
let uint8Array
|
|
40
|
-
if (data instanceof ArrayBuffer) {
|
|
41
|
-
uint8Array = new Uint8Array(data)
|
|
42
|
-
} else if (data instanceof Uint8Array) {
|
|
43
|
-
uint8Array = data
|
|
44
|
-
} else if (ArrayBuffer.isView(data)) {
|
|
45
|
-
uint8Array = new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
46
|
-
} else {
|
|
47
|
-
throw new Error("sha256 expects ArrayBuffer or ArrayBufferView")
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const hashResult = hash(uint8Array)
|
|
51
|
-
return hashResult.buffer.slice(
|
|
52
|
-
hashResult.byteOffset,
|
|
53
|
-
hashResult.byteOffset + hashResult.byteLength
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function formatFloat(num) {
|
|
58
|
-
let exp = num.toExponential(20)
|
|
59
|
-
exp = exp.replace(/e\+(\d)$/, "e+0$1")
|
|
60
|
-
exp = exp.replace(/e-(\d)$/, "e-0$1")
|
|
61
|
-
return exp
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function hasNonAscii(str) {
|
|
65
|
-
return /[^\x00-\x7F]/.test(str)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function encodeArrayItem(item) {
|
|
69
|
-
if (typeof item === "number") {
|
|
70
|
-
if (Number.isInteger(item)) {
|
|
71
|
-
return `"(ao-type-integer) ${item}"`
|
|
72
|
-
} else {
|
|
73
|
-
return `"(ao-type-float) ${formatFloat(item)}"`
|
|
74
|
-
}
|
|
75
|
-
} else if (typeof item === "string") {
|
|
76
|
-
return `"${item}"`
|
|
77
|
-
} else if (item === null) {
|
|
78
|
-
return `"(ao-type-atom) \\"null\\""`
|
|
79
|
-
} else if (item === undefined) {
|
|
80
|
-
return `"(ao-type-atom) \\"undefined\\""`
|
|
81
|
-
} else if (typeof item === "symbol") {
|
|
82
|
-
const desc = item.description || "Symbol.for()"
|
|
83
|
-
return `"(ao-type-atom) \\"${desc}\\""`
|
|
84
|
-
} else if (typeof item === "boolean") {
|
|
85
|
-
return `"(ao-type-atom) \\"${item}\\""`
|
|
86
|
-
} else if (Array.isArray(item)) {
|
|
87
|
-
const nestedItems = item
|
|
88
|
-
.map(nestedItem => {
|
|
89
|
-
if (typeof nestedItem === "number") {
|
|
90
|
-
if (Number.isInteger(nestedItem)) {
|
|
91
|
-
return `\\"(ao-type-integer) ${nestedItem}\\"`
|
|
92
|
-
} else {
|
|
93
|
-
return `\\"(ao-type-float) ${formatFloat(nestedItem)}\\"`
|
|
94
|
-
}
|
|
95
|
-
} else if (typeof nestedItem === "string") {
|
|
96
|
-
return `\\"${nestedItem}\\"`
|
|
97
|
-
} else if (nestedItem === null) {
|
|
98
|
-
return `\\"(ao-type-atom) \\\\\\"null\\\\\\"\\"`
|
|
99
|
-
} else if (nestedItem === undefined) {
|
|
100
|
-
return `\\"(ao-type-atom) \\\\\\"undefined\\\\\\"\\"`
|
|
101
|
-
} else if (typeof nestedItem === "symbol") {
|
|
102
|
-
const desc = nestedItem.description || "Symbol.for()"
|
|
103
|
-
return `\\"(ao-type-atom) \\\\\\"${desc}\\\\\\"\\"`
|
|
104
|
-
} else if (typeof nestedItem === "boolean") {
|
|
105
|
-
return `\\"(ao-type-atom) \\\\\\"${nestedItem}\\\\\\"\\"`
|
|
106
|
-
} else if (Array.isArray(nestedItem)) {
|
|
107
|
-
// Handle nested arrays recursively
|
|
108
|
-
const deeperItems = nestedItem
|
|
109
|
-
.map(deepItem => {
|
|
110
|
-
if (typeof deepItem === "number") {
|
|
111
|
-
if (Number.isInteger(deepItem)) {
|
|
112
|
-
return `\\\\\\"(ao-type-integer) ${deepItem}\\\\\\"`
|
|
113
|
-
} else {
|
|
114
|
-
return `\\\\\\"(ao-type-float) ${formatFloat(deepItem)}\\\\\\"`
|
|
115
|
-
}
|
|
116
|
-
} else if (typeof deepItem === "string") {
|
|
117
|
-
return `\\\\\\"${deepItem}\\\\\\"`
|
|
118
|
-
} else if (Array.isArray(deepItem)) {
|
|
119
|
-
// Even deeper nesting - need to escape more
|
|
120
|
-
const deepestItems = deepItem
|
|
121
|
-
.map(deepestItem => {
|
|
122
|
-
if (typeof deepestItem === "number") {
|
|
123
|
-
if (Number.isInteger(deepestItem)) {
|
|
124
|
-
return `\\\\\\\\\\\\\\"(ao-type-integer) ${deepestItem}\\\\\\\\\\\\\\"`
|
|
125
|
-
} else {
|
|
126
|
-
return `\\\\\\\\\\\\\\"(ao-type-float) ${formatFloat(deepestItem)}\\\\\\\\\\\\\\"`
|
|
127
|
-
}
|
|
128
|
-
} else if (typeof deepestItem === "string") {
|
|
129
|
-
return `\\\\\\\\\\\\\\"${deepestItem}\\\\\\\\\\\\\\"`
|
|
130
|
-
} else {
|
|
131
|
-
return `\\\\\\\\\\\\\\"${String(deepestItem)}\\\\\\\\\\\\\\"`
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
.join(", ")
|
|
135
|
-
return `\\\\\\"(ao-type-list) ${deepestItems}\\\\\\"`
|
|
136
|
-
} else if (deepItem === null) {
|
|
137
|
-
return `\\\\\\"(ao-type-atom) \\\\\\\\\\\\\\"null\\\\\\\\\\\\\\"\\\\\\"`
|
|
138
|
-
} else if (deepItem === undefined) {
|
|
139
|
-
return `\\\\\\"(ao-type-atom) \\\\\\\\\\\\\\"undefined\\\\\\\\\\\\\\"\\\\\\"`
|
|
140
|
-
} else if (typeof deepItem === "symbol") {
|
|
141
|
-
const desc = deepItem.description || "Symbol.for()"
|
|
142
|
-
return `\\\\\\"(ao-type-atom) \\\\\\\\\\\\\\"${desc}\\\\\\\\\\\\\\"\\\\\\"`
|
|
143
|
-
} else if (typeof deepItem === "boolean") {
|
|
144
|
-
return `\\\\\\"(ao-type-atom) \\\\\\\\\\\\\\"${deepItem}\\\\\\\\\\\\\\"\\\\\\"`
|
|
145
|
-
} else {
|
|
146
|
-
return `\\\\\\"${String(deepItem)}\\\\\\"`
|
|
147
|
-
}
|
|
148
|
-
})
|
|
149
|
-
.join(", ")
|
|
150
|
-
return `\\"(ao-type-list) ${deeperItems}\\"`
|
|
151
|
-
} else {
|
|
152
|
-
return `\\"${String(nestedItem)}\\"`
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
.join(", ")
|
|
156
|
-
return `"(ao-type-list) ${nestedItems}"`
|
|
157
|
-
} else if (isBytes(item)) {
|
|
158
|
-
const buffer = toBuffer(item)
|
|
159
|
-
if (buffer.length === 0 || buffer.byteLength === 0) {
|
|
160
|
-
return `""`
|
|
161
|
-
}
|
|
162
|
-
return `"(ao-type-binary)"`
|
|
163
|
-
} else if (isPojo(item)) {
|
|
164
|
-
const json = JSON.stringify(item)
|
|
165
|
-
const escaped = json.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
166
|
-
return `"(ao-type-map) ${escaped}"`
|
|
167
|
-
} else {
|
|
168
|
-
return `"${String(item)}"`
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function toBuffer(value) {
|
|
173
|
-
if (Buffer.isBuffer(value)) {
|
|
174
|
-
return value
|
|
175
|
-
} else if (
|
|
176
|
-
value &&
|
|
177
|
-
typeof value === "object" &&
|
|
178
|
-
value.type === "Buffer" &&
|
|
179
|
-
Array.isArray(value.data)
|
|
180
|
-
) {
|
|
181
|
-
return Buffer.from(value.data)
|
|
182
|
-
} else if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
|
183
|
-
return Buffer.from(value)
|
|
184
|
-
} else {
|
|
185
|
-
return Buffer.from(value)
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function collectBodyKeys(obj, prefix = "") {
|
|
190
|
-
console.log("=== collectBodyKeys START ===")
|
|
191
|
-
console.log("Input object:", JSON.stringify(obj))
|
|
192
|
-
|
|
193
|
-
const keys = []
|
|
194
|
-
|
|
195
|
-
function traverse(current, path) {
|
|
196
|
-
console.log(`[traverse] Called with path: "${path}"`)
|
|
197
|
-
let hasSimpleFields = false
|
|
198
|
-
const nestedPaths = []
|
|
199
|
-
let hasArraysWithObjects = false
|
|
200
|
-
|
|
201
|
-
for (const [key, value] of Object.entries(current)) {
|
|
202
|
-
const fullPath = path ? `${path}/${key}` : key
|
|
203
|
-
|
|
204
|
-
if (Array.isArray(value)) {
|
|
205
|
-
console.log(
|
|
206
|
-
`[traverse] Found array at ${fullPath}, length: ${value.length}`
|
|
207
|
-
)
|
|
208
|
-
const hasObjects = value.some(item => isPojo(item))
|
|
209
|
-
const hasNonObjects = value.some(item => !isPojo(item))
|
|
210
|
-
|
|
211
|
-
if (value.length === 0) {
|
|
212
|
-
console.log(
|
|
213
|
-
`[traverse] Empty array at ${fullPath} - marking parent as having simple fields`
|
|
214
|
-
)
|
|
215
|
-
hasSimpleFields = true
|
|
216
|
-
} else if (hasObjects) {
|
|
217
|
-
hasArraysWithObjects = true
|
|
218
|
-
// Check if we need special handling for mixed arrays
|
|
219
|
-
const hasEmptyStrings = value.some(
|
|
220
|
-
item => typeof item === "string" && item === ""
|
|
221
|
-
)
|
|
222
|
-
const hasEmptyObjects = value.some(
|
|
223
|
-
item => isPojo(item) && Object.keys(item).length === 0
|
|
224
|
-
)
|
|
225
|
-
const hasNonEmptyObjects = value.some(
|
|
226
|
-
item => isPojo(item) && Object.keys(item).length > 0
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
// Check if objects contain only empty values (not empty objects)
|
|
230
|
-
const hasObjectsWithOnlyEmptyValues = value.some(item => {
|
|
231
|
-
if (!isPojo(item) || Object.keys(item).length === 0) return false
|
|
232
|
-
return Object.values(item).every(
|
|
233
|
-
v =>
|
|
234
|
-
(typeof v === "string" && v === "") ||
|
|
235
|
-
(Array.isArray(v) && v.length === 0) ||
|
|
236
|
-
(isPojo(v) && Object.keys(v).length === 0)
|
|
237
|
-
)
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
// Only use special handling if we have BOTH empty elements AND non-empty objects
|
|
241
|
-
if ((hasEmptyStrings || hasEmptyObjects) && hasNonEmptyObjects) {
|
|
242
|
-
// Special case: mixed array with empty strings/objects - only non-empty objects get parts
|
|
243
|
-
value.forEach((item, index) => {
|
|
244
|
-
if (isPojo(item) && Object.keys(item).length > 0) {
|
|
245
|
-
const itemPath = `${fullPath}/${index + 1}`
|
|
246
|
-
keys.push(itemPath)
|
|
247
|
-
nestedPaths.push(itemPath)
|
|
248
|
-
}
|
|
249
|
-
})
|
|
250
|
-
if (hasNonObjects) {
|
|
251
|
-
hasSimpleFields = true
|
|
252
|
-
keys.push(fullPath)
|
|
253
|
-
}
|
|
254
|
-
} else if (hasObjectsWithOnlyEmptyValues && !hasNonObjects) {
|
|
255
|
-
// Special case: objects that contain only empty values should get parts
|
|
256
|
-
value.forEach((item, index) => {
|
|
257
|
-
if (isPojo(item)) {
|
|
258
|
-
const itemPath = `${fullPath}/${index + 1}`
|
|
259
|
-
keys.push(itemPath)
|
|
260
|
-
if (Object.keys(item).length > 0) {
|
|
261
|
-
nestedPaths.push(itemPath)
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
})
|
|
265
|
-
} else {
|
|
266
|
-
// Normal case: all objects get parts
|
|
267
|
-
value.forEach((item, index) => {
|
|
268
|
-
if (isPojo(item)) {
|
|
269
|
-
const itemPath = `${fullPath}/${index + 1}`
|
|
270
|
-
keys.push(itemPath)
|
|
271
|
-
if (Object.keys(item).length > 0) {
|
|
272
|
-
nestedPaths.push(itemPath)
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
})
|
|
276
|
-
if (hasNonObjects) {
|
|
277
|
-
hasSimpleFields = true
|
|
278
|
-
keys.push(fullPath)
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
} else {
|
|
282
|
-
console.log(
|
|
283
|
-
`[traverse] Non-empty array without objects at ${fullPath} - marking as simple field`
|
|
284
|
-
)
|
|
285
|
-
hasSimpleFields = true
|
|
286
|
-
}
|
|
287
|
-
} else if (isPojo(value)) {
|
|
288
|
-
if (Object.keys(value).length === 0) {
|
|
289
|
-
console.log(
|
|
290
|
-
`[traverse] Empty object at ${fullPath} - marking parent as having simple fields`
|
|
291
|
-
)
|
|
292
|
-
hasSimpleFields = true
|
|
293
|
-
} else {
|
|
294
|
-
// Don't traverse into the object if it only contains empty values
|
|
295
|
-
const containsOnlyEmptyCollections = Object.entries(value).every(
|
|
296
|
-
([k, v]) => {
|
|
297
|
-
return (
|
|
298
|
-
(Array.isArray(v) && v.length === 0) ||
|
|
299
|
-
(isPojo(v) && Object.keys(v).length === 0) ||
|
|
300
|
-
(isBytes(v) && (v.length === 0 || v.byteLength === 0)) ||
|
|
301
|
-
(typeof v === "string" && v.length === 0)
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
if (containsOnlyEmptyCollections && Object.keys(value).length > 0) {
|
|
307
|
-
console.log(
|
|
308
|
-
`[traverse] Object at ${fullPath} contains only empty collections - adding as body key`
|
|
309
|
-
)
|
|
310
|
-
keys.push(fullPath)
|
|
311
|
-
} else {
|
|
312
|
-
// Check if this object contains arrays with only empty elements
|
|
313
|
-
const hasArraysWithOnlyEmptyElements = Object.entries(value).some(
|
|
314
|
-
([k, v]) => {
|
|
315
|
-
return (
|
|
316
|
-
Array.isArray(v) &&
|
|
317
|
-
v.length > 0 &&
|
|
318
|
-
v.every(
|
|
319
|
-
item =>
|
|
320
|
-
(Array.isArray(item) && item.length === 0) ||
|
|
321
|
-
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
322
|
-
(typeof item === "string" && item === "")
|
|
323
|
-
)
|
|
324
|
-
)
|
|
325
|
-
}
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
if (hasArraysWithOnlyEmptyElements) {
|
|
329
|
-
// This object needs a body part to show its array types
|
|
330
|
-
console.log(
|
|
331
|
-
`[traverse] Object at ${fullPath} has arrays with empty elements - adding as body key`
|
|
332
|
-
)
|
|
333
|
-
keys.push(fullPath)
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
console.log(
|
|
337
|
-
`[traverse] Non-empty object at ${fullPath} - will traverse into it`
|
|
338
|
-
)
|
|
339
|
-
nestedPaths.push(fullPath)
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
} else if (isBytes(value) && value.length > 0) {
|
|
343
|
-
hasSimpleFields = true
|
|
344
|
-
} else if (
|
|
345
|
-
typeof value === "string" ||
|
|
346
|
-
typeof value === "number" ||
|
|
347
|
-
typeof value === "boolean" ||
|
|
348
|
-
value === null ||
|
|
349
|
-
value === undefined ||
|
|
350
|
-
typeof value === "symbol"
|
|
351
|
-
) {
|
|
352
|
-
hasSimpleFields = true
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (hasSimpleFields) {
|
|
357
|
-
console.log(`[traverse] Adding "${path}" to keys (has simple fields)`)
|
|
358
|
-
keys.push(path)
|
|
359
|
-
} else if (hasArraysWithObjects && path) {
|
|
360
|
-
// If the object only contains arrays with objects, we still need to add it as a body key
|
|
361
|
-
console.log(
|
|
362
|
-
`[traverse] Adding "${path}" to keys (contains arrays with objects)`
|
|
363
|
-
)
|
|
364
|
-
keys.push(path)
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Check for arrays with only empty elements that need their own body parts
|
|
368
|
-
for (const [key, value] of Object.entries(current)) {
|
|
369
|
-
const fullPath = path ? `${path}/${key}` : key
|
|
370
|
-
|
|
371
|
-
if (Array.isArray(value) && value.length > 0) {
|
|
372
|
-
const hasOnlyEmptyElements = value.every(
|
|
373
|
-
item =>
|
|
374
|
-
(Array.isArray(item) && item.length === 0) ||
|
|
375
|
-
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
376
|
-
(typeof item === "string" && item === "")
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
if (hasOnlyEmptyElements) {
|
|
380
|
-
console.log(
|
|
381
|
-
`[traverse] Array at ${fullPath} has only empty elements - adding as body key`
|
|
382
|
-
)
|
|
383
|
-
keys.push(fullPath)
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
for (const nestedPath of nestedPaths) {
|
|
389
|
-
const parts = nestedPath.split("/")
|
|
390
|
-
let nestedObj = obj
|
|
391
|
-
|
|
392
|
-
for (const part of parts) {
|
|
393
|
-
if (/^\d+$/.test(part)) {
|
|
394
|
-
nestedObj = nestedObj[parseInt(part) - 1]
|
|
395
|
-
} else {
|
|
396
|
-
nestedObj = nestedObj[part]
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (isPojo(nestedObj)) {
|
|
401
|
-
traverse(nestedObj, nestedPath)
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const objKeys = Object.keys(obj)
|
|
407
|
-
|
|
408
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
409
|
-
console.log(`\n[main loop] Processing key: "${key}"`)
|
|
410
|
-
console.log(
|
|
411
|
-
`[main loop] Value type: ${Array.isArray(value) ? "array" : typeof value}`
|
|
412
|
-
)
|
|
413
|
-
console.log(
|
|
414
|
-
`[main loop] Array length: ${Array.isArray(value) ? value.length : "N/A"}`
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
if (
|
|
418
|
-
(key === "data" || key === "body") &&
|
|
419
|
-
(typeof value === "string" ||
|
|
420
|
-
typeof value === "boolean" ||
|
|
421
|
-
typeof value === "number" ||
|
|
422
|
-
value === null ||
|
|
423
|
-
value === undefined ||
|
|
424
|
-
typeof value === "symbol") &&
|
|
425
|
-
objKeys.length > 1
|
|
426
|
-
) {
|
|
427
|
-
// Special handling: only add to body keys if there's no other data/body field with an object
|
|
428
|
-
if (
|
|
429
|
-
key === "data" &&
|
|
430
|
-
obj.body &&
|
|
431
|
-
isPojo(obj.body) &&
|
|
432
|
-
Object.keys(obj.body).length > 0
|
|
433
|
-
) {
|
|
434
|
-
console.log(`[main loop] Skipping special data field`)
|
|
435
|
-
} else if (
|
|
436
|
-
key === "body" &&
|
|
437
|
-
obj.data &&
|
|
438
|
-
isPojo(obj.data) &&
|
|
439
|
-
Object.keys(obj.data).length > 0
|
|
440
|
-
) {
|
|
441
|
-
console.log(`[main loop] Skipping special body field`)
|
|
442
|
-
} else {
|
|
443
|
-
console.log(`[main loop] Adding special data/body key: "${key}"`)
|
|
444
|
-
keys.push(key)
|
|
445
|
-
}
|
|
446
|
-
} else if (Array.isArray(value)) {
|
|
447
|
-
if (value.length === 0) {
|
|
448
|
-
console.log(`[main loop] SKIPPING empty array for key: "${key}"`)
|
|
449
|
-
continue
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const hasObjects = value.some(item => isPojo(item))
|
|
453
|
-
const hasArrays = value.some(item => Array.isArray(item))
|
|
454
|
-
const hasNonObjects = value.some(item => !isPojo(item))
|
|
455
|
-
|
|
456
|
-
// Check if this is an array of arrays containing objects
|
|
457
|
-
const hasArraysOfObjects = value.some(
|
|
458
|
-
item => Array.isArray(item) && item.some(subItem => isPojo(subItem))
|
|
459
|
-
)
|
|
460
|
-
|
|
461
|
-
console.log(
|
|
462
|
-
`[main loop] Array analysis: hasObjects=${hasObjects}, hasArrays=${hasArrays}, hasNonObjects=${hasNonObjects}, hasArraysOfObjects=${hasArraysOfObjects}`
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
if (value.length > 0) {
|
|
466
|
-
let bodyPartCounter = 1 // Start counting from 1
|
|
467
|
-
|
|
468
|
-
// Check for special mixed array case
|
|
469
|
-
const hasEmptyStrings = value.some(
|
|
470
|
-
item => typeof item === "string" && item === ""
|
|
471
|
-
)
|
|
472
|
-
const hasEmptyObjects = value.some(
|
|
473
|
-
item => isPojo(item) && Object.keys(item).length === 0
|
|
474
|
-
)
|
|
475
|
-
|
|
476
|
-
// Check for objects that contain only empty values
|
|
477
|
-
const hasObjectsWithOnlyEmptyValues = value.some(item => {
|
|
478
|
-
if (!isPojo(item) || Object.keys(item).length === 0) return false
|
|
479
|
-
return Object.values(item).every(
|
|
480
|
-
v =>
|
|
481
|
-
(typeof v === "string" && v === "") ||
|
|
482
|
-
(Array.isArray(v) && v.length === 0) ||
|
|
483
|
-
(isPojo(v) && Object.keys(v).length === 0)
|
|
484
|
-
)
|
|
485
|
-
})
|
|
486
|
-
|
|
487
|
-
if (hasArraysOfObjects) {
|
|
488
|
-
// Handle arrays of arrays containing objects
|
|
489
|
-
value.forEach((item, index) => {
|
|
490
|
-
if (Array.isArray(item)) {
|
|
491
|
-
item.forEach((subItem, subIndex) => {
|
|
492
|
-
if (isPojo(subItem)) {
|
|
493
|
-
const path = `${key}/${index + 1}/${subIndex + 1}`
|
|
494
|
-
console.log(
|
|
495
|
-
`[main loop] Adding nested object path: "${path}"`
|
|
496
|
-
)
|
|
497
|
-
keys.push(path)
|
|
498
|
-
}
|
|
499
|
-
})
|
|
500
|
-
}
|
|
501
|
-
bodyPartCounter++
|
|
502
|
-
})
|
|
503
|
-
// Always add the main array key
|
|
504
|
-
console.log(`[main loop] ADDING main array key: "${key}"`)
|
|
505
|
-
keys.push(key)
|
|
506
|
-
} else if (
|
|
507
|
-
hasObjects &&
|
|
508
|
-
(hasEmptyStrings || hasEmptyObjects) &&
|
|
509
|
-
!hasObjectsWithOnlyEmptyValues
|
|
510
|
-
) {
|
|
511
|
-
// Special handling: only non-empty objects get parts
|
|
512
|
-
value.forEach((item, index) => {
|
|
513
|
-
if (isPojo(item) && Object.keys(item).length > 0) {
|
|
514
|
-
const path = `${key}/${bodyPartCounter}`
|
|
515
|
-
console.log(
|
|
516
|
-
`[main loop] Adding non-empty object path: "${path}" (array index ${index})`
|
|
517
|
-
)
|
|
518
|
-
keys.push(path)
|
|
519
|
-
// Add paths for nested objects
|
|
520
|
-
for (const [nestedKey, nestedValue] of Object.entries(item)) {
|
|
521
|
-
if (isPojo(nestedValue)) {
|
|
522
|
-
const nestedPath = `${key}/${bodyPartCounter}/${nestedKey}`
|
|
523
|
-
console.log(
|
|
524
|
-
`[main loop] Adding nested object path: "${nestedPath}"`
|
|
525
|
-
)
|
|
526
|
-
keys.push(nestedPath)
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
bodyPartCounter++
|
|
531
|
-
})
|
|
532
|
-
// Always add the main array key
|
|
533
|
-
console.log(`[main loop] ADDING main array key: "${key}"`)
|
|
534
|
-
keys.push(key)
|
|
535
|
-
} else if (hasObjects) {
|
|
536
|
-
// Normal handling: all objects get parts (except if parent array has only empty elements)
|
|
537
|
-
let skipEmptyObjects = false
|
|
538
|
-
|
|
539
|
-
// Check if this array contains only empty elements
|
|
540
|
-
const arrayHasOnlyEmptyElements = value.every(
|
|
541
|
-
item =>
|
|
542
|
-
(Array.isArray(item) && item.length === 0) ||
|
|
543
|
-
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
544
|
-
(typeof item === "string" && item === "")
|
|
545
|
-
)
|
|
546
|
-
|
|
547
|
-
if (arrayHasOnlyEmptyElements) {
|
|
548
|
-
skipEmptyObjects = true
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
value.forEach((item, index) => {
|
|
552
|
-
if (isPojo(item)) {
|
|
553
|
-
// Skip empty objects if array has only empty elements
|
|
554
|
-
if (skipEmptyObjects && Object.keys(item).length === 0) {
|
|
555
|
-
bodyPartCounter++
|
|
556
|
-
return
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const path = `${key}/${bodyPartCounter}`
|
|
560
|
-
console.log(
|
|
561
|
-
`[main loop] Adding object path: "${path}" (array index ${index}, empty=${Object.keys(item).length === 0})`
|
|
562
|
-
)
|
|
563
|
-
keys.push(path)
|
|
564
|
-
// Add paths for nested objects (but not empty ones)
|
|
565
|
-
if (Object.keys(item).length > 0) {
|
|
566
|
-
for (const [nestedKey, nestedValue] of Object.entries(item)) {
|
|
567
|
-
if (
|
|
568
|
-
isPojo(nestedValue) &&
|
|
569
|
-
Object.keys(nestedValue).length > 0
|
|
570
|
-
) {
|
|
571
|
-
const nestedPath = `${key}/${bodyPartCounter}/${nestedKey}`
|
|
572
|
-
console.log(
|
|
573
|
-
`[main loop] Adding nested object path: "${nestedPath}"`
|
|
574
|
-
)
|
|
575
|
-
keys.push(nestedPath)
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
} else if (typeof item === "string" && item === "") {
|
|
580
|
-
// Empty strings may get parts in some formats
|
|
581
|
-
const path = `${key}/${bodyPartCounter}`
|
|
582
|
-
console.log(
|
|
583
|
-
`[main loop] Adding empty string path: "${path}" (array index ${index})`
|
|
584
|
-
)
|
|
585
|
-
keys.push(path)
|
|
586
|
-
}
|
|
587
|
-
bodyPartCounter++
|
|
588
|
-
})
|
|
589
|
-
// Don't add main array key for arrays with only objects containing empty values
|
|
590
|
-
if (
|
|
591
|
-
!hasObjectsWithOnlyEmptyValues ||
|
|
592
|
-
value.some(item => !isPojo(item))
|
|
593
|
-
) {
|
|
594
|
-
// Check if array has only empty elements
|
|
595
|
-
const hasOnlyEmptyElements = value.every(
|
|
596
|
-
item =>
|
|
597
|
-
(Array.isArray(item) && item.length === 0) ||
|
|
598
|
-
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
599
|
-
(typeof item === "string" && item === "")
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
if (!hasOnlyEmptyElements) {
|
|
603
|
-
// Always add the main array key
|
|
604
|
-
console.log(`[main loop] ADDING main array key: "${key}"`)
|
|
605
|
-
keys.push(key)
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
} else {
|
|
609
|
-
// Check if array has only empty elements
|
|
610
|
-
const hasOnlyEmptyArraysOrObjects = value.every(
|
|
611
|
-
item =>
|
|
612
|
-
(Array.isArray(item) && item.length === 0) ||
|
|
613
|
-
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
614
|
-
(typeof item === "string" && item === "")
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
if (hasOnlyEmptyArraysOrObjects && value.length > 0) {
|
|
618
|
-
// Always add the main array key for arrays with only empty elements
|
|
619
|
-
console.log(
|
|
620
|
-
`[main loop] ADDING main array key for empty elements: "${key}"`
|
|
621
|
-
)
|
|
622
|
-
keys.push(key)
|
|
623
|
-
} else if (!hasOnlyEmptyArraysOrObjects) {
|
|
624
|
-
// Always add the main array key
|
|
625
|
-
console.log(`[main loop] ADDING main array key: "${key}"`)
|
|
626
|
-
keys.push(key)
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
} else if (isPojo(value)) {
|
|
631
|
-
console.log(`[main loop] Traversing object at key: "${key}"`)
|
|
632
|
-
// Check if this object contains arrays with only empty elements
|
|
633
|
-
let hasArraysWithOnlyEmptyElements = false
|
|
634
|
-
for (const [k, v] of Object.entries(value)) {
|
|
635
|
-
if (
|
|
636
|
-
Array.isArray(v) &&
|
|
637
|
-
v.length > 0 &&
|
|
638
|
-
v.every(
|
|
639
|
-
item =>
|
|
640
|
-
(Array.isArray(item) && item.length === 0) ||
|
|
641
|
-
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
642
|
-
(typeof item === "string" && item === "")
|
|
643
|
-
)
|
|
644
|
-
) {
|
|
645
|
-
hasArraysWithOnlyEmptyElements = true
|
|
646
|
-
keys.push(`${key}/${k}`)
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
traverse(value, key)
|
|
650
|
-
} else if (isBytes(value) && value.length > 0) {
|
|
651
|
-
console.log(`[main loop] Adding key for non-empty bytes: "${key}"`)
|
|
652
|
-
keys.push(key)
|
|
653
|
-
} else if (typeof value === "string" && value.includes("\n")) {
|
|
654
|
-
console.log(`[main loop] Adding key for string with newline: "${key}"`)
|
|
655
|
-
keys.push(key)
|
|
656
|
-
} else if (typeof value === "string" && hasNonAscii(value)) {
|
|
657
|
-
console.log(`[main loop] Adding key for non-ASCII string: "${key}"`)
|
|
658
|
-
keys.push(key)
|
|
659
|
-
} else {
|
|
660
|
-
console.log(`[main loop] Skipping key: "${key}" (no match)`)
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const result = [...new Set(keys)].filter(k => {
|
|
665
|
-
if (k === "") return false
|
|
666
|
-
|
|
667
|
-
// Check if this is a path to an empty object inside an array with only empty elements
|
|
668
|
-
const parts = k.split("/")
|
|
669
|
-
if (parts.length >= 2 && /^\d+$/.test(parts[parts.length - 1])) {
|
|
670
|
-
// This is an array element path like "maps/1"
|
|
671
|
-
const arrayPath = parts.slice(0, -1).join("/")
|
|
672
|
-
let arrayValue = obj
|
|
673
|
-
|
|
674
|
-
// Navigate to the array
|
|
675
|
-
for (const part of parts.slice(0, -1)) {
|
|
676
|
-
if (/^\d+$/.test(part)) {
|
|
677
|
-
arrayValue = arrayValue[parseInt(part) - 1]
|
|
678
|
-
} else {
|
|
679
|
-
arrayValue = arrayValue[part]
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Check if this array contains only empty elements
|
|
684
|
-
if (Array.isArray(arrayValue)) {
|
|
685
|
-
const hasOnlyEmptyElements = arrayValue.every(
|
|
686
|
-
item =>
|
|
687
|
-
(Array.isArray(item) && item.length === 0) ||
|
|
688
|
-
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
689
|
-
(typeof item === "string" && item === "")
|
|
690
|
-
)
|
|
691
|
-
|
|
692
|
-
if (hasOnlyEmptyElements) {
|
|
693
|
-
// Filter out paths to individual empty elements
|
|
694
|
-
console.log(`[filter] Removing path to empty element: "${k}"`)
|
|
695
|
-
return false
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
return true
|
|
701
|
-
})
|
|
702
|
-
console.log("\n=== collectBodyKeys RESULT ===")
|
|
703
|
-
console.log("Final bodyKeys:", JSON.stringify(result))
|
|
704
|
-
console.log("=== collectBodyKeys END ===\n")
|
|
705
|
-
|
|
706
|
-
return result
|
|
21
|
+
// Step 1: Process and normalize input values (handle symbols, nested objects/arrays)
|
|
22
|
+
function processInputValues(obj) {
|
|
23
|
+
// Currently this is a no-op, but will be used for input validation/normalization
|
|
24
|
+
return obj
|
|
707
25
|
}
|
|
708
26
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
console.log("Encoding object:", JSON.stringify(obj))
|
|
712
|
-
|
|
713
|
-
const processValue = value => {
|
|
714
|
-
if (typeof value === "symbol") {
|
|
715
|
-
return value.description || "Symbol.for()"
|
|
716
|
-
} else if (Array.isArray(value)) {
|
|
717
|
-
return value.map(processValue)
|
|
718
|
-
} else if (isPojo(value)) {
|
|
719
|
-
const result = {}
|
|
720
|
-
for (const [k, v] of Object.entries(value)) {
|
|
721
|
-
result[k] = processValue(v)
|
|
722
|
-
}
|
|
723
|
-
return result
|
|
724
|
-
}
|
|
725
|
-
return value
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
const processedObj = {}
|
|
729
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
730
|
-
processedObj[k] = processValue(v)
|
|
731
|
-
}
|
|
732
|
-
|
|
27
|
+
// Step 2: Handle empty object case
|
|
28
|
+
function handleEmptyObject(obj) {
|
|
733
29
|
if (Object.keys(obj).length === 0) {
|
|
734
30
|
return { headers: {}, body: undefined }
|
|
735
31
|
}
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
736
34
|
|
|
35
|
+
// Step 3: Handle single field with empty binary
|
|
36
|
+
function handleSingleEmptyBinaryField(obj) {
|
|
737
37
|
const objKeys = Object.keys(obj)
|
|
738
38
|
|
|
739
39
|
if (objKeys.length === 1) {
|
|
@@ -750,14 +50,11 @@ async function encode(obj = {}) {
|
|
|
750
50
|
}
|
|
751
51
|
}
|
|
752
52
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
isBytes(obj.body) &&
|
|
756
|
-
(obj.body.length === 0 || obj.body.byteLength === 0) &&
|
|
757
|
-
objKeys.length > 1
|
|
758
|
-
) {
|
|
759
|
-
}
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
760
55
|
|
|
56
|
+
// Step 4: Handle single field with binary data
|
|
57
|
+
async function handleSingleBinaryField(obj) {
|
|
761
58
|
const hasBodyBinary = obj.body && isBytes(obj.body)
|
|
762
59
|
const otherFields = Object.keys(obj).filter(k => k !== "body")
|
|
763
60
|
|
|
@@ -776,28 +73,18 @@ async function encode(obj = {}) {
|
|
|
776
73
|
return { headers, body: obj.body }
|
|
777
74
|
}
|
|
778
75
|
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Step 5: Handle single field with primitive value (string/number/boolean/null/undefined/symbol)
|
|
80
|
+
async function handleSinglePrimitiveField(obj) {
|
|
81
|
+
const objKeys = Object.keys(obj)
|
|
82
|
+
|
|
779
83
|
if (objKeys.length === 1) {
|
|
780
84
|
const fieldName = objKeys[0]
|
|
781
85
|
const fieldValue = obj[fieldName]
|
|
782
86
|
|
|
783
|
-
if (
|
|
784
|
-
const headers = {}
|
|
785
|
-
const bodyBuffer = toBuffer(fieldValue)
|
|
786
|
-
const bodyArrayBuffer = bodyBuffer.buffer.slice(
|
|
787
|
-
bodyBuffer.byteOffset,
|
|
788
|
-
bodyBuffer.byteOffset + bodyBuffer.byteLength
|
|
789
|
-
)
|
|
790
|
-
|
|
791
|
-
const contentDigest = await sha256(bodyArrayBuffer)
|
|
792
|
-
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
793
|
-
headers["content-digest"] = `sha-256=:${base64}:`
|
|
794
|
-
|
|
795
|
-
if (fieldName !== "body") {
|
|
796
|
-
headers["inline-body-key"] = fieldName
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
return { headers, body: fieldValue }
|
|
800
|
-
} else if (
|
|
87
|
+
if (
|
|
801
88
|
(fieldName === "data" || fieldName === "body") &&
|
|
802
89
|
(typeof fieldValue === "string" ||
|
|
803
90
|
typeof fieldValue === "boolean" ||
|
|
@@ -807,21 +94,7 @@ async function encode(obj = {}) {
|
|
|
807
94
|
typeof fieldValue === "symbol")
|
|
808
95
|
) {
|
|
809
96
|
const headers = {}
|
|
810
|
-
|
|
811
|
-
let bodyContent
|
|
812
|
-
if (typeof fieldValue === "string") {
|
|
813
|
-
bodyContent = fieldValue
|
|
814
|
-
} else if (typeof fieldValue === "boolean") {
|
|
815
|
-
bodyContent = `"${fieldValue}"`
|
|
816
|
-
} else if (typeof fieldValue === "number") {
|
|
817
|
-
bodyContent = String(fieldValue)
|
|
818
|
-
} else if (fieldValue === null) {
|
|
819
|
-
bodyContent = '"null"'
|
|
820
|
-
} else if (fieldValue === undefined) {
|
|
821
|
-
bodyContent = '"undefined"'
|
|
822
|
-
} else if (typeof fieldValue === "symbol") {
|
|
823
|
-
bodyContent = `"${fieldValue.description || "Symbol.for()"}"`
|
|
824
|
-
}
|
|
97
|
+
const bodyContent = encodePrimitiveContent(fieldValue)
|
|
825
98
|
|
|
826
99
|
const encoder = new TextEncoder()
|
|
827
100
|
const encoded = encoder.encode(bodyContent)
|
|
@@ -829,16 +102,9 @@ async function encode(obj = {}) {
|
|
|
829
102
|
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
830
103
|
headers["content-digest"] = `sha-256=:${base64}:`
|
|
831
104
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
fieldValue === undefined ||
|
|
836
|
-
typeof fieldValue === "symbol"
|
|
837
|
-
) {
|
|
838
|
-
headers["ao-types"] = `${fieldName.toLowerCase()}="atom"`
|
|
839
|
-
} else if (typeof fieldValue === "number") {
|
|
840
|
-
headers["ao-types"] =
|
|
841
|
-
`${fieldName.toLowerCase()}="${Number.isInteger(fieldValue) ? "integer" : "float"}"`
|
|
105
|
+
const aoType = getAoType(fieldValue)
|
|
106
|
+
if (aoType === "atom" || aoType === "integer" || aoType === "float") {
|
|
107
|
+
headers["ao-types"] = `${fieldName.toLowerCase()}="${aoType}"`
|
|
842
108
|
}
|
|
843
109
|
|
|
844
110
|
if (fieldName !== "body") {
|
|
@@ -846,7 +112,52 @@ async function encode(obj = {}) {
|
|
|
846
112
|
}
|
|
847
113
|
|
|
848
114
|
return { headers, body: bodyContent }
|
|
849
|
-
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Step 6a: Handle single field with non-empty binary (not body field)
|
|
122
|
+
async function handleSingleNonEmptyBinaryField(obj) {
|
|
123
|
+
const objKeys = Object.keys(obj)
|
|
124
|
+
|
|
125
|
+
if (objKeys.length === 1) {
|
|
126
|
+
const fieldName = objKeys[0]
|
|
127
|
+
const fieldValue = obj[fieldName]
|
|
128
|
+
|
|
129
|
+
if (isBytes(fieldValue) && fieldValue.length > 0) {
|
|
130
|
+
const headers = {}
|
|
131
|
+
const bodyBuffer = toBuffer(fieldValue)
|
|
132
|
+
const bodyArrayBuffer = bodyBuffer.buffer.slice(
|
|
133
|
+
bodyBuffer.byteOffset,
|
|
134
|
+
bodyBuffer.byteOffset + bodyBuffer.byteLength
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
const contentDigest = await sha256(bodyArrayBuffer)
|
|
138
|
+
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
139
|
+
headers["content-digest"] = `sha-256=:${base64}:`
|
|
140
|
+
|
|
141
|
+
if (fieldName !== "body") {
|
|
142
|
+
headers["inline-body-key"] = fieldName
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { headers, body: fieldValue }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Step 6: Handle single field with non-ASCII string
|
|
153
|
+
async function handleSingleNonAsciiStringField(obj) {
|
|
154
|
+
const objKeys = Object.keys(obj)
|
|
155
|
+
|
|
156
|
+
if (objKeys.length === 1) {
|
|
157
|
+
const fieldName = objKeys[0]
|
|
158
|
+
const fieldValue = obj[fieldName]
|
|
159
|
+
|
|
160
|
+
if (typeof fieldValue === "string" && hasNonAscii(fieldValue)) {
|
|
850
161
|
const headers = {}
|
|
851
162
|
const encoder = new TextEncoder()
|
|
852
163
|
const encoded = encoder.encode(fieldValue)
|
|
@@ -862,11 +173,16 @@ async function encode(obj = {}) {
|
|
|
862
173
|
}
|
|
863
174
|
}
|
|
864
175
|
|
|
865
|
-
|
|
866
|
-
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
867
178
|
|
|
868
|
-
|
|
179
|
+
// Step 7: Collect all keys that need to go in body
|
|
180
|
+
function collectBodyKeysStep(obj) {
|
|
181
|
+
return collectBodyKeys(obj)
|
|
182
|
+
}
|
|
869
183
|
|
|
184
|
+
// Step 8: Process fields that can go in headers
|
|
185
|
+
function processHeaderFields(obj, bodyKeys, headers, headerTypes) {
|
|
870
186
|
for (const [key, value] of Object.entries(obj)) {
|
|
871
187
|
const needsBody =
|
|
872
188
|
bodyKeys.includes(key) || bodyKeys.some(k => k.startsWith(`${key}/`))
|
|
@@ -919,29 +235,15 @@ async function encode(obj = {}) {
|
|
|
919
235
|
headerTypes.push(`${key.toLowerCase()}="empty-message"`)
|
|
920
236
|
}
|
|
921
237
|
} else {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
headerTypes.push(`${key.toLowerCase()}="
|
|
926
|
-
} else if (Array.isArray(value) && value.length === 0) {
|
|
927
|
-
headerTypes.push(`${key.toLowerCase()}="empty-list"`)
|
|
928
|
-
} else if (isPojo(value) && Object.keys(value).length === 0) {
|
|
929
|
-
headerTypes.push(`${key.toLowerCase()}="empty-message"`)
|
|
930
|
-
} else if (
|
|
931
|
-
typeof value === "boolean" ||
|
|
932
|
-
value === null ||
|
|
933
|
-
value === undefined ||
|
|
934
|
-
typeof value === "symbol"
|
|
935
|
-
) {
|
|
936
|
-
headerTypes.push(`${key.toLowerCase()}="atom"`)
|
|
937
|
-
} else if (typeof value === "number") {
|
|
938
|
-
headerTypes.push(
|
|
939
|
-
`${key.toLowerCase()}="${Number.isInteger(value) ? "integer" : "float"}"`
|
|
940
|
-
)
|
|
238
|
+
// Fields that need body still get type annotations
|
|
239
|
+
const aoType = getAoType(value)
|
|
240
|
+
if (aoType) {
|
|
241
|
+
headerTypes.push(`${key.toLowerCase()}="${aoType}"`)
|
|
941
242
|
}
|
|
942
243
|
}
|
|
943
244
|
}
|
|
944
245
|
|
|
246
|
+
// Second pass for array type annotations
|
|
945
247
|
for (const [key, value] of Object.entries(obj)) {
|
|
946
248
|
if (Array.isArray(value)) {
|
|
947
249
|
if (
|
|
@@ -954,9 +256,11 @@ async function encode(obj = {}) {
|
|
|
954
256
|
}
|
|
955
257
|
}
|
|
956
258
|
}
|
|
259
|
+
}
|
|
957
260
|
|
|
261
|
+
// Step 9: Handle case where all body keys are empty binaries
|
|
262
|
+
function handleAllEmptyBinaryBodyKeys(obj, bodyKeys, headers, headerTypes) {
|
|
958
263
|
if (bodyKeys.length === 0) {
|
|
959
|
-
console.log("No bodyKeys, returning headers only")
|
|
960
264
|
if (headerTypes.length > 0) {
|
|
961
265
|
headers["ao-types"] = headerTypes.sort().join(", ")
|
|
962
266
|
}
|
|
@@ -964,19 +268,7 @@ async function encode(obj = {}) {
|
|
|
964
268
|
}
|
|
965
269
|
|
|
966
270
|
const allBodyKeysAreEmptyBinaries = bodyKeys.every(key => {
|
|
967
|
-
const
|
|
968
|
-
let value = obj
|
|
969
|
-
for (const part of pathParts) {
|
|
970
|
-
if (/^\d+$/.test(part)) {
|
|
971
|
-
const index = parseInt(part) - 1
|
|
972
|
-
console.log(
|
|
973
|
-
`[Body part] Getting array element at index ${index} from part ${part}`
|
|
974
|
-
)
|
|
975
|
-
value = value[index]
|
|
976
|
-
} else {
|
|
977
|
-
value = value[part]
|
|
978
|
-
}
|
|
979
|
-
}
|
|
271
|
+
const value = getValueByPath(obj, key)
|
|
980
272
|
return isBytes(value) && (value.length === 0 || value.byteLength === 0)
|
|
981
273
|
})
|
|
982
274
|
|
|
@@ -987,26 +279,23 @@ async function encode(obj = {}) {
|
|
|
987
279
|
return { headers, body: undefined }
|
|
988
280
|
}
|
|
989
281
|
|
|
282
|
+
return null
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Step 10: Handle single body key optimization
|
|
286
|
+
async function handleSingleBodyKeyOptimization(
|
|
287
|
+
obj,
|
|
288
|
+
bodyKeys,
|
|
289
|
+
headers,
|
|
290
|
+
headerTypes
|
|
291
|
+
) {
|
|
990
292
|
if (bodyKeys.length === 1) {
|
|
991
293
|
const singleKey = bodyKeys[0]
|
|
992
|
-
const
|
|
993
|
-
let value = obj
|
|
994
|
-
for (const part of pathParts) {
|
|
995
|
-
if (/^\d+$/.test(part)) {
|
|
996
|
-
value = value[parseInt(part) - 1]
|
|
997
|
-
} else {
|
|
998
|
-
value = value[part]
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
294
|
+
const value = getValueByPath(obj, singleKey)
|
|
1001
295
|
|
|
1002
296
|
const otherFieldsAreEmpty = Object.entries(obj).every(([key, val]) => {
|
|
1003
297
|
if (key === singleKey) return true
|
|
1004
|
-
return (
|
|
1005
|
-
(Array.isArray(val) && val.length === 0) ||
|
|
1006
|
-
(isPojo(val) && Object.keys(val).length === 0) ||
|
|
1007
|
-
(isBytes(val) && (val.length === 0 || val.byteLength === 0)) ||
|
|
1008
|
-
(typeof val === "string" && val.length === 0)
|
|
1009
|
-
)
|
|
298
|
+
return isEmpty(val)
|
|
1010
299
|
})
|
|
1011
300
|
|
|
1012
301
|
if (otherFieldsAreEmpty && isBytes(value) && value.length > 0) {
|
|
@@ -1032,23 +321,23 @@ async function encode(obj = {}) {
|
|
|
1032
321
|
}
|
|
1033
322
|
}
|
|
1034
323
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
324
|
+
return null
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Step 11: Sort body keys
|
|
328
|
+
function sortBodyKeys(bodyKeys) {
|
|
329
|
+
return bodyKeys.sort((a, b) => {
|
|
330
|
+
const aIsArrayElement = /\/\d+$/.test(a)
|
|
1038
331
|
const bIsArrayElement = /\/\d+$/.test(b)
|
|
1039
332
|
const aBase = a.split("/")[0]
|
|
1040
333
|
const bBase = b.split("/")[0]
|
|
1041
|
-
|
|
1042
|
-
// If both are for the same array
|
|
1043
334
|
if (aBase === bBase) {
|
|
1044
|
-
// Main array comes before element parts
|
|
1045
335
|
if (!aIsArrayElement && bIsArrayElement) {
|
|
1046
|
-
return -1
|
|
336
|
+
return -1
|
|
1047
337
|
}
|
|
1048
338
|
if (aIsArrayElement && !bIsArrayElement) {
|
|
1049
|
-
return 1
|
|
339
|
+
return 1
|
|
1050
340
|
}
|
|
1051
|
-
// Both are elements - sort by index
|
|
1052
341
|
if (aIsArrayElement && bIsArrayElement) {
|
|
1053
342
|
const aIndex = parseInt(a.split("/")[1])
|
|
1054
343
|
const bIndex = parseInt(b.split("/")[1])
|
|
@@ -1056,13 +345,13 @@ async function encode(obj = {}) {
|
|
|
1056
345
|
}
|
|
1057
346
|
return a.localeCompare(b)
|
|
1058
347
|
}
|
|
1059
|
-
|
|
1060
|
-
// Different arrays, sort by base name
|
|
1061
348
|
return a.localeCompare(b)
|
|
1062
349
|
})
|
|
350
|
+
}
|
|
1063
351
|
|
|
1064
|
-
|
|
1065
|
-
|
|
352
|
+
// Step 12: Check for special data/body case
|
|
353
|
+
function checkSpecialDataBodyCase(obj, sortedBodyKeys) {
|
|
354
|
+
return (
|
|
1066
355
|
sortedBodyKeys.includes("data") &&
|
|
1067
356
|
sortedBodyKeys.includes("body") &&
|
|
1068
357
|
obj.data &&
|
|
@@ -1071,771 +360,707 @@ async function encode(obj = {}) {
|
|
|
1071
360
|
obj.body &&
|
|
1072
361
|
obj.body.data &&
|
|
1073
362
|
isBytes(obj.body.data)
|
|
363
|
+
)
|
|
364
|
+
}
|
|
1074
365
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
if (
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
366
|
+
// Step 13.2.2: Handle empty string in nested path
|
|
367
|
+
function handleEmptyStringInNestedPath(bodyKey, value, pathParts) {
|
|
368
|
+
if (typeof value === "string" && value === "" && pathParts.length > 1) {
|
|
369
|
+
const lines = []
|
|
370
|
+
lines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
371
|
+
lines.push("")
|
|
372
|
+
lines.push("")
|
|
373
|
+
return new Blob([lines.join("\r\n")])
|
|
1081
374
|
}
|
|
375
|
+
return null
|
|
376
|
+
}
|
|
1082
377
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
378
|
+
// Step 13.2.3.2: Handle arrays with only empty elements
|
|
379
|
+
function handleArrayWithOnlyEmptyElements(
|
|
380
|
+
bodyKey,
|
|
381
|
+
value,
|
|
382
|
+
headers,
|
|
383
|
+
sortedBodyKeys
|
|
384
|
+
) {
|
|
385
|
+
const fieldLines = []
|
|
386
|
+
const partTypes = []
|
|
387
|
+
|
|
388
|
+
value.forEach((item, idx) => {
|
|
389
|
+
const index = idx + 1
|
|
390
|
+
const itemType = getAoType(item)
|
|
391
|
+
if (itemType) {
|
|
392
|
+
partTypes.push(`${index}="${itemType}"`)
|
|
393
|
+
}
|
|
394
|
+
})
|
|
1086
395
|
|
|
1087
|
-
const
|
|
396
|
+
const isInline = bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
1088
397
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
398
|
+
if (isInline) {
|
|
399
|
+
const orderedLines = []
|
|
400
|
+
if (partTypes.length > 0) {
|
|
401
|
+
orderedLines.push(
|
|
402
|
+
`ao-types: ${sortTypeAnnotations(partTypes).join(", ")}`
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
orderedLines.push("content-disposition: inline")
|
|
406
|
+
orderedLines.push("")
|
|
407
|
+
return new Blob([orderedLines.join("\r\n")])
|
|
408
|
+
} else {
|
|
409
|
+
const orderedLines = []
|
|
410
|
+
if (partTypes.length > 0) {
|
|
411
|
+
orderedLines.push(
|
|
412
|
+
`ao-types: ${sortTypeAnnotations(partTypes).join(", ")}`
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
orderedLines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
1092
416
|
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1095
|
-
|
|
417
|
+
const isLastBodyPart =
|
|
418
|
+
sortedBodyKeys.indexOf(bodyKey) === sortedBodyKeys.length - 1
|
|
419
|
+
const hasOnlyTypes = partTypes.length > 0 && fieldLines.length === 0
|
|
1096
420
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
421
|
+
if (isLastBodyPart && hasOnlyTypes) {
|
|
422
|
+
return new Blob([orderedLines.join("\r\n")])
|
|
423
|
+
} else {
|
|
424
|
+
orderedLines.push("")
|
|
425
|
+
return new Blob([orderedLines.join("\r\n")])
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
1100
429
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
430
|
+
// Step 13.2.3.3: Build indices with own parts
|
|
431
|
+
function buildIndicesWithOwnParts(bodyKey, sortedBodyKeys) {
|
|
432
|
+
const indicesWithOwnParts = new Set()
|
|
433
|
+
sortedBodyKeys.forEach(key => {
|
|
434
|
+
if (key.startsWith(bodyKey + "/")) {
|
|
435
|
+
const subPath = key.substring(bodyKey.length + 1)
|
|
436
|
+
const match = subPath.match(/^(\d+)/)
|
|
437
|
+
if (match) {
|
|
438
|
+
indicesWithOwnParts.add(parseInt(match[1]))
|
|
1105
439
|
}
|
|
1106
440
|
}
|
|
441
|
+
})
|
|
442
|
+
return indicesWithOwnParts
|
|
443
|
+
}
|
|
1107
444
|
|
|
1108
|
-
|
|
445
|
+
// Step 13.2.3.4: Process array items
|
|
446
|
+
function processArrayItems(
|
|
447
|
+
value,
|
|
448
|
+
indicesWithOwnParts,
|
|
449
|
+
hasNestedObjectParts,
|
|
450
|
+
pathParts
|
|
451
|
+
) {
|
|
452
|
+
const fieldLines = []
|
|
453
|
+
const partTypes = []
|
|
454
|
+
|
|
455
|
+
if (hasNestedObjectParts) {
|
|
456
|
+
value.forEach((item, idx) => {
|
|
457
|
+
const index = idx + 1
|
|
458
|
+
if (Array.isArray(item)) {
|
|
459
|
+
partTypes.push(`${index}="list"`)
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
}
|
|
1109
463
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
464
|
+
value.forEach((item, idx) => {
|
|
465
|
+
const index = idx + 1
|
|
466
|
+
|
|
467
|
+
if (indicesWithOwnParts.has(index)) {
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
if (
|
|
471
|
+
hasNestedObjectParts &&
|
|
472
|
+
Array.isArray(item) &&
|
|
473
|
+
item.some(subItem => isPojo(subItem))
|
|
474
|
+
) {
|
|
475
|
+
return
|
|
1118
476
|
}
|
|
1119
477
|
|
|
1120
|
-
if (
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
)
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
478
|
+
if (typeof item === "string" && item === "") {
|
|
479
|
+
partTypes.push(`${index}="empty-binary"`)
|
|
480
|
+
} else if (isPojo(item) && Object.keys(item).length === 0) {
|
|
481
|
+
partTypes.push(`${index}="empty-message"`)
|
|
482
|
+
} else if (isPojo(item)) {
|
|
483
|
+
// Non-empty objects are handled elsewhere
|
|
484
|
+
} else if (Array.isArray(item)) {
|
|
485
|
+
if (item.length === 0) {
|
|
486
|
+
partTypes.push(`${index}="empty-list"`)
|
|
487
|
+
} else {
|
|
488
|
+
partTypes.push(`${index}="list"`)
|
|
489
|
+
const encodedItems = item
|
|
490
|
+
.map(subItem => {
|
|
491
|
+
if (typeof subItem === "number") {
|
|
492
|
+
if (Number.isInteger(subItem)) {
|
|
493
|
+
return `"(ao-type-integer) ${subItem}"`
|
|
494
|
+
} else {
|
|
495
|
+
return `"(ao-type-float) ${formatFloat(subItem)}"`
|
|
496
|
+
}
|
|
497
|
+
} else if (typeof subItem === "string") {
|
|
498
|
+
return `"${subItem}"`
|
|
499
|
+
} else if (subItem === null) {
|
|
500
|
+
return `"(ao-type-atom) \\"null\\""`
|
|
501
|
+
} else if (subItem === undefined) {
|
|
502
|
+
return `"(ao-type-atom) \\"undefined\\""`
|
|
503
|
+
} else if (typeof subItem === "symbol") {
|
|
504
|
+
const desc = subItem.description || "Symbol.for()"
|
|
505
|
+
return `"(ao-type-atom) \\"${desc}\\""`
|
|
506
|
+
} else if (typeof subItem === "boolean") {
|
|
507
|
+
return `"(ao-type-atom) \\"${subItem}\\""`
|
|
508
|
+
} else if (Array.isArray(subItem)) {
|
|
509
|
+
return encodeArrayItem(subItem)
|
|
510
|
+
} else if (isBytes(subItem)) {
|
|
511
|
+
const buffer = toBuffer(subItem)
|
|
512
|
+
if (buffer.length === 0 || buffer.byteLength === 0) {
|
|
513
|
+
return `""`
|
|
514
|
+
}
|
|
515
|
+
return `"(ao-type-binary)"`
|
|
516
|
+
} else if (isPojo(subItem)) {
|
|
517
|
+
const json = JSON.stringify(subItem)
|
|
518
|
+
const escaped = json.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
519
|
+
return `"(ao-type-map) ${escaped}"`
|
|
520
|
+
} else {
|
|
521
|
+
return `"${String(subItem)}"`
|
|
522
|
+
}
|
|
523
|
+
})
|
|
524
|
+
.join(", ")
|
|
525
|
+
fieldLines.push(`${index}: ${encodedItems}`)
|
|
1143
526
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
value.forEach((item, idx) => {
|
|
1152
|
-
const index = idx + 1
|
|
1153
|
-
if (Array.isArray(item) && item.length === 0) {
|
|
1154
|
-
partTypes.push(`${index}="empty-list"`)
|
|
1155
|
-
} else if (isPojo(item) && Object.keys(item).length === 0) {
|
|
1156
|
-
partTypes.push(`${index}="empty-message"`)
|
|
1157
|
-
} else if (typeof item === "string" && item === "") {
|
|
1158
|
-
partTypes.push(`${index}="empty-binary"`)
|
|
1159
|
-
}
|
|
1160
|
-
})
|
|
1161
|
-
|
|
1162
|
-
const isInline =
|
|
1163
|
-
bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
1164
|
-
|
|
1165
|
-
if (isInline) {
|
|
1166
|
-
const orderedLines = []
|
|
1167
|
-
if (partTypes.length > 0) {
|
|
1168
|
-
orderedLines.push(
|
|
1169
|
-
`ao-types: ${partTypes
|
|
1170
|
-
.sort((a, b) => {
|
|
1171
|
-
const aNum = parseInt(a.split("=")[0])
|
|
1172
|
-
const bNum = parseInt(b.split("=")[0])
|
|
1173
|
-
return aNum - bNum
|
|
1174
|
-
})
|
|
1175
|
-
.join(", ")}`
|
|
1176
|
-
)
|
|
1177
|
-
}
|
|
1178
|
-
orderedLines.push("content-disposition: inline")
|
|
1179
|
-
orderedLines.push("")
|
|
1180
|
-
bodyParts.push(new Blob([orderedLines.join("\r\n")]))
|
|
1181
|
-
} else {
|
|
1182
|
-
const orderedLines = []
|
|
1183
|
-
if (partTypes.length > 0) {
|
|
1184
|
-
orderedLines.push(
|
|
1185
|
-
`ao-types: ${partTypes
|
|
1186
|
-
.sort((a, b) => {
|
|
1187
|
-
const aNum = parseInt(a.split("=")[0])
|
|
1188
|
-
const bNum = parseInt(b.split("=")[0])
|
|
1189
|
-
return aNum - bNum
|
|
1190
|
-
})
|
|
1191
|
-
.join(", ")}`
|
|
1192
|
-
)
|
|
1193
|
-
}
|
|
1194
|
-
orderedLines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
1195
|
-
|
|
1196
|
-
// Check if this is the last body part
|
|
1197
|
-
const isLastBodyPart =
|
|
1198
|
-
sortedBodyKeys.indexOf(bodyKey) === sortedBodyKeys.length - 1
|
|
1199
|
-
const hasOnlyTypes = partTypes.length > 0 && fieldLines.length === 0
|
|
1200
|
-
|
|
1201
|
-
if (isLastBodyPart && hasOnlyTypes) {
|
|
1202
|
-
// Don't add empty line for last part with only types
|
|
1203
|
-
bodyParts.push(new Blob([orderedLines.join("\r\n")]))
|
|
1204
|
-
} else {
|
|
1205
|
-
orderedLines.push("")
|
|
1206
|
-
bodyParts.push(new Blob([orderedLines.join("\r\n")]))
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
continue
|
|
527
|
+
} else if (typeof item === "number") {
|
|
528
|
+
if (Number.isInteger(item)) {
|
|
529
|
+
partTypes.push(`${index}="integer"`)
|
|
530
|
+
fieldLines.push(`${index}: ${item}`)
|
|
531
|
+
} else {
|
|
532
|
+
partTypes.push(`${index}="float"`)
|
|
533
|
+
fieldLines.push(`${index}: ${formatFloat(item)}`)
|
|
1210
534
|
}
|
|
535
|
+
} else if (typeof item === "string") {
|
|
536
|
+
fieldLines.push(`${index}: ${item}`)
|
|
537
|
+
} else if (
|
|
538
|
+
item === null ||
|
|
539
|
+
item === undefined ||
|
|
540
|
+
typeof item === "symbol" ||
|
|
541
|
+
typeof item === "boolean"
|
|
542
|
+
) {
|
|
543
|
+
partTypes.push(`${index}="atom"`)
|
|
544
|
+
if (item === null) {
|
|
545
|
+
fieldLines.push(`${index}: null`)
|
|
546
|
+
} else if (item === undefined) {
|
|
547
|
+
fieldLines.push(`${index}: undefined`)
|
|
548
|
+
} else if (typeof item === "symbol") {
|
|
549
|
+
const desc = item.description || "Symbol.for()"
|
|
550
|
+
fieldLines.push(`${index}: ${desc}`)
|
|
551
|
+
} else {
|
|
552
|
+
fieldLines.push(`${index}: ${item}`)
|
|
553
|
+
}
|
|
554
|
+
} else if (isBytes(item)) {
|
|
555
|
+
const buffer = toBuffer(item)
|
|
556
|
+
if (buffer.length === 0) {
|
|
557
|
+
partTypes.push(`${index}="empty-binary"`)
|
|
558
|
+
} else {
|
|
559
|
+
partTypes.push(`${index}="binary"`)
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
})
|
|
1211
563
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
sortedBodyKeys.forEach(key => {
|
|
1215
|
-
if (key.startsWith(bodyKey + "/")) {
|
|
1216
|
-
const subPath = key.substring(bodyKey.length + 1)
|
|
1217
|
-
const match = subPath.match(/^(\d+)/)
|
|
1218
|
-
if (match) {
|
|
1219
|
-
indicesWithOwnParts.add(parseInt(match[1]))
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
})
|
|
1223
|
-
|
|
1224
|
-
// Check if this array contains sub-arrays with objects that have their own parts
|
|
1225
|
-
const hasNestedObjectParts = sortedBodyKeys.some(
|
|
1226
|
-
key =>
|
|
1227
|
-
key.startsWith(bodyKey + "/") &&
|
|
1228
|
-
key.split("/").length > pathParts.length + 1
|
|
1229
|
-
)
|
|
564
|
+
return { fieldLines, partTypes }
|
|
565
|
+
}
|
|
1230
566
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
// For arrays that contain sub-arrays with objects, we need to add type info for the sub-arrays
|
|
1235
|
-
if (hasNestedObjectParts) {
|
|
1236
|
-
value.forEach((item, idx) => {
|
|
1237
|
-
const index = idx + 1
|
|
1238
|
-
if (Array.isArray(item)) {
|
|
1239
|
-
partTypes.push(`${index}="list"`)
|
|
1240
|
-
}
|
|
1241
|
-
})
|
|
1242
|
-
}
|
|
567
|
+
// Step 13.2.3.5: Create array body part
|
|
568
|
+
function createArrayBodyPart(bodyKey, fieldLines, partTypes, headers) {
|
|
569
|
+
const isInline = bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
1243
570
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
571
|
+
if (isInline) {
|
|
572
|
+
const orderedLines = []
|
|
573
|
+
if (partTypes.length > 0) {
|
|
574
|
+
orderedLines.push(
|
|
575
|
+
`ao-types: ${sortTypeAnnotations(partTypes).join(", ")}`
|
|
1247
576
|
)
|
|
1248
|
-
|
|
1249
|
-
|
|
577
|
+
}
|
|
578
|
+
orderedLines.push("content-disposition: inline")
|
|
579
|
+
if (fieldLines.length > 0) {
|
|
580
|
+
orderedLines.push("")
|
|
581
|
+
for (const line of fieldLines) {
|
|
582
|
+
orderedLines.push(line)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return new Blob([orderedLines.join("\r\n") + "\r\n"])
|
|
586
|
+
} else {
|
|
587
|
+
const orderedLines = []
|
|
588
|
+
if (partTypes.length > 0) {
|
|
589
|
+
orderedLines.push(
|
|
590
|
+
`ao-types: ${sortTypeAnnotations(partTypes).join(", ")}`
|
|
1250
591
|
)
|
|
592
|
+
}
|
|
593
|
+
orderedLines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
594
|
+
for (const line of fieldLines) {
|
|
595
|
+
orderedLines.push(line)
|
|
596
|
+
}
|
|
597
|
+
if (fieldLines.length > 0) {
|
|
598
|
+
return new Blob([orderedLines.join("\r\n") + "\r\n"])
|
|
599
|
+
} else {
|
|
600
|
+
return new Blob([orderedLines.join("\r\n")])
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
1251
604
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
return Object.values(item).every(
|
|
1256
|
-
v =>
|
|
1257
|
-
(typeof v === "string" && v === "") ||
|
|
1258
|
-
(Array.isArray(v) && v.length === 0) ||
|
|
1259
|
-
(isPojo(v) && Object.keys(v).length === 0)
|
|
1260
|
-
)
|
|
1261
|
-
})
|
|
1262
|
-
|
|
1263
|
-
const isMixedArray =
|
|
1264
|
-
hasObjects &&
|
|
1265
|
-
(hasEmptyStrings || hasEmptyObjects) &&
|
|
1266
|
-
!hasObjectsWithOnlyEmptyValues
|
|
1267
|
-
|
|
1268
|
-
// Process ALL items for type information
|
|
1269
|
-
value.forEach((item, idx) => {
|
|
1270
|
-
const index = idx + 1
|
|
1271
|
-
|
|
1272
|
-
// Skip type info for elements that have their own parts
|
|
1273
|
-
if (indicesWithOwnParts.has(index)) {
|
|
1274
|
-
return
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
// If we have nested object parts and this is an array with objects, skip processing it inline
|
|
1278
|
-
if (
|
|
1279
|
-
hasNestedObjectParts &&
|
|
1280
|
-
Array.isArray(item) &&
|
|
1281
|
-
item.some(subItem => isPojo(subItem))
|
|
1282
|
-
) {
|
|
1283
|
-
// Type info already added above
|
|
1284
|
-
return
|
|
1285
|
-
}
|
|
605
|
+
// Step 13.2.3: Handle array values
|
|
606
|
+
function handleArrayValue(bodyKey, value, headers, sortedBodyKeys, pathParts) {
|
|
607
|
+
const arrayInfo = analyzeArray(value)
|
|
1286
608
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
partTypes.push(`${index}="empty-binary"`)
|
|
1291
|
-
} else if (isPojo(item) && Object.keys(item).length === 0) {
|
|
1292
|
-
// Empty objects don't get field lines but do get type annotations
|
|
1293
|
-
partTypes.push(`${index}="empty-message"`)
|
|
1294
|
-
} else if (isPojo(item)) {
|
|
1295
|
-
// Non-empty objects might have parts
|
|
1296
|
-
} else if (Array.isArray(item)) {
|
|
1297
|
-
if (item.length === 0) {
|
|
1298
|
-
// Empty arrays don't get field lines but do get type annotations
|
|
1299
|
-
partTypes.push(`${index}="empty-list"`)
|
|
1300
|
-
} else {
|
|
1301
|
-
partTypes.push(`${index}="list"`)
|
|
1302
|
-
// Encode array
|
|
1303
|
-
const encodedItems = item
|
|
1304
|
-
.map(subItem => {
|
|
1305
|
-
if (typeof subItem === "number") {
|
|
1306
|
-
if (Number.isInteger(subItem)) {
|
|
1307
|
-
return `"(ao-type-integer) ${subItem}"`
|
|
1308
|
-
} else {
|
|
1309
|
-
return `"(ao-type-float) ${formatFloat(subItem)}"`
|
|
1310
|
-
}
|
|
1311
|
-
} else if (typeof subItem === "string") {
|
|
1312
|
-
return `"${subItem}"`
|
|
1313
|
-
} else if (subItem === null) {
|
|
1314
|
-
return `"(ao-type-atom) \\"null\\""`
|
|
1315
|
-
} else if (subItem === undefined) {
|
|
1316
|
-
return `"(ao-type-atom) \\"undefined\\""`
|
|
1317
|
-
} else if (typeof subItem === "symbol") {
|
|
1318
|
-
const desc = subItem.description || "Symbol.for()"
|
|
1319
|
-
return `"(ao-type-atom) \\"${desc}\\""`
|
|
1320
|
-
} else if (typeof subItem === "boolean") {
|
|
1321
|
-
return `"(ao-type-atom) \\"${subItem}\\""`
|
|
1322
|
-
} else if (Array.isArray(subItem)) {
|
|
1323
|
-
// Use the full encodeArrayItem for nested arrays
|
|
1324
|
-
return encodeArrayItem(subItem)
|
|
1325
|
-
} else if (isBytes(subItem)) {
|
|
1326
|
-
const buffer = toBuffer(subItem)
|
|
1327
|
-
if (buffer.length === 0 || buffer.byteLength === 0) {
|
|
1328
|
-
return `""`
|
|
1329
|
-
}
|
|
1330
|
-
return `"(ao-type-binary)"`
|
|
1331
|
-
} else if (isPojo(subItem)) {
|
|
1332
|
-
const json = JSON.stringify(subItem)
|
|
1333
|
-
const escaped = json
|
|
1334
|
-
.replace(/\\/g, "\\\\")
|
|
1335
|
-
.replace(/"/g, '\\"')
|
|
1336
|
-
return `"(ao-type-map) ${escaped}"`
|
|
1337
|
-
} else {
|
|
1338
|
-
return `"${String(subItem)}"`
|
|
1339
|
-
}
|
|
1340
|
-
})
|
|
1341
|
-
.join(", ")
|
|
1342
|
-
fieldLines.push(`${index}: ${encodedItems}`)
|
|
1343
|
-
}
|
|
1344
|
-
} else if (typeof item === "number") {
|
|
1345
|
-
if (Number.isInteger(item)) {
|
|
1346
|
-
partTypes.push(`${index}="integer"`)
|
|
1347
|
-
fieldLines.push(`${index}: ${item}`)
|
|
1348
|
-
} else {
|
|
1349
|
-
partTypes.push(`${index}="float"`)
|
|
1350
|
-
fieldLines.push(`${index}: ${formatFloat(item)}`)
|
|
1351
|
-
}
|
|
1352
|
-
} else if (typeof item === "string") {
|
|
1353
|
-
// Non-empty strings just get field lines, no type annotation
|
|
1354
|
-
fieldLines.push(`${index}: ${item}`)
|
|
1355
|
-
} else if (
|
|
1356
|
-
item === null ||
|
|
1357
|
-
item === undefined ||
|
|
1358
|
-
typeof item === "symbol" ||
|
|
1359
|
-
typeof item === "boolean"
|
|
1360
|
-
) {
|
|
1361
|
-
partTypes.push(`${index}="atom"`)
|
|
1362
|
-
if (item === null) {
|
|
1363
|
-
fieldLines.push(`${index}: null`)
|
|
1364
|
-
} else if (item === undefined) {
|
|
1365
|
-
fieldLines.push(`${index}: undefined`)
|
|
1366
|
-
} else if (typeof item === "symbol") {
|
|
1367
|
-
const desc = item.description || "Symbol.for()"
|
|
1368
|
-
fieldLines.push(`${index}: ${desc}`)
|
|
1369
|
-
} else {
|
|
1370
|
-
fieldLines.push(`${index}: ${item}`)
|
|
1371
|
-
}
|
|
1372
|
-
} else if (isBytes(item)) {
|
|
1373
|
-
const buffer = toBuffer(item)
|
|
1374
|
-
if (buffer.length === 0) {
|
|
1375
|
-
partTypes.push(`${index}="empty-binary"`)
|
|
1376
|
-
} else {
|
|
1377
|
-
partTypes.push(`${index}="binary"`)
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
})
|
|
1381
|
-
|
|
1382
|
-
const isInline =
|
|
1383
|
-
bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
1384
|
-
|
|
1385
|
-
if (isInline) {
|
|
1386
|
-
const orderedLines = []
|
|
1387
|
-
|
|
1388
|
-
if (partTypes.length > 0) {
|
|
1389
|
-
orderedLines.push(
|
|
1390
|
-
`ao-types: ${partTypes
|
|
1391
|
-
.sort((a, b) => {
|
|
1392
|
-
const aNum = parseInt(a.split("=")[0])
|
|
1393
|
-
const bNum = parseInt(b.split("=")[0])
|
|
1394
|
-
return aNum - bNum
|
|
1395
|
-
})
|
|
1396
|
-
.join(", ")}`
|
|
1397
|
-
)
|
|
1398
|
-
}
|
|
609
|
+
if (arrayInfo.hasOnlyNonEmptyObjects) {
|
|
610
|
+
return null
|
|
611
|
+
}
|
|
1399
612
|
|
|
1400
|
-
|
|
613
|
+
if (arrayInfo.hasOnlyEmptyElements) {
|
|
614
|
+
return handleArrayWithOnlyEmptyElements(
|
|
615
|
+
bodyKey,
|
|
616
|
+
value,
|
|
617
|
+
headers,
|
|
618
|
+
sortedBodyKeys
|
|
619
|
+
)
|
|
620
|
+
}
|
|
1401
621
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
622
|
+
const indicesWithOwnParts = buildIndicesWithOwnParts(bodyKey, sortedBodyKeys)
|
|
623
|
+
const hasNestedObjectParts = sortedBodyKeys.some(
|
|
624
|
+
key =>
|
|
625
|
+
key.startsWith(bodyKey + "/") &&
|
|
626
|
+
key.split("/").length > pathParts.length + 1
|
|
627
|
+
)
|
|
1408
628
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
.sort((a, b) => {
|
|
1418
|
-
const aNum = parseInt(a.split("=")[0])
|
|
1419
|
-
const bNum = parseInt(b.split("=")[0])
|
|
1420
|
-
return aNum - bNum
|
|
1421
|
-
})
|
|
1422
|
-
.join(", ")}`
|
|
1423
|
-
)
|
|
1424
|
-
}
|
|
629
|
+
const { fieldLines, partTypes } = processArrayItems(
|
|
630
|
+
value,
|
|
631
|
+
indicesWithOwnParts,
|
|
632
|
+
hasNestedObjectParts,
|
|
633
|
+
pathParts
|
|
634
|
+
)
|
|
635
|
+
return createArrayBodyPart(bodyKey, fieldLines, partTypes, headers)
|
|
636
|
+
}
|
|
1425
637
|
|
|
1426
|
-
|
|
638
|
+
// Step 13.2.4.3: Process object fields
|
|
639
|
+
function processObjectFields(value, bodyKey, sortedBodyKeys) {
|
|
640
|
+
const objectTypes = []
|
|
641
|
+
const fieldLines = []
|
|
642
|
+
const binaryFields = []
|
|
643
|
+
const arrayTypes = []
|
|
644
|
+
|
|
645
|
+
// First collect array types
|
|
646
|
+
for (const [k, v] of Object.entries(value)) {
|
|
647
|
+
if (Array.isArray(v)) {
|
|
648
|
+
arrayTypes.push(
|
|
649
|
+
`${k.toLowerCase()}="${v.length === 0 ? "empty-list" : "list"}"`
|
|
650
|
+
)
|
|
651
|
+
}
|
|
652
|
+
}
|
|
1427
653
|
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
}
|
|
654
|
+
// Then process other fields
|
|
655
|
+
for (const [k, v] of Object.entries(value)) {
|
|
656
|
+
const childPath = `${bodyKey}/${k}`
|
|
1432
657
|
|
|
1433
|
-
|
|
1434
|
-
if (fieldLines.length > 0) {
|
|
1435
|
-
bodyParts.push(new Blob([orderedLines.join("\r\n") + "\r\n"]))
|
|
1436
|
-
} else {
|
|
1437
|
-
bodyParts.push(new Blob([orderedLines.join("\r\n")]))
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
658
|
+
if (sortedBodyKeys.includes(childPath)) {
|
|
1440
659
|
continue
|
|
1441
660
|
}
|
|
1442
661
|
|
|
1443
|
-
if (isPojo(
|
|
1444
|
-
|
|
1445
|
-
if (
|
|
1446
|
-
// Check if this is a parent array context where empty objects should get parts
|
|
1447
|
-
const parentPath = pathParts.slice(0, -1).join("/")
|
|
1448
|
-
let parentValue = obj
|
|
1449
|
-
for (const part of pathParts.slice(0, -1)) {
|
|
1450
|
-
if (/^\d+$/.test(part)) {
|
|
1451
|
-
parentValue = parentValue[parseInt(part) - 1]
|
|
1452
|
-
} else {
|
|
1453
|
-
parentValue = parentValue[part]
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
if (Array.isArray(parentValue)) {
|
|
1458
|
-
// Check if parent array has mixed content
|
|
1459
|
-
const hasEmptyStrings = parentValue.some(
|
|
1460
|
-
item => typeof item === "string" && item === ""
|
|
1461
|
-
)
|
|
1462
|
-
const hasEmptyObjects = parentValue.some(
|
|
1463
|
-
item => isPojo(item) && Object.keys(item).length === 0
|
|
1464
|
-
)
|
|
1465
|
-
const hasObjects = parentValue.some(item => isPojo(item))
|
|
1466
|
-
|
|
1467
|
-
if (hasObjects && (hasEmptyStrings || hasEmptyObjects)) {
|
|
1468
|
-
// Special mixed array case - empty objects don't get parts
|
|
1469
|
-
console.log(
|
|
1470
|
-
`[Body part] Skipping empty object in mixed array at ${bodyKey}`
|
|
1471
|
-
)
|
|
1472
|
-
continue
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
// Normal case - empty objects might get parts
|
|
1477
|
-
console.log(`[Body part] Empty object at ${bodyKey}`)
|
|
1478
|
-
// For now, skip empty objects
|
|
662
|
+
if (Array.isArray(v) && v.some(item => isPojo(item))) {
|
|
663
|
+
const hasOnlyEmpty = v.every(item => isEmpty(item))
|
|
664
|
+
if (hasOnlyEmpty) {
|
|
1479
665
|
continue
|
|
1480
666
|
}
|
|
667
|
+
}
|
|
1481
668
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
669
|
+
if (Array.isArray(v)) {
|
|
670
|
+
// Type already added in arrayTypes
|
|
671
|
+
} else if (
|
|
672
|
+
v === null ||
|
|
673
|
+
v === undefined ||
|
|
674
|
+
typeof v === "symbol" ||
|
|
675
|
+
typeof v === "boolean"
|
|
676
|
+
) {
|
|
677
|
+
objectTypes.push(`${k.toLowerCase()}="atom"`)
|
|
678
|
+
} else if (typeof v === "number") {
|
|
679
|
+
objectTypes.push(
|
|
680
|
+
`${k.toLowerCase()}="${Number.isInteger(v) ? "integer" : "float"}"`
|
|
681
|
+
)
|
|
682
|
+
} else if (typeof v === "string" && v.length === 0) {
|
|
683
|
+
objectTypes.push(`${k.toLowerCase()}="empty-binary"`)
|
|
684
|
+
} else if (isBytes(v) && (v.length === 0 || v.byteLength === 0)) {
|
|
685
|
+
objectTypes.push(`${k.toLowerCase()}="empty-binary"`)
|
|
686
|
+
} else if (isPojo(v) && Object.keys(v).length === 0) {
|
|
687
|
+
objectTypes.push(`${k.toLowerCase()}="empty-message"`)
|
|
688
|
+
}
|
|
1492
689
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
if (isInline) {
|
|
1497
|
-
lines.push(`content-disposition: inline`)
|
|
690
|
+
if (typeof v === "string") {
|
|
691
|
+
if (v.length === 0) {
|
|
692
|
+
fieldLines.push(`${k}: `)
|
|
1498
693
|
} else {
|
|
1499
|
-
|
|
694
|
+
fieldLines.push(`${k}: ${v}`)
|
|
1500
695
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
(
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
v.length > 0 &&
|
|
1523
|
-
v.every(
|
|
1524
|
-
item =>
|
|
1525
|
-
(Array.isArray(item) && item.length === 0) ||
|
|
1526
|
-
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
1527
|
-
(typeof item === "string" && item === "")
|
|
1528
|
-
)
|
|
1529
|
-
)
|
|
1530
|
-
}
|
|
1531
|
-
)
|
|
1532
|
-
|
|
1533
|
-
// Collect type information for arrays in the object BEFORE processing fields
|
|
1534
|
-
const arrayTypes = []
|
|
1535
|
-
for (const [k, v] of Object.entries(value)) {
|
|
1536
|
-
if (Array.isArray(v)) {
|
|
1537
|
-
arrayTypes.push(
|
|
1538
|
-
`${k.toLowerCase()}="${v.length === 0 ? "empty-list" : "list"}"`
|
|
1539
|
-
)
|
|
696
|
+
} else if (typeof v === "number") {
|
|
697
|
+
fieldLines.push(`${k}: ${v}`)
|
|
698
|
+
} else if (typeof v === "boolean") {
|
|
699
|
+
fieldLines.push(`${k}: "${v}"`)
|
|
700
|
+
} else if (v === null) {
|
|
701
|
+
fieldLines.push(`${k}: "null"`)
|
|
702
|
+
} else if (v === undefined) {
|
|
703
|
+
fieldLines.push(`${k}: "undefined"`)
|
|
704
|
+
} else if (typeof v === "symbol") {
|
|
705
|
+
const desc = v.description || "Symbol.for()"
|
|
706
|
+
fieldLines.push(`${k}: "${desc}"`)
|
|
707
|
+
} else if (isBytes(v)) {
|
|
708
|
+
const buffer = toBuffer(v)
|
|
709
|
+
binaryFields.push({ key: k, buffer })
|
|
710
|
+
} else if (Array.isArray(v) && v.length > 0) {
|
|
711
|
+
const childPath = `${bodyKey}/${k}`
|
|
712
|
+
if (!sortedBodyKeys.includes(childPath)) {
|
|
713
|
+
const hasObjects = v.some(item => isPojo(item))
|
|
714
|
+
if (!hasObjects) {
|
|
715
|
+
const encodedItems = v.map(item => encodeArrayItem(item)).join(", ")
|
|
716
|
+
fieldLines.push(`${k}: ${encodedItems}`)
|
|
1540
717
|
}
|
|
1541
718
|
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
1542
721
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
if (sortedBodyKeys.includes(childPath)) {
|
|
1547
|
-
continue
|
|
1548
|
-
}
|
|
722
|
+
const allTypes = [...arrayTypes, ...objectTypes]
|
|
723
|
+
return { allTypes, fieldLines, binaryFields }
|
|
724
|
+
}
|
|
1549
725
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
726
|
+
// Step 13.2.4.5: Create object body part
|
|
727
|
+
function createObjectBodyPart(
|
|
728
|
+
bodyKey,
|
|
729
|
+
value,
|
|
730
|
+
allTypes,
|
|
731
|
+
fieldLines,
|
|
732
|
+
binaryFields,
|
|
733
|
+
headers,
|
|
734
|
+
sortedBodyKeys
|
|
735
|
+
) {
|
|
736
|
+
const isInline = bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
737
|
+
const lines = []
|
|
738
|
+
|
|
739
|
+
if (isInline) {
|
|
740
|
+
lines.push(`content-disposition: inline`)
|
|
741
|
+
} else {
|
|
742
|
+
lines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
743
|
+
}
|
|
1566
744
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
} else if (typeof v === "number") {
|
|
1577
|
-
objectTypes.push(
|
|
1578
|
-
`${k.toLowerCase()}="${Number.isInteger(v) ? "integer" : "float"}"`
|
|
1579
|
-
)
|
|
1580
|
-
} else if (typeof v === "string" && v.length === 0) {
|
|
1581
|
-
objectTypes.push(`${k.toLowerCase()}="empty-binary"`)
|
|
1582
|
-
} else if (isBytes(v) && (v.length === 0 || v.byteLength === 0)) {
|
|
1583
|
-
objectTypes.push(`${k.toLowerCase()}="empty-binary"`)
|
|
1584
|
-
} else if (isPojo(v) && Object.keys(v).length === 0) {
|
|
1585
|
-
objectTypes.push(`${k.toLowerCase()}="empty-message"`)
|
|
1586
|
-
}
|
|
745
|
+
if (isInline) {
|
|
746
|
+
const orderedLines = []
|
|
747
|
+
for (const line of fieldLines) {
|
|
748
|
+
orderedLines.push(line)
|
|
749
|
+
}
|
|
750
|
+
if (allTypes.length > 0) {
|
|
751
|
+
orderedLines.push(`ao-types: ${allTypes.sort().join(", ")}`)
|
|
752
|
+
}
|
|
753
|
+
orderedLines.push("content-disposition: inline")
|
|
1587
754
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
} else if (typeof v === "symbol") {
|
|
1604
|
-
const desc = v.description || "Symbol.for()"
|
|
1605
|
-
fieldLines.push(`${k}: "${desc}"`)
|
|
1606
|
-
} else if (isBytes(v)) {
|
|
1607
|
-
const buffer = toBuffer(v)
|
|
1608
|
-
binaryFields.push({ key: k, buffer })
|
|
1609
|
-
continue
|
|
1610
|
-
} else if (Array.isArray(v)) {
|
|
1611
|
-
if (v.length === 0) {
|
|
1612
|
-
// Don't add field line for empty array
|
|
1613
|
-
} else {
|
|
1614
|
-
// Check if this array will have its own body part
|
|
1615
|
-
const childPath = `${bodyKey}/${k}`
|
|
1616
|
-
if (!sortedBodyKeys.includes(childPath)) {
|
|
1617
|
-
// Check if this array contains objects - if so, don't add field line
|
|
1618
|
-
const hasObjects = v.some(item => isPojo(item))
|
|
1619
|
-
if (!hasObjects) {
|
|
1620
|
-
const encodedItems = v
|
|
1621
|
-
.map(item => encodeArrayItem(item))
|
|
1622
|
-
.join(", ")
|
|
1623
|
-
fieldLines.push(`${k}: ${encodedItems}`)
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
} else if (isPojo(v) && Object.keys(v).length === 0) {
|
|
1628
|
-
// Empty object - don't add field line
|
|
1629
|
-
}
|
|
755
|
+
const binaryFieldsForInline = Object.entries(value)
|
|
756
|
+
.filter(
|
|
757
|
+
([k, v]) => isBytes(v) && !sortedBodyKeys.includes(`${bodyKey}/${k}`)
|
|
758
|
+
)
|
|
759
|
+
.map(([k, v]) => ({
|
|
760
|
+
key: k,
|
|
761
|
+
buffer: toBuffer(v),
|
|
762
|
+
}))
|
|
763
|
+
|
|
764
|
+
if (binaryFieldsForInline.length > 0) {
|
|
765
|
+
const parts = []
|
|
766
|
+
parts.push(Buffer.from(orderedLines.join("\r\n")))
|
|
767
|
+
for (const { key, buffer } of binaryFieldsForInline) {
|
|
768
|
+
parts.push(Buffer.from(`\r\n${key}: `))
|
|
769
|
+
parts.push(buffer)
|
|
1630
770
|
}
|
|
771
|
+
parts.push(Buffer.from("\r\n"))
|
|
772
|
+
const fullBody = Buffer.concat(parts)
|
|
773
|
+
return new Blob([fullBody])
|
|
774
|
+
} else {
|
|
775
|
+
const isLastBodyPart =
|
|
776
|
+
sortedBodyKeys.indexOf(bodyKey) === sortedBodyKeys.length - 1
|
|
777
|
+
const hasOnlyTypes = allTypes.length > 0 && fieldLines.length === 0
|
|
778
|
+
if (isLastBodyPart && hasOnlyTypes) {
|
|
779
|
+
return new Blob([orderedLines.join("\r\n")])
|
|
780
|
+
} else if (fieldLines.length === 0) {
|
|
781
|
+
return new Blob([orderedLines.join("\r\n")])
|
|
782
|
+
} else {
|
|
783
|
+
return new Blob([orderedLines.join("\r\n") + "\r\n"])
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
} else {
|
|
787
|
+
const orderedLines = []
|
|
788
|
+
if (allTypes.length > 0) {
|
|
789
|
+
orderedLines.push(`ao-types: ${allTypes.sort().join(", ")}`)
|
|
790
|
+
}
|
|
791
|
+
orderedLines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
792
|
+
|
|
793
|
+
const hasBinaryFields = binaryFields && binaryFields.length > 0
|
|
794
|
+
if (hasBinaryFields || fieldLines.length === 0) {
|
|
795
|
+
orderedLines.push("")
|
|
796
|
+
}
|
|
1631
797
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
1645
|
-
(typeof item === "string" && item === "")
|
|
1646
|
-
)
|
|
1647
|
-
return hasOnlyEmpty || sortedBodyKeys.includes(childPath)
|
|
798
|
+
for (const line of fieldLines) {
|
|
799
|
+
orderedLines.push(line)
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (binaryFields && binaryFields.length > 0) {
|
|
803
|
+
const parts = []
|
|
804
|
+
const headerText = orderedLines.join("\r\n")
|
|
805
|
+
parts.push(Buffer.from(headerText))
|
|
806
|
+
for (let i = 0; i < binaryFields.length; i++) {
|
|
807
|
+
const { key, buffer } = binaryFields[i]
|
|
808
|
+
if (i > 0) {
|
|
809
|
+
parts.push(Buffer.from("\r\n"))
|
|
1648
810
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
811
|
+
parts.push(Buffer.from(`${key}: `))
|
|
812
|
+
parts.push(buffer)
|
|
813
|
+
}
|
|
814
|
+
parts.push(Buffer.from("\r\n"))
|
|
815
|
+
const fullBody = Buffer.concat(parts)
|
|
816
|
+
return new Blob([fullBody])
|
|
817
|
+
} else {
|
|
818
|
+
if (fieldLines.length > 0) {
|
|
819
|
+
return new Blob([orderedLines.join("\r\n") + "\r\n"])
|
|
820
|
+
} else {
|
|
821
|
+
return new Blob([orderedLines.join("\r\n")])
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
1651
826
|
|
|
827
|
+
// Step 13.2.4: Handle object values
|
|
828
|
+
function handleObjectValue(
|
|
829
|
+
obj,
|
|
830
|
+
bodyKey,
|
|
831
|
+
value,
|
|
832
|
+
headers,
|
|
833
|
+
sortedBodyKeys,
|
|
834
|
+
pathParts,
|
|
835
|
+
hasSpecialDataBody
|
|
836
|
+
) {
|
|
837
|
+
if (Object.keys(value).length === 0) {
|
|
838
|
+
// Skip empty objects in certain contexts
|
|
839
|
+
const parentPath = pathParts.slice(0, -1).join("/")
|
|
840
|
+
const parentValue = parentPath ? getValueByPath(obj, parentPath) : obj
|
|
841
|
+
|
|
842
|
+
if (Array.isArray(parentValue)) {
|
|
843
|
+
const parentArrayInfo = analyzeArray(parentValue)
|
|
1652
844
|
if (
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
!hasArraysWithOnlyEmptyElements
|
|
845
|
+
parentArrayInfo.hasObjects &&
|
|
846
|
+
(parentArrayInfo.hasEmptyStrings || parentArrayInfo.hasEmptyObjects)
|
|
1656
847
|
) {
|
|
1657
|
-
|
|
848
|
+
return null
|
|
1658
849
|
}
|
|
850
|
+
}
|
|
851
|
+
return null
|
|
852
|
+
}
|
|
1659
853
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
})
|
|
1671
|
-
|
|
1672
|
-
if (isInline) {
|
|
1673
|
-
const orderedLines = []
|
|
854
|
+
// Skip special data/body case
|
|
855
|
+
if (
|
|
856
|
+
hasSpecialDataBody &&
|
|
857
|
+
bodyKey === "data" &&
|
|
858
|
+
Object.keys(value).length === 1 &&
|
|
859
|
+
value.body &&
|
|
860
|
+
isBytes(value.body)
|
|
861
|
+
) {
|
|
862
|
+
return null
|
|
863
|
+
}
|
|
1674
864
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
865
|
+
const { allTypes, fieldLines, binaryFields } = processObjectFields(
|
|
866
|
+
value,
|
|
867
|
+
bodyKey,
|
|
868
|
+
sortedBodyKeys
|
|
869
|
+
)
|
|
1679
870
|
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
871
|
+
// Check if object should be skipped
|
|
872
|
+
const hasOnlyEmptyCollections = Object.entries(value).every(([k, v]) =>
|
|
873
|
+
isEmpty(v)
|
|
874
|
+
)
|
|
875
|
+
const hasArraysWithOnlyEmptyElements = Object.entries(value).some(
|
|
876
|
+
([k, v]) =>
|
|
877
|
+
Array.isArray(v) && v.length > 0 && v.every(item => isEmpty(item))
|
|
878
|
+
)
|
|
1683
879
|
|
|
1684
|
-
|
|
880
|
+
const shouldSkipObject = Object.entries(value).every(([k, v]) => {
|
|
881
|
+
const childPath = `${bodyKey}/${k}`
|
|
882
|
+
if (sortedBodyKeys.includes(childPath)) return true
|
|
883
|
+
if (Array.isArray(v) && v.some(item => isPojo(item))) {
|
|
884
|
+
const hasOnlyEmpty = v.every(item => isEmpty(item))
|
|
885
|
+
return hasOnlyEmpty || sortedBodyKeys.includes(childPath)
|
|
886
|
+
}
|
|
887
|
+
return false
|
|
888
|
+
})
|
|
1685
889
|
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
buffer: toBuffer(v),
|
|
1694
|
-
}))
|
|
890
|
+
if (
|
|
891
|
+
shouldSkipObject &&
|
|
892
|
+
!hasOnlyEmptyCollections &&
|
|
893
|
+
!hasArraysWithOnlyEmptyElements
|
|
894
|
+
) {
|
|
895
|
+
return null
|
|
896
|
+
}
|
|
1695
897
|
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
898
|
+
return createObjectBodyPart(
|
|
899
|
+
bodyKey,
|
|
900
|
+
value,
|
|
901
|
+
allTypes,
|
|
902
|
+
fieldLines,
|
|
903
|
+
binaryFields,
|
|
904
|
+
headers,
|
|
905
|
+
sortedBodyKeys
|
|
906
|
+
)
|
|
907
|
+
}
|
|
1699
908
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
909
|
+
// Step 13.2.5: Handle primitive values
|
|
910
|
+
function handlePrimitiveValue(bodyKey, value, headers) {
|
|
911
|
+
const isInline = bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
912
|
+
const lines = []
|
|
1704
913
|
|
|
1705
|
-
|
|
914
|
+
if (isInline) {
|
|
915
|
+
lines.push(`content-disposition: inline`)
|
|
916
|
+
} else {
|
|
917
|
+
lines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
918
|
+
}
|
|
1706
919
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
} else if (fieldLines.length === 0) {
|
|
1719
|
-
bodyParts.push(new Blob([orderedLines.join("\r\n")]))
|
|
1720
|
-
} else {
|
|
1721
|
-
bodyParts.push(new Blob([orderedLines.join("\r\n") + "\r\n"]))
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
} else {
|
|
1725
|
-
// Put ao-types first, then content-disposition, then field lines
|
|
1726
|
-
const orderedLines = []
|
|
920
|
+
if (typeof value === "string") {
|
|
921
|
+
lines.push("")
|
|
922
|
+
lines.push(value)
|
|
923
|
+
return new Blob([lines.join("\r\n")])
|
|
924
|
+
} else {
|
|
925
|
+
const content = encodePrimitiveContent(value)
|
|
926
|
+
lines.push("")
|
|
927
|
+
lines.push(content)
|
|
928
|
+
return new Blob([lines.join("\r\n")])
|
|
929
|
+
}
|
|
930
|
+
}
|
|
1727
931
|
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
932
|
+
// Step 13.2.6: Handle binary values
|
|
933
|
+
function handleBinaryValue(bodyKey, value, headers) {
|
|
934
|
+
const isInline = bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
935
|
+
const lines = []
|
|
1731
936
|
|
|
1732
|
-
|
|
937
|
+
if (isInline) {
|
|
938
|
+
lines.push(`content-disposition: inline`)
|
|
939
|
+
} else {
|
|
940
|
+
lines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
941
|
+
}
|
|
1733
942
|
|
|
1734
|
-
|
|
1735
|
-
|
|
943
|
+
const buffer = toBuffer(value)
|
|
944
|
+
const headerText = lines.join("\r\n") + "\r\n\r\n"
|
|
945
|
+
return new Blob([headerText, buffer])
|
|
946
|
+
}
|
|
1736
947
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
948
|
+
// Step 13.3: Handle special data/body case
|
|
949
|
+
function handleSpecialDataBodyCase(obj, hasSpecialDataBody) {
|
|
950
|
+
if (
|
|
951
|
+
hasSpecialDataBody &&
|
|
952
|
+
obj.data &&
|
|
953
|
+
obj.data.body &&
|
|
954
|
+
isBytes(obj.data.body)
|
|
955
|
+
) {
|
|
956
|
+
const buffer = toBuffer(obj.data.body)
|
|
957
|
+
const specialPart = [
|
|
958
|
+
`content-disposition: form-data;name="data/body"`,
|
|
959
|
+
"",
|
|
960
|
+
"",
|
|
961
|
+
].join("\r\n")
|
|
962
|
+
return new Blob([specialPart, buffer])
|
|
963
|
+
}
|
|
964
|
+
return null
|
|
965
|
+
}
|
|
1741
966
|
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
967
|
+
// Step 13: Build body parts for each body key
|
|
968
|
+
function buildBodyParts(obj, sortedBodyKeys, headers, hasSpecialDataBody) {
|
|
969
|
+
// Step 13.1: Initialize body parts collection
|
|
970
|
+
const bodyParts = []
|
|
1746
971
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
972
|
+
// Step 13.2: Process each body key
|
|
973
|
+
for (const bodyKey of sortedBodyKeys) {
|
|
974
|
+
// Step 13.2.1: Get value for current body key
|
|
975
|
+
const value = getValueByPath(obj, bodyKey)
|
|
976
|
+
const pathParts = bodyKey.split("/")
|
|
1751
977
|
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
978
|
+
// Step 13.2.2: Handle empty string in nested path
|
|
979
|
+
const emptyStringPart = handleEmptyStringInNestedPath(
|
|
980
|
+
bodyKey,
|
|
981
|
+
value,
|
|
982
|
+
pathParts
|
|
983
|
+
)
|
|
984
|
+
if (emptyStringPart) {
|
|
985
|
+
bodyParts.push(emptyStringPart)
|
|
986
|
+
continue
|
|
987
|
+
}
|
|
1760
988
|
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
989
|
+
// Step 13.2.3: Handle array values
|
|
990
|
+
if (Array.isArray(value)) {
|
|
991
|
+
const arrayPart = handleArrayValue(
|
|
992
|
+
bodyKey,
|
|
993
|
+
value,
|
|
994
|
+
headers,
|
|
995
|
+
sortedBodyKeys,
|
|
996
|
+
pathParts
|
|
997
|
+
)
|
|
998
|
+
if (arrayPart) {
|
|
999
|
+
bodyParts.push(arrayPart)
|
|
1772
1000
|
}
|
|
1773
|
-
|
|
1774
1001
|
continue
|
|
1775
1002
|
}
|
|
1776
1003
|
|
|
1777
|
-
|
|
1778
|
-
if (
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1004
|
+
// Step 13.2.4: Handle object values
|
|
1005
|
+
if (isPojo(value)) {
|
|
1006
|
+
const objectPart = handleObjectValue(
|
|
1007
|
+
obj,
|
|
1008
|
+
bodyKey,
|
|
1009
|
+
value,
|
|
1010
|
+
headers,
|
|
1011
|
+
sortedBodyKeys,
|
|
1012
|
+
pathParts,
|
|
1013
|
+
hasSpecialDataBody
|
|
1014
|
+
)
|
|
1015
|
+
if (objectPart) {
|
|
1016
|
+
bodyParts.push(objectPart)
|
|
1017
|
+
}
|
|
1018
|
+
continue
|
|
1782
1019
|
}
|
|
1783
1020
|
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
bodyParts.push(new Blob([headerText, buffer]))
|
|
1788
|
-
} else if (typeof value === "string") {
|
|
1789
|
-
lines.push("")
|
|
1790
|
-
lines.push(value)
|
|
1791
|
-
bodyParts.push(new Blob([lines.join("\r\n")]))
|
|
1792
|
-
} else if (
|
|
1021
|
+
// Step 13.2.5: Handle primitive values
|
|
1022
|
+
if (
|
|
1023
|
+
typeof value === "string" ||
|
|
1793
1024
|
typeof value === "boolean" ||
|
|
1794
1025
|
typeof value === "number" ||
|
|
1795
1026
|
value === null ||
|
|
1796
1027
|
value === undefined ||
|
|
1797
1028
|
typeof value === "symbol"
|
|
1798
1029
|
) {
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
content = String(value)
|
|
1804
|
-
} else if (value === null) {
|
|
1805
|
-
content = '"null"'
|
|
1806
|
-
} else if (value === undefined) {
|
|
1807
|
-
content = '"undefined"'
|
|
1808
|
-
} else if (typeof value === "symbol") {
|
|
1809
|
-
content = `"${value.description || "Symbol.for()"}"`
|
|
1810
|
-
}
|
|
1030
|
+
const primitivePart = handlePrimitiveValue(bodyKey, value, headers)
|
|
1031
|
+
bodyParts.push(primitivePart)
|
|
1032
|
+
continue
|
|
1033
|
+
}
|
|
1811
1034
|
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1035
|
+
// Step 13.2.6: Handle binary values
|
|
1036
|
+
if (isBytes(value)) {
|
|
1037
|
+
const binaryPart = handleBinaryValue(bodyKey, value, headers)
|
|
1038
|
+
bodyParts.push(binaryPart)
|
|
1039
|
+
continue
|
|
1815
1040
|
}
|
|
1816
1041
|
}
|
|
1817
1042
|
|
|
1818
|
-
//
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
obj.data.body &&
|
|
1823
|
-
isBytes(obj.data.body)
|
|
1824
|
-
) {
|
|
1825
|
-
const buffer = toBuffer(obj.data.body)
|
|
1826
|
-
const specialPart = [
|
|
1827
|
-
`content-disposition: form-data;name="data/body"`,
|
|
1828
|
-
"",
|
|
1829
|
-
"",
|
|
1830
|
-
].join("\r\n")
|
|
1831
|
-
bodyParts.push(new Blob([specialPart, buffer]))
|
|
1043
|
+
// Step 13.3: Handle special data/body case
|
|
1044
|
+
const specialPart = handleSpecialDataBodyCase(obj, hasSpecialDataBody)
|
|
1045
|
+
if (specialPart) {
|
|
1046
|
+
bodyParts.push(specialPart)
|
|
1832
1047
|
}
|
|
1833
1048
|
|
|
1049
|
+
// Step 13.4: Return body parts
|
|
1050
|
+
return bodyParts
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Step 14: Generate multipart boundary
|
|
1054
|
+
async function generateBoundary(bodyParts) {
|
|
1834
1055
|
const partsContent = await Promise.all(bodyParts.map(part => part.text()))
|
|
1835
1056
|
const allContent = partsContent.join("")
|
|
1836
1057
|
const boundaryHash = await sha256(new TextEncoder().encode(allContent))
|
|
1837
1058
|
const boundary = base64url.encode(Buffer.from(boundaryHash))
|
|
1059
|
+
return boundary
|
|
1060
|
+
}
|
|
1838
1061
|
|
|
1062
|
+
// Step 15: Assemble final multipart body
|
|
1063
|
+
function assembleMultipartBody(bodyParts, boundary) {
|
|
1839
1064
|
const finalParts = []
|
|
1840
1065
|
for (let i = 0; i < bodyParts.length; i++) {
|
|
1841
1066
|
if (i === 0) {
|
|
@@ -1847,20 +1072,130 @@ async function encode(obj = {}) {
|
|
|
1847
1072
|
}
|
|
1848
1073
|
finalParts.push(new Blob([`\r\n--${boundary}--`]))
|
|
1849
1074
|
|
|
1850
|
-
|
|
1851
|
-
|
|
1075
|
+
return new Blob(finalParts)
|
|
1076
|
+
}
|
|
1852
1077
|
|
|
1078
|
+
// Step 16: Calculate content digest
|
|
1079
|
+
async function calculateContentDigest(body) {
|
|
1853
1080
|
const finalContent = await body.arrayBuffer()
|
|
1854
1081
|
|
|
1855
1082
|
if (finalContent.byteLength > 0) {
|
|
1856
1083
|
const contentDigest = await sha256(finalContent)
|
|
1857
1084
|
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
1858
|
-
|
|
1085
|
+
return { digest: base64, byteLength: finalContent.byteLength }
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
return { digest: null, byteLength: finalContent.byteLength }
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// Step 17: Set final headers (content-type, content-length)
|
|
1092
|
+
function setFinalHeaders(headers, boundary, contentDigest, byteLength) {
|
|
1093
|
+
headers["content-type"] = `multipart/form-data; boundary="${boundary}"`
|
|
1094
|
+
|
|
1095
|
+
if (contentDigest) {
|
|
1096
|
+
headers["content-digest"] = `sha-256=:${contentDigest}:`
|
|
1859
1097
|
}
|
|
1860
1098
|
|
|
1861
|
-
headers["content-length"] = String(
|
|
1099
|
+
headers["content-length"] = String(byteLength)
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
async function encode(obj = {}) {
|
|
1103
|
+
// Step 1: Process and normalize input values
|
|
1104
|
+
const processedObj = processInputValues(obj)
|
|
1105
|
+
|
|
1106
|
+
// Step 2: Handle empty object case
|
|
1107
|
+
const emptyResult = handleEmptyObject(processedObj)
|
|
1108
|
+
if (emptyResult) return emptyResult
|
|
1109
|
+
|
|
1110
|
+
// Step 3: Handle single field with empty binary
|
|
1111
|
+
const emptyBinaryResult = handleSingleEmptyBinaryField(processedObj)
|
|
1112
|
+
if (emptyBinaryResult) return emptyBinaryResult
|
|
1113
|
+
|
|
1114
|
+
// Step 4: Handle single field with binary data
|
|
1115
|
+
const singleBinaryResult = await handleSingleBinaryField(processedObj)
|
|
1116
|
+
if (singleBinaryResult) return singleBinaryResult
|
|
1117
|
+
|
|
1118
|
+
// Step 5: Handle single field with primitive value
|
|
1119
|
+
const primitiveResult = await handleSinglePrimitiveField(processedObj)
|
|
1120
|
+
if (primitiveResult) return primitiveResult
|
|
1121
|
+
|
|
1122
|
+
// Step 6a: Handle single field with non-empty binary
|
|
1123
|
+
const nonEmptyBinaryResult =
|
|
1124
|
+
await handleSingleNonEmptyBinaryField(processedObj)
|
|
1125
|
+
if (nonEmptyBinaryResult) return nonEmptyBinaryResult
|
|
1126
|
+
|
|
1127
|
+
// Step 6: Handle single field with non-ASCII string
|
|
1128
|
+
const nonAsciiResult = await handleSingleNonAsciiStringField(processedObj)
|
|
1129
|
+
if (nonAsciiResult) return nonAsciiResult
|
|
1130
|
+
|
|
1131
|
+
// Step 7: Collect all keys that need to go in body
|
|
1132
|
+
const bodyKeys = collectBodyKeysStep(processedObj)
|
|
1133
|
+
|
|
1134
|
+
const objKeys = Object.keys(obj)
|
|
1135
|
+
const headers = {}
|
|
1136
|
+
const headerTypes = []
|
|
1137
|
+
|
|
1138
|
+
// Step 8: Process fields that can go in headers
|
|
1139
|
+
processHeaderFields(obj, bodyKeys, headers, headerTypes)
|
|
1140
|
+
|
|
1141
|
+
// Step 9: Handle case where all body keys are empty binaries
|
|
1142
|
+
const emptyBinaryBodyResult = handleAllEmptyBinaryBodyKeys(
|
|
1143
|
+
obj,
|
|
1144
|
+
bodyKeys,
|
|
1145
|
+
headers,
|
|
1146
|
+
headerTypes
|
|
1147
|
+
)
|
|
1148
|
+
if (emptyBinaryBodyResult) return emptyBinaryBodyResult
|
|
1149
|
+
|
|
1150
|
+
// Step 10: Handle single body key optimization
|
|
1151
|
+
const singleBodyKeyResult = await handleSingleBodyKeyOptimization(
|
|
1152
|
+
obj,
|
|
1153
|
+
bodyKeys,
|
|
1154
|
+
headers,
|
|
1155
|
+
headerTypes
|
|
1156
|
+
)
|
|
1157
|
+
if (singleBodyKeyResult) return singleBodyKeyResult
|
|
1158
|
+
|
|
1159
|
+
// Step 11: Sort body keys
|
|
1160
|
+
const sortedBodyKeys = sortBodyKeys(bodyKeys)
|
|
1161
|
+
|
|
1162
|
+
// Step 12: Check for special data/body case
|
|
1163
|
+
const hasSpecialDataBody = checkSpecialDataBodyCase(obj, sortedBodyKeys)
|
|
1164
|
+
|
|
1165
|
+
headers["body-keys"] = sortedBodyKeys.map(k => `"${k}"`).join(", ")
|
|
1166
|
+
|
|
1167
|
+
if (!hasSpecialDataBody) {
|
|
1168
|
+
if (sortedBodyKeys.includes("body") && sortedBodyKeys.length === 1) {
|
|
1169
|
+
headers["inline-body-key"] = "body"
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (headerTypes.length > 0) {
|
|
1174
|
+
headers["ao-types"] = headerTypes.sort().join(", ")
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// Step 13: Build body parts for each body key
|
|
1178
|
+
const bodyParts = buildBodyParts(
|
|
1179
|
+
obj,
|
|
1180
|
+
sortedBodyKeys,
|
|
1181
|
+
headers,
|
|
1182
|
+
hasSpecialDataBody
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1185
|
+
// Step 14: Generate multipart boundary
|
|
1186
|
+
const boundary = await generateBoundary(bodyParts)
|
|
1187
|
+
|
|
1188
|
+
// Step 15: Assemble final multipart body
|
|
1189
|
+
const body = assembleMultipartBody(bodyParts, boundary)
|
|
1190
|
+
|
|
1191
|
+
// Step 16: Calculate content digest
|
|
1192
|
+
const { digest: contentDigest, byteLength } =
|
|
1193
|
+
await calculateContentDigest(body)
|
|
1194
|
+
|
|
1195
|
+
// Step 17: Set final headers (content-type, content-length)
|
|
1196
|
+
setFinalHeaders(headers, boundary, contentDigest, byteLength)
|
|
1862
1197
|
|
|
1863
|
-
|
|
1198
|
+
// Step 18: Return result
|
|
1864
1199
|
return { headers, body }
|
|
1865
1200
|
}
|
|
1866
1201
|
|