This is an automated email from the ASF dual-hosted git repository.

suyanhanx pushed a commit to branch binding-nodejs-capability
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git

commit 88f31a01b8a5d790a1e2b38991f7ae3cdabd3921
Author: suyanhanx <[email protected]>
AuthorDate: Wed Nov 22 23:31:17 2023 +0800

    feat(bindings/nodejs): capability
    
    Signed-off-by: suyanhanx <[email protected]>
---
 bindings/nodejs/generated.js                    |   3 +-
 bindings/nodejs/index.d.ts                      | 112 ++++++++++
 bindings/nodejs/src/capability.rs               | 258 +++++++++++++++++++++++-
 bindings/nodejs/src/lib.rs                      |   6 +
 bindings/nodejs/tests/suites/index.mjs          |   2 +
 bindings/nodejs/tests/suites/services.suite.mjs |  11 +
 6 files changed, 383 insertions(+), 9 deletions(-)

diff --git a/bindings/nodejs/generated.js b/bindings/nodejs/generated.js
index 2b40ec25e..b21cd679e 100644
--- a/bindings/nodejs/generated.js
+++ b/bindings/nodejs/generated.js
@@ -271,8 +271,9 @@ if (!nativeBinding) {
   throw new Error(`Failed to load native binding`)
 }
 
-const { Operator, Entry, Metadata, Lister, BlockingLister, Layer, RetryLayer } 
= nativeBinding
+const { Capability, Operator, Entry, Metadata, Lister, BlockingLister, Layer, 
RetryLayer } = nativeBinding
 
+module.exports.Capability = Capability
 module.exports.Operator = Operator
 module.exports.Entry = Entry
 module.exports.Metadata = Metadata
diff --git a/bindings/nodejs/index.d.ts b/bindings/nodejs/index.d.ts
index 6c6ceba31..5e995e94a 100644
--- a/bindings/nodejs/index.d.ts
+++ b/bindings/nodejs/index.d.ts
@@ -36,9 +36,121 @@ export interface PresignedRequest {
   /** HTTP headers of this request. */
   headers: Record<string, string>
 }
