Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 3e7baf7

Browse files
committed
feat: bufferring free multipart body encoder
1 parent 881bc08 commit 3e7baf7

16 files changed

+1000
-302
lines changed

packages/ipfs-core-utils/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
"src",
1212
"dist"
1313
],
14+
"browser": {
15+
"./src/lib/blob.js": "./src/lib/blob.browser.js",
16+
"./src/lib/file.js": "./src/lib/file.browser.js"
17+
},
1418
"repository": {
1519
"type": "git",
1620
"url": "git+https://github.com/ipfs/js-ipfs.git"
@@ -40,4 +44,4 @@
4044
"dirty-chai": "^2.0.1",
4145
"it-all": "^1.0.1"
4246
}
43-
}
47+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// @ts-check
2+
'use strict'
3+
/* eslint-env browser */
4+
5+
exports.Blob = Blob
6+
7+
/**
8+
* Universal blob reading function
9+
* @param {Blob} blob
10+
* @returns {AsyncIterable<Uint8Array>}
11+
*/
12+
const readBlob = async function * (blob) {
13+
const { body } = new Response(blob)
14+
const reader = body.getReader()
15+
while (true) {
16+
const next = await reader.read()
17+
if (next.done) {
18+
return
19+
} else {
20+
yield next.value
21+
}
22+
}
23+
}
24+
exports.readBlob = readBlob
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// @ts-check
2+
'use strict'
3+
4+
const { TextEncoder, TextDecoder } = require('util')
5+
6+
class Blob {
7+
/**
8+
*
9+
* @param {BlobPart[]} init
10+
* @param {Object} [options]
11+
* @param {string} [options.type]
12+
*
13+
*/
14+
constructor (init, options = {}) {
15+
/** @type {Uint8Array[]} */
16+
const parts = []
17+
18+
let size = 0
19+
for (const part of init) {
20+
if (typeof part === 'string') {
21+
const bytes = new TextEncoder().encode(part)
22+
parts.push(bytes)
23+
size += bytes.byteLength
24+
} else if (part instanceof Blob) {
25+
size += part.size
26+
// @ts-ignore - `_parts` is marked private so TS will complain about
27+
// accessing it.
28+
parts.push(...part._parts)
29+
} else if (part instanceof ArrayBuffer) {
30+
parts.push(new Uint8Array(part))
31+
size += part.byteLength
32+
} else if (part instanceof Uint8Array) {
33+
parts.push(part)
34+
size += part.byteLength
35+
} else if (ArrayBuffer.isView(part)) {
36+
const { buffer, byteOffset, byteLength } = part
37+
parts.push(new Uint8Array(buffer, byteOffset, byteLength))
38+
size += byteLength
39+
} else {
40+
throw new TypeError(`Can not convert ${part} value to a BlobPart`)
41+
}
42+
}
43+
44+
/** @private */
45+
this._size = size
46+
/** @private */
47+
this._type = options.type || ''
48+
/** @private */
49+
this._parts = parts
50+
}
51+
52+
/**
53+
* A string indicating the MIME type of the data contained in the Blob.
54+
* If the type is unknown, this string is empty.
55+
* @type {string}
56+
*/
57+
get type () {
58+
return this._type
59+
}
60+
61+
/**
62+
* The size, in bytes, of the data contained in the Blob object.
63+
* @type {number}
64+
*/
65+
get size () {
66+
return this._size
67+
}
68+
69+
/**
70+
* Returns a new Blob object containing the data in the specified range of
71+
* bytes of the blob on which it's called.
72+
* @param {number} [start=0] - An index into the Blob indicating the first
73+
* byte to include in the new Blob. If you specify a negative value, it's
74+
* treated as an offset from the end of the Blob toward the beginning. For
75+
* example, `-10` would be the 10th from last byte in the Blob. The default
76+
* value is `0`. If you specify a value for start that is larger than the
77+
* size of the source Blob, the returned Blob has size 0 and contains no
78+
* data.
79+
* @param {number} [end] - An index into the `Blob` indicating the first byte
80+
* that will *not* be included in the new `Blob` (i.e. the byte exactly at
81+
* this index is not included). If you specify a negative value, it's treated
82+
* as an offset from the end of the Blob toward the beginning. For example,
83+
* `-10` would be the 10th from last byte in the `Blob`. The default value is
84+
* size.
85+
* @param {string} [type] - The content type to assign to the new Blob;
86+
* this will be the value of its type property. The default value is an empty
87+
* string.
88+
* @returns {Blob}
89+
*/
90+
slice (start = 0, end = this.size, type = '') {
91+
const { size, _parts } = this
92+
let offset = start < 0
93+
? Math.max(size + start, 0)
94+
: Math.min(start, size)
95+
96+
let limit = (end < 0 ? Math.max(size + end, 0) : Math.min(end, size))
97+
const span = Math.max(limit - offset, 0)
98+
99+
let blobSize = 0
100+
const blobParts = []
101+
for (const part of _parts) {
102+
const { byteLength } = part
103+
if (offset > 0 && byteLength <= offset) {
104+
offset -= byteLength
105+
limit -= byteLength
106+
} else {
107+
const chunk = part.subarray(offset, Math.min(byteLength, limit))
108+
blobParts.push(chunk)
109+
blobSize += chunk.byteLength
110+
// no longer need to take that into account
111+
offset = 0
112+
113+
// don't add the overflow to new blobParts
114+
if (blobSize >= span) {
115+
break
116+
}
117+
}
118+
}
119+
120+
const blob = new Blob([], { type })
121+
blob._parts = blobParts
122+
blob._size = blobSize
123+
124+
return blob
125+
}
126+
127+
/**
128+
* Returns a promise that resolves with an ArrayBuffer containing the entire
129+
* contents of the Blob as binary data.
130+
* @returns {Promise<ArrayBuffer>}
131+
*/
132+
// eslint-disable-next-line require-await
133+
async arrayBuffer () {
134+
const buffer = new ArrayBuffer(this.size)
135+
const bytes = new Uint8Array(buffer)
136+
let offset = 0
137+
for (const part of this._parts) {
138+
bytes.set(part, offset)
139+
offset += part.byteLength
140+
}
141+
return buffer
142+
}
143+
144+
/**
145+
* Returns a promise that resolves with a USVString containing the entire
146+
* contents of the Blob interpreted as UTF-8 text.
147+
* @returns {Promise<string>}
148+
*/
149+
// eslint-disable-next-line require-await
150+
async text () {
151+
const decoder = new TextDecoder()
152+
let text = ''
153+
for (const part of this._parts) {
154+
text += decoder.decode(part)
155+
}
156+
return text
157+
}
158+
159+
/**
160+
* @returns {never}
161+
*/
162+
// eslint-disable-next-line valid-jsdoc
163+
stream () {
164+
throw Error('Not implemented')
165+
}
166+
167+
/**
168+
* Non standard, but if `ReadableStream`s are extended to be
169+
* made async iterable why not blobs.
170+
* @returns {AsyncIterator<Uint8Array>}
171+
*/
172+
// eslint-disable-next-line require-await
173+
async * [Symbol.asyncIterator] () {
174+
yield * this._parts
175+
}
176+
}
177+
178+
exports.Blob = Blob
179+
180+
/**
181+
* Universal blob reading function
182+
* @param {Blob} blob
183+
* @returns {AsyncIterable<Uint8Array>}
184+
*/
185+
const readBlob = (blob) => blob
186+
exports.readBlob = readBlob
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
'use strict'
2+
/* eslint-env browser */
3+
4+
exports.File = File
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// @ts-check
2+
'use strict'
3+
4+
const { Blob } = require('./blob')
5+
6+
class File extends Blob {
7+
/**
8+
*
9+
* @param {BlobPart[]} init
10+
* @param {string} name - A USVString representing the file name or the path
11+
* to the file.
12+
* @param {Object} [options]
13+
* @param {string} [options.type] - A DOMString representing the MIME type
14+
* of the content that will be put into the file. Defaults to a value of "".
15+
* @param {number} [options.lastModified] - A number representing the number
16+
* of milliseconds between the Unix time epoch and when the file was last
17+
* modified. Defaults to a value of Date.now().
18+
*/
19+
constructor (init, name, options = {}) {
20+
super(init, options)
21+
/** @private */
22+
this._name = name.replace(/\//g, ':')
23+
this._lastModified = options.lastModified || Date.now()
24+
}
25+
26+
/**
27+
* The name of the file referenced by the File object.
28+
* @type {string}
29+
*/
30+
get name () {
31+
return this._name
32+
}
33+
34+
/**
35+
* The path the URL of the File is relative to.
36+
* @type {string}
37+
*/
38+
get webkitRelativePath () {
39+
return ''
40+
}
41+
42+
/**
43+
* Returns the last modified time of the file, in millisecond since the UNIX
44+
* epoch (January 1st, 1970 at Midnight).
45+
* @returns {number}
46+
*/
47+
get lastModified () {
48+
return this._lastModified
49+
}
50+
51+
get [Symbol.toStringTag] () {
52+
return 'File'
53+
}
54+
}
55+
exports.File = File

0 commit comments

Comments
 (0)