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') + }) +}
