Skip to content

Commit 062307d

Browse files
authored
feat(bindings/nodejs): Add ReadOptions and ReaderOptions support for new options API (#6312)
* feat(bindings/nodejs): Add ReadOptions and ReaderOptions support for new options API * fix: grpc limit * refactor(bindings/nodejs): simplify range handling using BytesRange
1 parent f6dc4e0 commit 062307d

File tree

8 files changed

+1089
-15
lines changed

8 files changed

+1089
-15
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"rust-analyzer.cargo.allTargets": true,
33
"rust-analyzer.cargo.features": "all",
4+
"rust-analyzer.procMacro.ignored": { "napi-derive": ["napi"] },
45
"rust-analyzer.linkedProjects": [
56
"${workspaceFolder}/core/Cargo.toml",
67
"${workspaceFolder}/bindings/python/Cargo.toml",

bindings/nodejs/generated.d.ts

Lines changed: 162 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,158 @@ export interface StatOptions {
7474
*/
7575
overrideContentDisposition?: string
7676
}
77+
export interface ReadOptions {
78+
/**
79+
* Set `version` for this operation.
80+
*
81+
* This option can be used to retrieve the data of a specified version of the given path.
82+
*/
83+
version?: string
84+
/**
85+
* Set `concurrent` for the operation.
86+
*
87+
* OpenDAL by default to read file without concurrent. This is not efficient for cases when users
88+
* read large chunks of data. By setting `concurrent`, opendal will reading files concurrently
89+
* on support storage services.
90+
*
91+
* By setting `concurrent`, opendal will fetch chunks concurrently with
92+
* the give chunk size.
93+
*/
94+
concurrent?: number
95+
/**
96+
* Sets the chunk size for this operation.
97+
*
98+
* OpenDAL will use services' preferred chunk size by default. Users can set chunk based on their own needs.
99+
*/
100+
chunk?: number
101+
/**
102+
* Controls the optimization strategy for range reads in [`Reader::fetch`].
103+
*
104+
* When performing range reads, if the gap between two requested ranges is smaller than
105+
* the configured `gap` size, OpenDAL will merge these ranges into a single read request
106+
* and discard the unrequested data in between. This helps reduce the number of API calls
107+
* to remote storage services.
108+
*
109+
* This optimization is particularly useful when performing multiple small range reads
110+
* that are close to each other, as it reduces the overhead of multiple network requests
111+
* at the cost of transferring some additional data.
112+
*/
113+
gap?: bigint
114+
/**
115+
* Sets the offset (starting position) for range read operations.
116+
* The read will start from this position in the file.
117+
*/
118+
offset?: bigint
119+
/**
120+
* Sets the size (length) for range read operations.
121+
* The read will continue for this many bytes after the offset.
122+
*/
123+
size?: bigint
124+
/**
125+
* Sets if-match condition for this operation.
126+
* If file exists and its etag doesn't match, an error will be returned.
127+
*/
128+
ifMatch?: string
129+
/**
130+
* Sets if-none-match condition for this operation.
131+
* If file exists and its etag matches, an error will be returned.
132+
*/
133+
ifNoneMatch?: string
134+
/**
135+
* Sets if-modified-since condition for this operation.
136+
* If file exists and hasn't been modified since the specified time, an error will be returned.
137+
* ISO 8601 formatted date string
138+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
139+
*/
140+
ifModifiedSince?: string
141+
/**
142+
* Sets if-unmodified-since condition for this operation.
143+
* If file exists and has been modified since the specified time, an error will be returned.
144+
* ISO 8601 formatted date string
145+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
146+
*/
147+
ifUnmodifiedSince?: string
148+
/**
149+
* Specify the `content-type` header that should be sent back by the operation.
150+
*
151+
* This option is only meaningful when used along with presign.
152+
*/
153+
contentType?: string
154+
/**
155+
* Specify the `cache-control` header that should be sent back by the operation.
156+
*
157+
* This option is only meaningful when used along with presign.
158+
*/
159+
cacheControl?: string
160+
/**
161+
* Specify the `content-disposition` header that should be sent back by the operation.
162+
*
163+
* This option is only meaningful when used along with presign.
164+
*/
165+
contentDisposition?: string
166+
}
167+
export interface ReaderOptions {
168+
/**
169+
* Set `version` for this operation.
170+
*
171+
* This option can be used to retrieve the data of a specified version of the given path.
172+
*/
173+
version?: string
174+
/**
175+
* Set `concurrent` for the operation.
176+
*
177+
* OpenDAL by default to read file without concurrent. This is not efficient for cases when users
178+
* read large chunks of data. By setting `concurrent`, opendal will reading files concurrently
179+
* on support storage services.
180+
*
181+
* By setting `concurrent`, opendal will fetch chunks concurrently with
182+
* the give chunk size.
183+
*/
184+
concurrent?: number
185+
/**
186+
* Sets the chunk size for this operation.
187+
*
188+
* OpenDAL will use services' preferred chunk size by default. Users can set chunk based on their own needs.
189+
*/
190+
chunk?: number
191+
/**
192+
* Controls the optimization strategy for range reads in [`Reader::fetch`].
193+
*
194+
* When performing range reads, if the gap between two requested ranges is smaller than
195+
* the configured `gap` size, OpenDAL will merge these ranges into a single read request
196+
* and discard the unrequested data in between. This helps reduce the number of API calls
197+
* to remote storage services.
198+
*
199+
* This optimization is particularly useful when performing multiple small range reads
200+
* that are close to each other, as it reduces the overhead of multiple network requests
201+
* at the cost of transferring some additional data.
202+
*/
203+
gap?: bigint
204+
/**
205+
* Sets if-match condition for this operation.
206+
* If file exists and its etag doesn't match, an error will be returned.
207+
*/
208+
ifMatch?: string
209+
/**
210+
* Sets if-none-match condition for this operation.
211+
* If file exists and its etag matches, an error will be returned.
212+
*/
213+
ifNoneMatch?: string
214+
/**
215+
* Sets if-modified-since condition for this operation.
216+
* If file exists and hasn't been modified since the specified time, an error will be returned.
217+
* ISO 8601 formatted date string
218+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
219+
*/
220+
ifModifiedSince?: string
221+
/**
222+
* Sets if-unmodified-since condition for this operation.
223+
* If file exists and has been modified since the specified time, an error will be returned.
224+
* ISO 8601 formatted date string
225+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
226+
*/
227+
ifUnmodifiedSince?: string
228+
}
77229
export const enum EntryMode {
78230
/** FILE means the path has data to read. */
79231
FILE = 0,
@@ -157,6 +309,12 @@ export class Capability {
157309
get statWithOverrideContentDisposition(): boolean
158310
/** If operator supports read. */
159311
get read(): boolean
312+
/** If operator supports read with version. */
313+
get readWithVersion(): boolean
314+
/** If operator supports read with range. */
315+
get readWithIfModifiedSince(): boolean
316+
/** If operator supports read with if unmodified since. */
317+
get readWithIfUnmodifiedSince(): boolean
160318
/** If operator supports read with if matched. */
161319
get readWithIfMatch(): boolean
162320
/** If operator supports read with if not match. */
@@ -332,13 +490,13 @@ export class Operator {
332490
* const buf = await op.read("path/to/file");
333491
* ```
334492
*/
335-
read(path: string): Promise<Buffer>
493+
read(path: string, options?: ReadOptions | undefined | null): Promise<Buffer>
336494
/**
337495
* Create a reader to read the given path.
338496
*
339497
* It could be used to read large file in a streaming way.
340498
*/
341-
reader(path: string): Promise<Reader>
499+
reader(path: string, options?: ReaderOptions | undefined | null): Promise<Reader>
342500
/**
343501
* Read the whole path into a buffer synchronously.
344502
*
@@ -347,13 +505,13 @@ export class Operator {
347505
* const buf = op.readSync("path/to/file");
348506
* ```
349507
*/
350-
readSync(path: string): Buffer
508+
readSync(path: string, options?: ReadOptions | undefined | null): Buffer
351509
/**
352510
* Create a reader to read the given path synchronously.
353511
*
354512
* It could be used to read large file in a streaming way.
355513
*/
356-
readerSync(path: string): BlockingReader
514+
readerSync(path: string, options?: ReaderOptions | undefined | null): BlockingReader
357515
/**
358516
* Write bytes into a path.
359517
*

bindings/nodejs/src/capability.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,24 @@ impl Capability {
9696
self.0.read
9797
}
9898

99+
/// If operator supports read with version.
100+
#[napi(getter)]
101+
pub fn read_with_version(&self) -> bool {
102+
self.0.read_with_version
103+
}
104+
105+
/// If operator supports read with range.
106+
#[napi(getter)]
107+
pub fn read_with_if_modified_since(&self) -> bool {
108+
self.0.read_with_if_modified_since
109+
}
110+
111+
/// If operator supports read with if unmodified since.
112+
#[napi(getter)]
113+
pub fn read_with_if_unmodified_since(&self) -> bool {
114+
self.0.read_with_if_unmodified_since
115+
}
116+
99117
/// If operator supports read with if matched.
100118
#[napi(getter)]
101119
pub fn read_with_if_match(&self) -> bool {

bindings/nodejs/src/lib.rs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use std::time::Duration;
2727
use futures::AsyncReadExt;
2828
use futures::TryStreamExt;
2929
use napi::bindgen_prelude::*;
30-
use opendal::options::StatOptions;
30+
use opendal::options::{ReadOptions, ReaderOptions, StatOptions};
3131

3232
mod capability;
3333
mod options;
@@ -214,10 +214,15 @@ impl Operator {
214214
/// const buf = await op.read("path/to/file");
215215
/// ```
216216
#[napi]
217-
pub async fn read(&self, path: String) -> Result<Buffer> {
217+
pub async fn read(
218+
&self,
219+
path: String,
220+
options: Option<options::ReadOptions>,
221+
) -> Result<Buffer> {
222+
let options = options.map_or_else(ReadOptions::default, ReadOptions::from);
218223
let res = self
219224
.async_op
220-
.read(&path)
225+
.read_options(&path, options)
221226
.await
222227
.map_err(format_napi_error)?
223228
.to_vec();
@@ -228,15 +233,20 @@ impl Operator {
228233
///
229234
/// It could be used to read large file in a streaming way.
230235
#[napi]
231-
pub async fn reader(&self, path: String) -> Result<Reader> {
236+
pub async fn reader(
237+
&self,
238+
path: String,
239+
options: Option<options::ReaderOptions>,
240+
) -> Result<Reader> {
241+
let options = options.map_or_else(ReaderOptions::default, ReaderOptions::from);
232242
let r = self
233243
.async_op
234-
.reader(&path)
244+
.reader_options(&path, options)
235245
.await
236246
.map_err(format_napi_error)?;
237247
Ok(Reader {
238248
inner: r
239-
.into_futures_async_read(..)
249+
.into_futures_async_read(std::ops::RangeFull)
240250
.await
241251
.map_err(format_napi_error)?,
242252
})
@@ -249,10 +259,11 @@ impl Operator {
249259
/// const buf = op.readSync("path/to/file");
250260
/// ```
251261
#[napi]
252-
pub fn read_sync(&self, path: String) -> Result<Buffer> {
262+
pub fn read_sync(&self, path: String, options: Option<options::ReadOptions>) -> Result<Buffer> {
263+
let options = options.map_or_else(ReadOptions::default, ReadOptions::from);
253264
let res = self
254265
.blocking_op
255-
.read(&path)
266+
.read_options(&path, options)
256267
.map_err(format_napi_error)?
257268
.to_vec();
258269
Ok(res.into())
@@ -262,8 +273,16 @@ impl Operator {
262273
///
263274
/// It could be used to read large file in a streaming way.
264275
#[napi]
265-
pub fn reader_sync(&self, path: String) -> Result<BlockingReader> {
266-
let r = self.blocking_op.reader(&path).map_err(format_napi_error)?;
276+
pub fn reader_sync(
277+
&self,
278+
path: String,
279+
options: Option<options::ReaderOptions>,
280+
) -> Result<BlockingReader> {
281+
let options = options.map_or_else(ReaderOptions::default, ReaderOptions::from);
282+
let r = self
283+
.blocking_op
284+
.reader_options(&path, options)
285+
.map_err(format_napi_error)?;
267286
Ok(BlockingReader {
268287
inner: r.into_std_read(..).map_err(format_napi_error)?,
269288
})

0 commit comments

Comments
 (0)