+/**
+ * Capability is used to describe what operations are supported
+ * by current Operator.
+ *
+ * Via capability, we can know:
+ *
+ * - Whether current Operator supports read or not.
+ * - Whether current Operator supports read with if match or not.
+ * - What's current Operator max supports batch operations count.
+ *
+ * Add fields of Capabilities with be public and can be accessed directly.
+ */
+export class Capability {
+  /** If operator supports stat. */
+  get stat(): boolean
+  /** If operator supports stat with if match. */
+  get statWithIfMatch(): boolean
+  /** If operator supports stat with if none match. */
+  get statWithIfNoneMatch(): boolean
+  /** If operator supports read. */
+  get read(): boolean
+  /** If operator supports seek on returning reader. */
+  get readCanSeek(): boolean
+  /** If operator supports next on returning reader. */
+  get readCanNext(): boolean
+  /** If operator supports read with range. */
+  get readWithRange(): boolean
+  /** If operator supports read with if match. */
+  get readWithIfMatch(): boolean
+  /** If operator supports read with if none match. */
+  get readWithIfNoneMatch(): boolean
+  /** if operator supports read with override cache control. */
+  get readWithOverrideCacheControl(): boolean
+  /** if operator supports read with override content disposition. */
+  get readWithOverrideContentDisposition(): boolean
+  /** if operator supports read with override content type. */
+  get readWithOverrideContentType(): boolean
+  /** If operator supports write. */
+  get write(): boolean
+  /** If operator supports write can be called in multi times. */
+  get writeCanMulti(): boolean
+  /** If operator supports write with empty content. */
+  get writeCanEmpty(): boolean
+  /** If operator supports write by append. */
+  get writeCanAppend(): boolean
+  /** If operator supports write with content type. */
+  get writeWithContentType(): boolean
+  /** If operator supports write with content disposition. */
+  get writeWithContentDisposition(): boolean
+  /** If operator supports write with cache control. */
+  get writeWithCacheControl(): boolean
+  /**
+   * write_multi_max_size is the max size that services support in write_multi.
+   *
+   * For example, AWS S3 supports 5GiB as max in write_multi.
+   */
+  get writeMultiMaxSize(): number | null
+  /**
+   * write_multi_min_size is the min size that services support in write_multi.
+   *
+   * For example, AWS S3 requires at least 5MiB in write_multi expect the last 
one.
+   */
+  get writeMultiMinSize(): number | null
+  /**
+   * write_multi_align_size is the align size that services required in 
write_multi.
+   *
+   * For example, Google GCS requires align size to 256KiB in write_multi.
+   */
+  get writeMultiAlignSize(): number | null
+  /**
+   * write_total_max_size is the max size that services support in write_total.
+   *
+   * For example, Cloudflare D1 supports 1MB as max in write_total.
+   */
+  get writeTotalMaxSize(): number | null
+  /** If operator supports create dir. */
+  get createDir(): boolean
+  /** If operator supports delete. */
+  get delete(): boolean
+  /** If operator supports copy. */
+  get copy(): boolean
+  /** If operator supports rename. */
+  get rename(): boolean
+  /** If operator supports list. */
+  get list(): boolean
+  /** If backend supports list with limit. */
+  get listWithLimit(): boolean
+  /** If backend supports list with start after. */
+  get listWithStartAfter(): boolean
+  /** If backend supports list with recursive. */
+  get listWithRecursive(): boolean
+  /** If backend supports list without recursive. */
+  get listWithoutRecursive(): boolean
+  /** If operator supports presign. */
+  get presign(): boolean
+  /** If operator supports presign read. */
+  get presignRead(): boolean
+  /** If operator supports presign stat. */
+  get presignStat(): boolean
+  /** If operator supports presign write. */
+  get presignWrite(): boolean
+  /** If operator supports batch. */
+  get batch(): boolean
+  /** If operator supports batch delete. */
+  get batchDelete(): boolean
+  /** The max operations that operator supports in batch. */
+  get batchMaxOperations(): number | null
+  /** If operator supports blocking. */
+  get blocking(): boolean
+}
 export class Operator {
   /** @see For a detailed definition of scheme, see 
https://opendal.apache.org/docs/category/services */
   constructor(scheme: string, options?: Record<string, string> | undefined | 
null)
+  /** Get current operator(service)'s full capability. */
+  capability(): Capability
   /**
    * Get current path's metadata **without cache** directly.
    *
diff --git a/bindings/nodejs/src/capability.rs 
b/bindings/nodejs/src/capability.rs
index 2eaee90a1..98553b128 100644
--- a/bindings/nodejs/src/capability.rs
+++ b/bindings/nodejs/src/capability.rs
@@ -1,44 +1,286 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
 
+/// Capability is used to describe what operations are supported
+/// by current Operator.
+///
+/// Via capability, we can know:
+///
+/// - Whether current Operator supports read or not.
+/// - Whether current Operator supports read with if match or not.
+/// - What's current Operator max supports batch operations count.
+///
+/// Add fields of Capabilities with be public and can be accessed directly.
 #[napi]
 pub struct Capability(opendal::Capability);
 
 impl Capability {
-    pub fn new(op: opendal::Operator) -> Self {
-        Self(op.info().full_capability())
+    pub fn new(cap: opendal::Capability) -> Self {
+        Self(cap)
     }
 }
 
 #[napi]
 impl Capability {
+    /// If operator supports stat.
     #[napi(getter)]
     pub fn stat(&self) -> bool {
         self.0.stat
     }
 
-    // stat_with_if_match
+    /// If operator supports stat with if match.
     #[napi(getter)]
-    pub fn stat_with_if_none_match(&self) -> bool {
-        self.0.stat_with_if_none_match
+    pub fn stat_with_if_match(&self) -> bool {
+        self.0.stat_with_if_match
     }
 
-    // stat_with_if_none_match
+    /// If operator supports stat with if none match.
     #[napi(getter)]
-    pub fn stat_with_if_match(&self) -> bool {
-        self.0.stat_with_if_match
+    pub fn stat_with_if_none_match(&self) -> bool {
+        self.0.stat_with_if_none_match
     }
 
+    /// If operator supports read.
     #[napi(getter)]
     pub fn read(&self) -> bool {
         self.0.read
     }
 
+    /// If operator supports seek on returning reader.
+    #[napi(getter)]
+    pub fn read_can_seek(&self) -> bool {
+        self.0.read_can_seek
+    }
+
+    /// If operator supports next on returning reader.
+    #[napi(getter)]
+    pub fn read_can_next(&self) -> bool {
+        self.0.read_can_next
+    }
+
+    /// If operator supports read with range.
+    #[napi(getter)]
+    pub fn read_with_range(&self) -> bool {
+        self.0.read_with_range
+    }
+
+    /// If operator supports read with if match.
+    #[napi(getter)]
+    pub fn read_with_if_match(&self) -> bool {
+        self.0.read_with_if_match
+    }
+
+    /// If operator supports read with if none match.
+    #[napi(getter)]
+    pub fn read_with_if_none_match(&self) -> bool {
+        self.0.read_with_if_none_match
+    }
+
+    /// if operator supports read with override cache control.
+    #[napi(getter)]
+    pub fn read_with_override_cache_control(&self) -> bool {
+        self.0.read_with_override_cache_control
+    }
+
+    /// if operator supports read with override content disposition.
+    #[napi(getter)]
+    pub fn read_with_override_content_disposition(&self) -> bool {
+        self.0.read_with_override_content_disposition
+    }
+
+    /// if operator supports read with override content type.
+    #[napi(getter)]
+    pub fn read_with_override_content_type(&self) -> bool {
+        self.0.read_with_override_content_type
+    }
+
+    /// If operator supports write.
     #[napi(getter)]
     pub fn write(&self) -> bool {
         self.0.write
     }
 
+    /// If operator supports write can be called in multi times.
+    #[napi(getter)]
+    pub fn write_can_multi(&self) -> bool {
+        self.0.write_can_multi
+    }
+
+    /// If operator supports write with empty content.
+    #[napi(getter)]
+    pub fn write_can_empty(&self) -> bool {
+        self.0.write_can_empty
+    }
+
+    /// If operator supports write by append.
+    #[napi(getter)]
+    pub fn write_can_append(&self) -> bool {
+        self.0.write_can_append
+    }
+
+    /// If operator supports write with content type.
+    #[napi(getter)]
+    pub fn write_with_content_type(&self) -> bool {
+        self.0.write_with_content_type
+    }
+
+    /// If operator supports write with content disposition.
+    #[napi(getter)]
+    pub fn write_with_content_disposition(&self) -> bool {
+        self.0.write_with_content_disposition
+    }
+
+    /// If operator supports write with cache control.
+    #[napi(getter)]
+    pub fn write_with_cache_control(&self) -> bool {
+        self.0.write_with_cache_control
+    }
+
+    /// write_multi_max_size is the max size that services support in 
write_multi.
+    ///
+    /// For example, AWS S3 supports 5GiB as max in write_multi.
+    #[napi(getter)]
+    pub fn write_multi_max_size(&self) -> Option<u32> {
+        self.0.write_multi_max_size.map(|v| v as u32)
+    }
+
+    /// write_multi_min_size is the min size that services support in 
write_multi.
+    ///
+    /// For example, AWS S3 requires at least 5MiB in write_multi expect the 
last one.
+    #[napi(getter)]
+    pub fn write_multi_min_size(&self) -> Option<u32> {
+        self.0.write_multi_min_size.map(|v| v as u32)
+    }
+
+    /// write_multi_align_size is the align size that services required in 
write_multi.
+    ///
+    /// For example, Google GCS requires align size to 256KiB in write_multi.
+    #[napi(getter)]
+    pub fn write_multi_align_size(&self) -> Option<u32> {
+        self.0.write_multi_align_size.map(|v| v as u32)
+    }
+
+    /// write_total_max_size is the max size that services support in 
write_total.
+    ///
+    /// For example, Cloudflare D1 supports 1MB as max in write_total.
+    #[napi(getter)]
+    pub fn write_total_max_size(&self) -> Option<u32> {
+        self.0.write_total_max_size.map(|v| v as u32)
+    }
+
+    /// If operator supports create dir.
+    #[napi(getter)]
+    pub fn create_dir(&self) -> bool {
+        self.0.create_dir
+    }
+
+    /// If operator supports delete.
     #[napi(getter)]
     pub fn delete(&self) -> bool {
         self.0.delete
     }
+
+    /// If operator supports copy.
+    #[napi(getter)]
+    pub fn copy(&self) -> bool {
+        self.0.copy
+    }
+
+    /// If operator supports rename.
+    #[napi(getter)]
+    pub fn rename(&self) -> bool {
+        self.0.rename
+    }
+
+    /// If operator supports list.
+    #[napi(getter)]
+    pub fn list(&self) -> bool {
+        self.0.list
+    }
+
+    /// If backend supports list with limit.
+    #[napi(getter)]
+    pub fn list_with_limit(&self) -> bool {
+        self.0.list_with_limit
+    }
+
+    /// If backend supports list with start after.
+    #[napi(getter)]
+    pub fn list_with_start_after(&self) -> bool {
+        self.0.list_with_start_after
+    }
+
+    /// If backend supports list with recursive.
+    #[napi(getter)]
+    pub fn list_with_recursive(&self) -> bool {
+        self.0.list_with_recursive
+    }
+
+    /// If backend supports list without recursive.
+    #[napi(getter)]
+    pub fn list_without_recursive(&self) -> bool {
+        self.0.list_without_recursive
+    }
+
+    /// If operator supports presign.
+    #[napi(getter)]
+    pub fn presign(&self) -> bool {
+        self.0.presign
+    }
+
+    /// If operator supports presign read.
+    #[napi(getter)]
+    pub fn presign_read(&self) -> bool {
+        self.0.presign_read
+    }
+
+    /// If operator supports presign stat.
+    #[napi(getter)]
+    pub fn presign_stat(&self) -> bool {
+        self.0.presign_stat
+    }
+
+    /// If operator supports presign write.
+    #[napi(getter)]
+    pub fn presign_write(&self) -> bool {
+        self.0.presign_write
+    }
+
+    /// If operator supports batch.
+    #[napi(getter)]
+    pub fn batch(&self) -> bool {
+        self.0.batch
+    }
+
+    /// If operator supports batch delete.
+    #[napi(getter)]
+    pub fn batch_delete(&self) -> bool {
+        self.0.batch_delete
+    }
+
+    /// The max operations that operator supports in batch.
+    #[napi(getter)]
+    pub fn batch_max_operations(&self) -> Option<u32> {
+        self.0.batch_max_operations.map(|v| v as u32)
+    }
+
+    /// If operator supports blocking.
+    #[napi(getter)]
+    pub fn blocking(&self) -> bool {
+        self.0.blocking
+    }
 }
diff --git a/bindings/nodejs/src/lib.rs b/bindings/nodejs/src/lib.rs
index ec964e594..e243ce8b1 100644
--- a/bindings/nodejs/src/lib.rs
+++ b/bindings/nodejs/src/lib.rs
@@ -58,6 +58,12 @@ impl Operator {
         Ok(Operator(op))
     }
 
+    /// Get current operator(service)'s full capability.
+    #[napi]
+    pub fn capability(&self) -> Result<capability::Capability> {
+        Ok(capability::Capability::new(self.0.info().full_capability()))
+    }
+
     /// Get current path's metadata **without cache** directly.
     ///
     /// ### Notes
diff --git a/bindings/nodejs/tests/suites/index.mjs 
b/bindings/nodejs/tests/suites/index.mjs
index a67467f28..cbfe739ee 100644
--- a/bindings/nodejs/tests/suites/index.mjs
+++ b/bindings/nodejs/tests/suites/index.mjs
@@ -22,6 +22,7 @@ import { Operator, layers } from '../../index.js'
 import { checkRandomRootEnabled, generateRandomRoot, loadConfigFromEnv } from 
'../utils.mjs'
 
 import { run as AsyncIOTestRun } from './async.suite.mjs'
+import { run as ServicesTestRun } from './services.suite.mjs'
 import { run as SyncIOTestRun } from './sync.suite.mjs'
 
 export function runner(testName, scheme) {
@@ -46,6 +47,7 @@ export function runner(testName, scheme) {
 
   describe.skipIf(!operator)(testName, () => {
     AsyncIOTestRun(operator)
+    ServicesTestRun(operator)
     SyncIOTestRun(operator)
   })
 }
diff --git a/bindings/nodejs/tests/suites/services.suite.mjs 
b/bindings/nodejs/tests/suites/services.suite.mjs
new file mode 100644
index 000000000..798a3484f
--- /dev/null
+++ b/bindings/nodejs/tests/suites/services.suite.mjs
@@ -0,0 +1,11 @@
+export function run(operator) {
+  test('get capability', () => {
+    assert.ok(operator.capability())
+    assert.ok(operator.capability().read)
+  })
+
+  test('try to non-exist capability', () => {
+    assert.ok(operator.capability())
+    assert.ifError(operator.capability().nonExist, 'try get a non-exist 
capability should return undefined')
+  })
+}

Reply via email to