You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2025/08/03 09:34:10 UTC

(opendal) branch main updated: feat(bindings/ocaml): add more blocking api for ocaml (#6238)

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

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new b04da7c8c  feat(bindings/ocaml): add more blocking api for ocaml (#6238)
b04da7c8c is described below

commit b04da7c8cb8175237a54783c81a86d868a06ccc8
Author: Asuka Minato <i...@asukaminato.eu.org>
AuthorDate: Sun Aug 3 18:34:04 2025 +0900

     feat(bindings/ocaml): add more blocking api for ocaml (#6238)
    
    * add more api
    
    * try fix
    
    * try fix
    
    * fix test
    
    * fmt
    
    * clippy
    
    * prepare for pub
    
    * seperate pr
    
    * Apply suggestions from code review
---
 .github/actions/setup-ocaml/action.yaml            |   1 +
 .github/workflows/ci_bindings_ocaml.yml            |   9 -
 bindings/ocaml/CHANGES.md                          |   8 +
 bindings/ocaml/dune-project                        |   4 +-
 bindings/ocaml/lib/opendal.ml                      |   8 +
 bindings/ocaml/lib/operator.ml                     |  51 ++
 bindings/ocaml/lib/operator.mli                    | 651 ++++++++++++++++++---
 bindings/ocaml/opendal.opam                        |   2 +
 bindings/ocaml/release.sh                          |  68 +++
 bindings/ocaml/src/operator.ml                     |  44 ++
 bindings/ocaml/src/operator.mli                    |  44 ++
 bindings/ocaml/src/operator/_type.rs               |  21 +
 bindings/ocaml/src/operator/capability.rs          | 114 ++++
 .../ocaml/src/operator/{_type.rs => lister.rs}     |  26 +-
 bindings/ocaml/src/operator/mod.rs                 |  40 ++
 .../src/operator/{_type.rs => operator_info.rs}    |  30 +-
 .../ocaml/src/operator/{_type.rs => writer.rs}     |  26 +-
 bindings/ocaml/test/test.ml                        | 284 ++++++++-
 18 files changed, 1282 insertions(+), 149 deletions(-)

diff --git a/.github/actions/setup-ocaml/action.yaml b/.github/actions/setup-ocaml/action.yaml
index cd096d6d7..765be73b9 100644
--- a/.github/actions/setup-ocaml/action.yaml
+++ b/.github/actions/setup-ocaml/action.yaml
@@ -32,6 +32,7 @@ runs:
         ocaml-compiler: 4.14
         opam-depext: ${{inputs.need-depext == true}}
         opam-pin: ${{inputs.need-pin == true}}
+        dune-cache: true
 
     - name: Set Opam env
       shell: bash
diff --git a/.github/workflows/ci_bindings_ocaml.yml b/.github/workflows/ci_bindings_ocaml.yml
index 44783ad0e..85e539e3e 100644
--- a/.github/workflows/ci_bindings_ocaml.yml
+++ b/.github/workflows/ci_bindings_ocaml.yml
@@ -50,15 +50,6 @@ jobs:
             ${{ runner.os }}-opam-
             ${{ runner.os }}-opam-doc-
 
-      - name: Cache Dune build artifacts
-        uses: actions/cache@v4
-        with:
-          path: bindings/ocaml/_build
-          key: ${{ runner.os }}-dune-${{ hashFiles('bindings/ocaml/**/*.{ml,mli,opam}') }}
-          restore-keys: |
-            ${{ runner.os }}-dune-
-            ${{ runner.os }}-dune-doc-
-
       - name: Setup OCaml toolchain
         uses: ./.github/actions/setup-ocaml
         with:
diff --git a/bindings/ocaml/CHANGES.md b/bindings/ocaml/CHANGES.md
new file mode 100644
index 000000000..74bc51ccb
--- /dev/null
+++ b/bindings/ocaml/CHANGES.md
@@ -0,0 +1,8 @@
+# Changelog
+
+All notable changes to the OpenDAL OCaml bindings will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
diff --git a/bindings/ocaml/dune-project b/bindings/ocaml/dune-project
index b9cd925bf..b42102100 100644
--- a/bindings/ocaml/dune-project
+++ b/bindings/ocaml/dune-project
@@ -17,6 +17,7 @@
 ; under the License.
 
 (name opendal)
+(version 0.1.0)
 (generate_opam_files true)
 (license Apache-2.0)
 (maintainers "OpenDAL Contributors <de...@opendal.apache.org>")
@@ -32,4 +33,5 @@
   (ounit2 (and (>= 2.2.6) :with-test))
   (ocaml (and (>= "4.10.0") (< 5)))
   (dune (>= "2.1"))
-  conf-rust))
+  conf-rust)
+ (tags (storage "data access" s3 filesystem cloud)))
diff --git a/bindings/ocaml/lib/opendal.ml b/bindings/ocaml/lib/opendal.ml
index 2d549daf3..a48afa9d3 100644
--- a/bindings/ocaml/lib/opendal.ml
+++ b/bindings/ocaml/lib/opendal.ml
@@ -19,4 +19,12 @@
 
 module Operator = Operator
 
+(* Re-export types from Opendal_core for convenience *)
 type operator = Opendal_core.Operator.operator
+type reader = Opendal_core.Operator.reader
+type writer = Opendal_core.Operator.writer
+type lister = Opendal_core.Operator.lister
+type metadata = Opendal_core.Operator.metadata
+type entry = Opendal_core.Operator.entry
+type operator_info = Opendal_core.Operator.operator_info
+type capability = Opendal_core.Operator.capability
diff --git a/bindings/ocaml/lib/operator.ml b/bindings/ocaml/lib/operator.ml
index 5c6f6e502..b22fc0b66 100644
--- a/bindings/ocaml/lib/operator.ml
+++ b/bindings/ocaml/lib/operator.ml
@@ -17,24 +17,43 @@
    under the License.
 *)
 
+(** Core operator functions *)
 let new_operator = Opendal_core.Operator.operator
+
 let list = Opendal_core.Operator.blocking_list
+let lister = Opendal_core.Operator.blocking_lister
 let stat = Opendal_core.Operator.blocking_stat
 let is_exist = Opendal_core.Operator.blocking_is_exist
 let create_dir = Opendal_core.Operator.blocking_create_dir
 let read = Opendal_core.Operator.blocking_read
 let reader = Opendal_core.Operator.blocking_reader
 let write = Opendal_core.Operator.blocking_write
+let writer = Opendal_core.Operator.blocking_writer
 let copy = Opendal_core.Operator.blocking_copy
 let rename = Opendal_core.Operator.blocking_rename
 let delete = Opendal_core.Operator.blocking_delete
 let remove = Opendal_core.Operator.blocking_remove
 let remove_all = Opendal_core.Operator.blocking_remove_all
+let check = Opendal_core.Operator.blocking_check
+let info = Opendal_core.Operator.operator_info
 
+(** Reader module for reading data *)
 module Reader = struct
   let pread = Opendal_core.Operator.reader_pread
 end
 
+(** Writer module for writing data *)
+module Writer = struct
+  let write = Opendal_core.Operator.writer_write
+  let close = Opendal_core.Operator.writer_close
+end
+
+(** Lister module for streaming directory listings *)
+module Lister = struct
+  let next = Opendal_core.Operator.lister_next
+end
+
+(** Metadata module for file/directory information *)
 module Metadata = struct
   let is_file = Opendal_core.Operator.metadata_is_file
   let is_dir = Opendal_core.Operator.metadata_is_dir
@@ -46,8 +65,40 @@ module Metadata = struct
   let last_modified = Opendal_core.Operator.metadata_last_modified
 end
 
+(** Entry module for directory entry information *)
 module Entry = struct
   let path = Opendal_core.Operator.entry_path
   let name = Opendal_core.Operator.entry_name
   let metadata = Opendal_core.Operator.entry_metadata
 end
+
+(** OperatorInfo module for operator information *)
+module OperatorInfo = struct
+  let name = Opendal_core.Operator.operator_info_name
+  let scheme = Opendal_core.Operator.operator_info_scheme
+  let root = Opendal_core.Operator.operator_info_root
+  let capability = Opendal_core.Operator.operator_info_capability
+end
+
+(** Capability module for checking supported operations *)
+module Capability = struct
+  let stat = Opendal_core.Operator.capability_stat
+  let read = Opendal_core.Operator.capability_read
+  let write = Opendal_core.Operator.capability_write
+  let create_dir = Opendal_core.Operator.capability_create_dir
+  let delete = Opendal_core.Operator.capability_delete
+  let copy = Opendal_core.Operator.capability_copy
+  let rename = Opendal_core.Operator.capability_rename
+  let list = Opendal_core.Operator.capability_list
+  let list_with_limit = Opendal_core.Operator.capability_list_with_limit
+
+  let list_with_start_after =
+    Opendal_core.Operator.capability_list_with_start_after
+
+  let list_with_recursive = Opendal_core.Operator.capability_list_with_recursive
+  let presign = Opendal_core.Operator.capability_presign
+  let presign_read = Opendal_core.Operator.capability_presign_read
+  let presign_stat = Opendal_core.Operator.capability_presign_stat
+  let presign_write = Opendal_core.Operator.capability_presign_write
+  let shared = Opendal_core.Operator.capability_shared
+end
diff --git a/bindings/ocaml/lib/operator.mli b/bindings/ocaml/lib/operator.mli
index 7b0f0e0ba..0b156fbbe 100644
--- a/bindings/ocaml/lib/operator.mli
+++ b/bindings/ocaml/lib/operator.mli
@@ -17,170 +17,641 @@
    under the License.
 *)
 
+(** {1 OpenDAL OCaml Bindings}
+
+    Apache OpenDAL™ OCaml binding provides a unified data access layer that
+    allows users to easily and efficiently retrieve data from various storage
+    services.
+
+    {2 Basic Usage}
+
+    {[
+      (* Create an operator for local filesystem *)
+      let op =
+        Operator.new_operator "fs" [ ("root", "/tmp") ] |> Result.get_ok
+      in
+
+      (* Write data to a file *)
+      let _ = Operator.write op "hello.txt" (Bytes.of_string "Hello, World!") in
+
+      (* Read data back *)
+      let content = Operator.read op "hello.txt" |> Result.get_ok in
+      print_endline (String.of_bytes content)
+    ]} *)
+
+(** {2 Core Operations} *)
+
 val new_operator :
   string ->
   (string * string) list ->
   (Opendal_core.Operator.operator, string) result
-(** [new_operator scheme config_map] Create a new block operator from given
-    scheme and config_map.
+(** [new_operator scheme config_map] creates a new blocking operator from given
+    scheme and configuration.
 
     @param scheme
-      Supported services, for details, refer to
-      https://opendal.apache.org/docs/category/services/
-    @param config_map Configuration information required by the target service
-    @return The block operator *)
+      The storage service scheme. Supported services include:
+      - "fs" for local filesystem
+      - "s3" for Amazon S3
+      - "gcs" for Google Cloud Storage
+      - "azblob" for Azure Blob Storage
+      - And many more. See {{:https://opendal.apache.org/docs/category/services/} the full list}.
+    
+    @param config_map 
+      Configuration key-value pairs required by the target service.
+      For example, for S3: [("bucket", "my-bucket"); ("region", "us-east-1")]
+    
+    @return 
+      A blocking operator wrapped in Result.
+    
+    @example
+    {[
+      (* Local filesystem *)
+      let fs_op = new_operator "fs" [("root", "/tmp")] in
+      
+      (* S3 storage *)
+      let s3_op = new_operator "s3" [
+        ("bucket", "my-bucket");
+        ("region", "us-east-1");
+        ("access_key_id", "...");
+        ("secret_access_key", "...")
+      ] in
+    ]}
+*)
 
 val list :
   Opendal_core.Operator.operator ->
   string ->
   (Opendal_core.Operator.entry array, string) result
+(** [list operator path] lists all entries in the given directory path.
+
+    @param operator The blocking operator
+    @param path Directory path to list (should end with "/")
+    @return Array of directory entries
+    
+    Note: This loads all entries into memory. For large directories,
+    consider using {!lister} for streaming access.
+    
+    @example
+    {[
+      match list op "data/" with
+      | Ok entries -> 
+          Array.iter (fun entry -> 
+            printf "Found: %s\n" (Entry.name entry)
+          ) entries
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
+
+val lister :
+  Opendal_core.Operator.operator ->
+  string ->
+  (Opendal_core.Operator.lister, string) result
+(** [lister operator path] creates a streaming lister for the given directory.
+
+    @param operator The blocking operator
+    @param path Directory path to list (should end with "/")
+    @return A lister for streaming access to entries
+    
+    Use {!Lister.next} to iterate through entries one by one.
+    This is memory-efficient for large directories.
+    
+    @example
+    {[
+      match lister op "data/" with
+      | Ok lst ->
+          let rec iter () =
+            match Lister.next lst with
+            | Ok (Some entry) -> 
+                printf "Found: %s\n" (Entry.name entry);
+                iter ()
+            | Ok None -> () (* End of listing *)
+            | Error err -> printf "Error: %s\n" err
+          in iter ()
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val stat :
   Opendal_core.Operator.operator ->
   string ->
   (Opendal_core.Operator.metadata, string) result
-(** [is_exist operator path] Get current path's metadata **without cache**
-    directly.
-
-    @param operator The operator
-    @param path want to stat
-    @return metadata *)
+(** [stat operator path] gets metadata for the given path.
+
+    @param operator The blocking operator
+    @param path Path to get metadata for
+    @return Metadata for the path
+    
+    @example
+    {[
+      match stat op "file.txt" with
+      | Ok meta ->
+          printf "Size: %Ld bytes\n" (Metadata.content_length meta);
+          printf "Is file: %b\n" (Metadata.is_file meta)
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val is_exist : Opendal_core.Operator.operator -> string -> (bool, string) result
-(** [is_exist operator path] Check if this path exists or not.
-
-    @param operator The operator
-    @param path want to check
-    @return is exists *)
+(** [is_exist operator path] checks if the given path exists.
+
+    @param operator The blocking operator
+    @param path Path to check
+    @return true if path exists, false otherwise
+    
+    @example
+    {[
+      match is_exist op "file.txt" with
+      | Ok true -> print_endline "File exists"
+      | Ok false -> print_endline "File does not exist"  
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val create_dir :
   Opendal_core.Operator.operator -> string -> (bool, string) result
-(** [create_dir operator path] Create a dir at given path.
-
-    # Notes
-
-    To indicate that a path is a directory, it is compulsory to include a
-    trailing / in the path. Failure to do so may result in `NotADirectory` error
-    being returned by OpenDAL.
-
-    # Behavior
-
-    - Create on existing dir will succeed.
-    - Create dir is always recursive, works like `mkdir -p`
-
-    @param operator The operator
-    @param path want to create dir *)
+(** [create_dir operator path] creates a directory at the given path.
+
+    @param operator The blocking operator
+    @param path Directory path to create (must end with "/")
+    
+    Notes:
+    - Creating existing directories succeeds
+    - Creates parent directories recursively (like "mkdir -p")
+    - Path must end with "/" to indicate it's a directory
+    
+    @example
+    {[
+      match create_dir op "data/subdir/" with
+      | Ok () -> print_endline "Directory created"
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val read :
   Opendal_core.Operator.operator -> string -> (char array, string) result
-(** [read operator path] Read the whole path into a bytes.
-
-    @param operator The operator
-    @param path want to read
-    @return data of path *)
+(** [read operator path] reads the entire file content into memory.
+
+    @param operator The blocking operator
+    @param path File path to read
+    @return File content as a char array
+    
+    For large files or streaming access, consider using {!reader}.
+    
+    @example
+    {[
+      match read op "file.txt" with
+      | Ok content -> 
+          let bytes = Array.to_seq content |> Bytes.of_seq in
+          print_endline (Bytes.to_string bytes)
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val reader :
   Opendal_core.Operator.operator ->
   string ->
   (Opendal_core.Operator.reader, string) result
-(** [read operator path] Create a new reader which can read the whole path.
-
-    @param operator The operator
-    @param path want to read
-    @return reader *)
+(** [reader operator path] creates a reader for streaming file access.
+
+    @param operator The blocking operator
+    @param path File path to read
+    @return A reader for streaming access
+    
+    Use {!Reader.pread} to read data from specific positions.
+    
+    @example
+    {[
+      match reader op "file.txt" with
+      | Ok r ->
+          let buf = Bytes.create 1024 in
+          (match Reader.pread r buf 0L with
+           | Ok bytes_read -> printf "Read %d bytes\n" bytes_read
+           | Error err -> printf "Error: %s\n" err)
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val write :
   Opendal_core.Operator.operator -> string -> bytes -> (unit, string) result
-(** [write operator path data] Write bytes into given path.
-    - Write will make sure all bytes has been written, or an error will be
-      returned.
+(** [write operator path data] writes data to the given path.
+
+    @param operator The blocking operator
+    @param path File path to write to
+    @param data Data to write
+    
+    Notes:
+    - Overwrites existing files
+    - Creates parent directories if needed
+    - Ensures all data is written atomically
+    
+    @example
+    {[
+      let data = Bytes.of_string "Hello, World!" in
+      match write op "hello.txt" data with
+      | Ok () -> print_endline "File written"
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
-    @param operator The operator
-    @param path want to write
-    @param data want to write *)
+val writer :
+  Opendal_core.Operator.operator ->
+  string ->
+  (Opendal_core.Operator.writer, string) result
+(** [writer operator path] creates a writer for streaming file writes.
+
+    @param operator The blocking operator
+    @param path File path to write to
+    @return A writer for streaming writes
+    
+    Use {!Writer.write} to write data chunks and {!Writer.close} to finalize.
+    
+    @example
+    {[
+      match writer op "large_file.txt" with
+      | Ok w ->
+          let _ = Writer.write w (Bytes.of_string "chunk1") in
+          let _ = Writer.write w (Bytes.of_string "chunk2") in
+          Writer.close w
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val copy :
   Opendal_core.Operator.operator -> string -> string -> (unit, string) result
-(** [copy operator from to] Copy a file from [from] to [to].
-    - [from] and [to] must be a file.
-    - [to] will be overwritten if it exists.
-    - If [from] and [to] are the same, nothing will happen.
-    - copy is idempotent. For same [from] and [to] input, the result will be the
-      same.
-
-    @param operator The operator
-    @param from file path
-    @param to file path *)
+(** [copy operator from to] copies a file from source to destination.
+
+    @param operator The blocking operator
+    @param from Source file path
+    @param to Destination file path
+    
+    Notes:
+    - Overwrites destination if it exists
+    - Creates parent directories if needed
+    - Both paths must be files (not directories)
+    
+    @example
+    {[
+      match copy op "source.txt" "backup.txt" with
+      | Ok () -> print_endline "File copied"
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val rename :
   Opendal_core.Operator.operator -> string -> string -> (unit, string) result
-(** [rename operator from to] Rename a file from [from] to [to].
-    - [from] and [to] must be a file.
-    - [to] will be overwritten if it exists.
-    - If [from] and [to] are the same, a `IsSameFile` error will occur.
-
-    @param operator The operator
-    @param from file path
-    @param to file path *)
+(** [rename operator from to] renames/moves a file from source to destination.
+
+    @param operator The blocking operator
+    @param from Source file path
+    @param to Destination file path
+    
+    Notes:
+    - Overwrites destination if it exists
+    - Creates parent directories if needed
+    - Source file is removed after successful operation
+    
+    @example
+    {[
+      match rename op "old_name.txt" "new_name.txt" with
+      | Ok () -> print_endline "File renamed"
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val delete : Opendal_core.Operator.operator -> string -> (unit, string) result
-(** [delete operator path] Delete given path.
-    - Delete not existing error won't return errors.
-
-    @param operator The block operator
-    @param path file path *)
+(** [delete operator path] deletes the file at the given path.
+
+    @param operator The blocking operator
+    @param path File path to delete
+    
+    Notes:
+    - Succeeds even if file doesn't exist (idempotent)
+    - Cannot be used to delete directories (use {!remove_all} instead)
+    
+    @example
+    {[
+      match delete op "unwanted.txt" with
+      | Ok () -> print_endline "File deleted"
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val remove :
   Opendal_core.Operator.operator -> string array -> (unit, string) result
-(** [remove operator paths] Remove path array.
-    - We don't support batch delete now, will call delete on each object in turn
-
-    @param operator The block operator
-    @param paths file path array *)
+(** [remove operator paths] deletes multiple files in a batch operation.
+
+    @param operator The blocking operator
+    @param paths Array of file paths to delete
+    
+    This is more efficient than calling {!delete} multiple times
+    for services that support batch deletion.
+    
+    @example
+    {[
+      let files = [|"file1.txt"; "file2.txt"; "file3.txt"|] in
+      match remove op files with
+      | Ok () -> print_endline "Files deleted"
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
 val remove_all :
   Opendal_core.Operator.operator -> string -> (unit, string) result
-(** [remove_all operator path] Remove the path and all nested dirs and files
-    recursively.
-    - We don't support batch delete now, will call delete on each object in turn
+(** [remove_all operator path] recursively deletes the directory and all its contents.
+
+    @param operator The blocking operator
+    @param path Directory path to delete (should end with "/")
+    
+    ⚠️  WARNING: This operation permanently deletes all files and subdirectories.
+    Use with extreme caution!
+    
+    @example
+    {[
+      match remove_all op "temp_data/" with
+      | Ok () -> print_endline "Directory deleted"
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
-    @param operator The block operator
-    @param path file path *)
+val check : Opendal_core.Operator.operator -> (unit, string) result
+(** [check operator] performs a health check on the storage service.
+
+    @param operator The blocking operator
+    
+    This verifies that the operator can connect to and access
+    the configured storage service.
+    
+    @example
+    {[
+      match check op with
+      | Ok () -> print_endline "Storage service is accessible"
+      | Error err -> printf "Error: %s\n" err
+    ]}
+*)
 
+val info : Opendal_core.Operator.operator -> Opendal_core.Operator.operator_info
+(** [info operator] returns information about the operator.
+
+    @param operator The blocking operator
+    @return Operator information (name, scheme, root path, capabilities)
+    
+    @example
+    {[
+      let info = info op in
+      printf "Service: %s\n" (OperatorInfo.name info);
+      printf "Scheme: %s\n" (OperatorInfo.scheme info);
+      printf "Root: %s\n" (OperatorInfo.root info)
+    ]}
+*)
+
+(** {2 Reader Operations}
+
+    Module for reading data from files with fine-grained control. *)
 module Reader : sig
   val pread :
     Opendal_core.Operator.reader -> bytes -> int64 -> (int, string) result
-  (** [read reader buf] Read data to [buf] and return data size.*)
+  (** [pread reader buf offset] reads data from the reader at the given offset.
+
+      @param reader The reader instance
+      @param buf Buffer to read data into
+      @param offset Byte offset in the file to start reading from
+      @return Number of bytes actually read
+      
+      @example
+      {[
+        let buf = Bytes.create 1024 in
+        match Reader.pread reader buf 100L with
+        | Ok bytes_read -> 
+            printf "Read %d bytes starting at offset 100\n" bytes_read
+        | Error err -> printf "Error: %s\n" err
+      ]}
+  *)
+end
+
+(** {2 Writer Operations}
+
+    Module for writing data to files in chunks. *)
+module Writer : sig
+  val write : Opendal_core.Operator.writer -> bytes -> (unit, string) result
+  (** [write writer data] writes a chunk of data.
+
+      @param writer The writer instance
+      @param data Data chunk to write
+      
+      @example
+      {[
+        match Writer.write writer (Bytes.of_string "Hello ") with
+        | Ok () -> 
+            (match Writer.write writer (Bytes.of_string "World!") with
+             | Ok () -> print_endline "Data written"
+             | Error err -> printf "Error: %s\n" err)
+        | Error err -> printf "Error: %s\n" err
+      ]}
+  *)
+
+  val close :
+    Opendal_core.Operator.writer ->
+    (Opendal_core.Operator.metadata, string) result
+  (** [close writer] finalizes the write operation and returns metadata.
+
+      Must be called to ensure all data is properly written and committed.
+      
+      @param writer The writer instance
+      @return Metadata about the written file
+      
+      @example
+      {[
+        (* After writing all chunks *)
+        match Writer.close writer with
+        | Ok metadata -> 
+            printf "File closed successfully, size: %Ld bytes\n" 
+              (Metadata.content_length metadata)
+        | Error err -> printf "Error closing file: %s\n" err
+      ]}
+  *)
+end
+
+(** {2 Lister Operations}
+
+    Module for streaming directory listings. *)
+module Lister : sig
+  val next :
+    Opendal_core.Operator.lister ->
+    (Opendal_core.Operator.entry option, string) result
+  (** [next lister] gets the next entry from the directory listing.
+
+      @param lister The lister instance
+      @return Some entry if available, None if no more entries
+      
+      @example
+      {[
+        let rec process_all lister =
+          match Lister.next lister with
+          | Ok (Some entry) ->
+              printf "Processing: %s\n" (Entry.name entry);
+              process_all lister
+          | Ok None ->
+              print_endline "Finished processing all entries"
+          | Error err ->
+              printf "Error: %s\n" err
+        in
+        process_all my_lister
+      ]}
+  *)
 end
 
+(** {2 Metadata Operations}
+
+    Module for accessing file and directory metadata. *)
 module Metadata : sig
   val is_file : Opendal_core.Operator.metadata -> bool
-  (** [is_file metadata] Returns `true` if this metadata is for a file.*)
+  (** [is_file metadata] checks if the metadata represents a file.
+      
+      @example {[if Metadata.is_file meta then print_endline "It's a file"]}
+  *)
 
   val is_dir : Opendal_core.Operator.metadata -> bool
-  (** [is_dir metadata] Returns `true` if this metadata is for a directory.*)
+  (** [is_dir metadata] checks if the metadata represents a directory.
+      
+      @example {[if Metadata.is_dir meta then print_endline "It's a directory"]}
+  *)
 
   val content_length : Opendal_core.Operator.metadata -> int64
-  (** [content_length metadata] Content length of this entry.*)
+  (** [content_length metadata] gets the size of the content in bytes.
+      
+      @example {[printf "File size: %Ld bytes\n" (Metadata.content_length meta)]}
+  *)
 
   val content_md5 : Opendal_core.Operator.metadata -> string option
-  (** [content_md5 metadata] Content MD5 of this entry.*)
+  (** [content_md5 metadata] gets the MD5 hash of the content, if available. *)
 
   val content_type : Opendal_core.Operator.metadata -> string option
-  (** [content_type metadata] Content Type of this entry.*)
+  (** [content_type metadata] gets the MIME type of the content, if available.
+  *)
 
   val content_disposition : Opendal_core.Operator.metadata -> string option
-  (** [content_disposition metadata] Content-Disposition of this entry*)
+  (** [content_disposition metadata] gets the Content-Disposition header, if
+      available. *)
 
   val etag : Opendal_core.Operator.metadata -> string option
-  (** [etag metadata] ETag of this entry.*)
+  (** [etag metadata] gets the ETag of the content, if available. *)
 
   val last_modified : Opendal_core.Operator.metadata -> int64 option
-  (** [last_modified metadata] Last modified of this entry.*)
+  (** [last_modified metadata] gets the last modification time as Unix
+      timestamp, if available. *)
 end
 
+(** {2 Entry Operations}
+
+    Module for accessing directory entry information. *)
 module Entry : sig
   val path : Opendal_core.Operator.entry -> string
+  (** [path entry] gets the full path of the directory entry.
+      
+      @example {[printf "Full path: %s\n" (Entry.path entry)]}
+  *)
+
   val name : Opendal_core.Operator.entry -> string
+  (** [name entry] gets the name (filename) of the directory entry.
+      
+      @example {[printf "Name: %s\n" (Entry.name entry)]}
+  *)
+
   val metadata : Opendal_core.Operator.entry -> Opendal_core.Operator.metadata
+  (** [metadata entry] gets the metadata for the directory entry.
+      
+      @example 
+      {[
+        let meta = Entry.metadata entry in
+        printf "Size: %Ld\n" (Metadata.content_length meta)
+      ]}
+  *)
+end
+
+(** {2 Operator Information}
+
+    Module for accessing operator configuration and capabilities. *)
+module OperatorInfo : sig
+  val name : Opendal_core.Operator.operator_info -> string
+  (** [name info] gets the name of the operator instance. *)
+
+  val scheme : Opendal_core.Operator.operator_info -> string
+  (** [scheme info] gets the storage scheme (e.g., "fs", "s3", "gcs").
+      
+      @example {[printf "Using scheme: %s\n" (OperatorInfo.scheme info)]}
+  *)
+
+  val root : Opendal_core.Operator.operator_info -> string
+  (** [root info] gets the root path configured for the operator.
+      
+      @example {[printf "Root path: %s\n" (OperatorInfo.root info)]}
+  *)
+
+  val capability :
+    Opendal_core.Operator.operator_info -> Opendal_core.Operator.capability
+  (** [capability info] gets the capability information for the operator.
+      
+      @example 
+      {[
+        let cap = OperatorInfo.capability info in
+        if Capability.write cap then print_endline "Write operations supported"
+      ]}
+  *)
+end
+
+(** {2 Capability Checking}
+
+    Module for checking what operations are supported by the storage backend. *)
+module Capability : sig
+  val stat : Opendal_core.Operator.capability -> bool
+  (** [stat cap] checks if stat operations are supported. *)
+
+  val read : Opendal_core.Operator.capability -> bool
+  (** [read cap] checks if read operations are supported. *)
+
+  val write : Opendal_core.Operator.capability -> bool
+  (** [write cap] checks if write operations are supported. *)
+
+  val create_dir : Opendal_core.Operator.capability -> bool
+  (** [create_dir cap] checks if directory creation is supported. *)
+
+  val delete : Opendal_core.Operator.capability -> bool
+  (** [delete cap] checks if delete operations are supported. *)
+
+  val copy : Opendal_core.Operator.capability -> bool
+  (** [copy cap] checks if copy operations are supported. *)
+
+  val rename : Opendal_core.Operator.capability -> bool
+  (** [rename cap] checks if rename operations are supported. *)
+
+  val list : Opendal_core.Operator.capability -> bool
+  (** [list cap] checks if list operations are supported. *)
+
+  val list_with_limit : Opendal_core.Operator.capability -> bool
+  (** [list_with_limit cap] checks if list operations with limit are supported.
+  *)
+
+  val list_with_start_after : Opendal_core.Operator.capability -> bool
+  (** [list_with_start_after cap] checks if list operations with start_after are
+      supported. *)
+
+  val list_with_recursive : Opendal_core.Operator.capability -> bool
+  (** [list_with_recursive cap] checks if recursive list operations are
+      supported. *)
+
+  val presign : Opendal_core.Operator.capability -> bool
+  (** [presign cap] checks if presigned URL generation is supported. *)
+
+  val presign_read : Opendal_core.Operator.capability -> bool
+  (** [presign_read cap] checks if presigned read URLs are supported. *)
+
+  val presign_stat : Opendal_core.Operator.capability -> bool
+  (** [presign_stat cap] checks if presigned stat URLs are supported. *)
+
+  val presign_write : Opendal_core.Operator.capability -> bool
+  (** [presign_write cap] checks if presigned write URLs are supported. *)
+
+  val shared : Opendal_core.Operator.capability -> bool
+  (** [shared cap] checks if the operator can be safely shared between threads.
+  *)
 end
diff --git a/bindings/ocaml/opendal.opam b/bindings/ocaml/opendal.opam
index 99b84a9ea..5b089ee63 100644
--- a/bindings/ocaml/opendal.opam
+++ b/bindings/ocaml/opendal.opam
@@ -1,11 +1,13 @@
 # This file is generated by dune, edit dune-project instead
 opam-version: "2.0"
+version: "0.1.0"
 synopsis: "Apache OpenDAL™ OCaml Binding"
 description:
   "Apache OpenDAL™ is a data access layer that allows users to easily and efficiently retrieve data from various storage services in a unified way."
 maintainer: ["OpenDAL Contributors <de...@opendal.apache.org>"]
 authors: ["OpenDAL Contributors <de...@opendal.apache.org>"]
 license: "Apache-2.0"
+tags: ["storage" "data access" "s3" "filesystem" "cloud"]
 homepage: "https://github.com/apache/opendal"
 doc: "https://opendal.apache.org/"
 bug-reports: "https://github.com/apache/opendal/issues"
diff --git a/bindings/ocaml/release.sh b/bindings/ocaml/release.sh
new file mode 100755
index 000000000..855f12efa
--- /dev/null
+++ b/bindings/ocaml/release.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+
+# 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.
+
+set -euo pipefail
+
+# Release script for OpenDAL OCaml bindings to opam
+
+VERSION=${1:-""}
+if [ -z "$VERSION" ]; then
+    echo "Usage: $0 <version>"
+    echo "Example: $0 0.48.0"
+    exit 1
+fi
+
+echo "🚀 Releasing OpenDAL OCaml bindings version $VERSION"
+
+# Step 1: Update version in dune-project
+echo "📝 Updating version in dune-project..."
+sed -i "s/(version [^)]*)/(version $VERSION)/" dune-project
+
+# Step 2: Regenerate opam file
+echo "🔄 Regenerating opam file..."
+dune build
+
+# Step 3: Run tests
+echo "🧪 Running tests..."
+dune runtest
+
+# Step 4: Build documentation
+echo "📚 Building documentation..."
+dune build @doc
+
+# Step 5: Create a source distribution
+echo "📦 Creating source distribution..."
+dune-release tag v$VERSION
+dune-release distrib
+
+# Step 6: Publish to opam-repository (requires manual PR)
+echo "🌐 Creating opam-repository PR..."
+echo "Run the following command to submit to opam-repository:"
+echo "dune-release publish distrib --verbose"
+echo ""
+echo "Or manually create a PR to opam-repository with the generated opam file:"
+echo "https://github.com/ocaml/opam-repository"
+
+echo "✅ Release preparation complete!"
+echo ""
+echo "Next steps:"
+echo "1. Review the generated tarball in _build/"
+echo "2. Test installation: opam install ./"
+echo "3. Submit PR to opam-repository"
+echo "4. Tag the release in git: git tag v$VERSION && git push origin v$VERSION" 
\ No newline at end of file
diff --git a/bindings/ocaml/src/operator.ml b/bindings/ocaml/src/operator.ml
index 91d8a16e3..9ad8440a6 100644
--- a/bindings/ocaml/src/operator.ml
+++ b/bindings/ocaml/src/operator.ml
@@ -6,8 +6,32 @@ open! Bigarray
 
 type operator
 type reader
+type writer
+type lister
 type metadata
 type entry
+type operator_info
+type capability
+type presigned_request
+
+(* file: capability.rs *)
+
+external capability_stat: capability -> bool  = "capability_stat"
+external capability_read: capability -> bool  = "capability_read"
+external capability_write: capability -> bool  = "capability_write"
+external capability_create_dir: capability -> bool  = "capability_create_dir"
+external capability_delete: capability -> bool  = "capability_delete"
+external capability_copy: capability -> bool  = "capability_copy"
+external capability_rename: capability -> bool  = "capability_rename"
+external capability_list: capability -> bool  = "capability_list"
+external capability_list_with_limit: capability -> bool  = "capability_list_with_limit"
+external capability_list_with_start_after: capability -> bool  = "capability_list_with_start_after"
+external capability_list_with_recursive: capability -> bool  = "capability_list_with_recursive"
+external capability_presign: capability -> bool  = "capability_presign"
+external capability_presign_read: capability -> bool  = "capability_presign_read"
+external capability_presign_stat: capability -> bool  = "capability_presign_stat"
+external capability_presign_write: capability -> bool  = "capability_presign_write"
+external capability_shared: capability -> bool  = "capability_shared"
 
 (* file: entry.rs *)
 
@@ -15,6 +39,10 @@ external entry_path: entry -> string  = "entry_path"
 external entry_name: entry -> string  = "entry_name"
 external entry_metadata: entry -> metadata  = "entry_metadata"
 
+(* file: lister.rs *)
+
+external lister_next: lister -> (entry option, string) Result.t  = "lister_next"
+
 (* file: metadata.rs *)
 
 external metadata_is_file: metadata -> bool  = "metadata_is_file"
@@ -30,18 +58,34 @@ external metadata_last_modified: metadata -> int64 option  = "metadata_last_modi
 
 external operator: string -> (string * string) list -> (operator, string) Result.t  = "operator"
 external blocking_list: operator -> string -> (entry array, string) Result.t  = "blocking_list"
+external blocking_lister: operator -> string -> (lister, string) Result.t  = "blocking_lister"
 external blocking_stat: operator -> string -> (metadata, string) Result.t  = "blocking_stat"
 external blocking_is_exist: operator -> string -> (bool, string) Result.t  = "blocking_is_exist"
 external blocking_create_dir: operator -> string -> (bool, string) Result.t  = "blocking_create_dir"
 external blocking_read: operator -> string -> (char array, string) Result.t  = "blocking_read"
 external blocking_reader: operator -> string -> (reader, string) Result.t  = "blocking_reader"
 external blocking_write: operator -> string -> bytes -> (unit, string) Result.t  = "blocking_write"
+external blocking_writer: operator -> string -> (writer, string) Result.t  = "blocking_writer"
 external blocking_copy: operator -> string -> string -> (unit, string) Result.t  = "blocking_copy"
 external blocking_rename: operator -> string -> string -> (unit, string) Result.t  = "blocking_rename"
 external blocking_delete: operator -> string -> (unit, string) Result.t  = "blocking_delete"
 external blocking_remove: operator -> string array -> (unit, string) Result.t  = "blocking_remove"
 external blocking_remove_all: operator -> string -> (unit, string) Result.t  = "blocking_remove_all"
+external blocking_check: operator -> (unit, string) Result.t  = "blocking_check"
+external operator_info: operator -> operator_info  = "operator_info"
+external operator_info_capability: operator_info -> capability  = "operator_info_capability"
+
+(* file: operator_info.rs *)
+
+external operator_info_name: operator_info -> string  = "operator_info_name"
+external operator_info_scheme: operator_info -> string  = "operator_info_scheme"
+external operator_info_root: operator_info -> string  = "operator_info_root"
 
 (* file: reader.rs *)
 
 external reader_pread: reader -> bytes -> int64 -> (int, string) Result.t  = "reader_pread"
+
+(* file: writer.rs *)
+
+external writer_write: writer -> bytes -> (unit, string) Result.t  = "writer_write"
+external writer_close: writer -> (metadata, string) Result.t  = "writer_close"
diff --git a/bindings/ocaml/src/operator.mli b/bindings/ocaml/src/operator.mli
index 91d8a16e3..9ad8440a6 100644
--- a/bindings/ocaml/src/operator.mli
+++ b/bindings/ocaml/src/operator.mli
@@ -6,8 +6,32 @@ open! Bigarray
 
 type operator
 type reader
+type writer
+type lister
 type metadata
 type entry
+type operator_info
+type capability
+type presigned_request
+
+(* file: capability.rs *)
+
+external capability_stat: capability -> bool  = "capability_stat"
+external capability_read: capability -> bool  = "capability_read"
+external capability_write: capability -> bool  = "capability_write"
+external capability_create_dir: capability -> bool  = "capability_create_dir"
+external capability_delete: capability -> bool  = "capability_delete"
+external capability_copy: capability -> bool  = "capability_copy"
+external capability_rename: capability -> bool  = "capability_rename"
+external capability_list: capability -> bool  = "capability_list"
+external capability_list_with_limit: capability -> bool  = "capability_list_with_limit"
+external capability_list_with_start_after: capability -> bool  = "capability_list_with_start_after"
+external capability_list_with_recursive: capability -> bool  = "capability_list_with_recursive"
+external capability_presign: capability -> bool  = "capability_presign"
+external capability_presign_read: capability -> bool  = "capability_presign_read"
+external capability_presign_stat: capability -> bool  = "capability_presign_stat"
+external capability_presign_write: capability -> bool  = "capability_presign_write"
+external capability_shared: capability -> bool  = "capability_shared"
 
 (* file: entry.rs *)
 
@@ -15,6 +39,10 @@ external entry_path: entry -> string  = "entry_path"
 external entry_name: entry -> string  = "entry_name"
 external entry_metadata: entry -> metadata  = "entry_metadata"
 
+(* file: lister.rs *)
+
+external lister_next: lister -> (entry option, string) Result.t  = "lister_next"
+
 (* file: metadata.rs *)
 
 external metadata_is_file: metadata -> bool  = "metadata_is_file"
@@ -30,18 +58,34 @@ external metadata_last_modified: metadata -> int64 option  = "metadata_last_modi
 
 external operator: string -> (string * string) list -> (operator, string) Result.t  = "operator"
 external blocking_list: operator -> string -> (entry array, string) Result.t  = "blocking_list"
+external blocking_lister: operator -> string -> (lister, string) Result.t  = "blocking_lister"
 external blocking_stat: operator -> string -> (metadata, string) Result.t  = "blocking_stat"
 external blocking_is_exist: operator -> string -> (bool, string) Result.t  = "blocking_is_exist"
 external blocking_create_dir: operator -> string -> (bool, string) Result.t  = "blocking_create_dir"
 external blocking_read: operator -> string -> (char array, string) Result.t  = "blocking_read"
 external blocking_reader: operator -> string -> (reader, string) Result.t  = "blocking_reader"
 external blocking_write: operator -> string -> bytes -> (unit, string) Result.t  = "blocking_write"
+external blocking_writer: operator -> string -> (writer, string) Result.t  = "blocking_writer"
 external blocking_copy: operator -> string -> string -> (unit, string) Result.t  = "blocking_copy"
 external blocking_rename: operator -> string -> string -> (unit, string) Result.t  = "blocking_rename"
 external blocking_delete: operator -> string -> (unit, string) Result.t  = "blocking_delete"
 external blocking_remove: operator -> string array -> (unit, string) Result.t  = "blocking_remove"
 external blocking_remove_all: operator -> string -> (unit, string) Result.t  = "blocking_remove_all"
+external blocking_check: operator -> (unit, string) Result.t  = "blocking_check"
+external operator_info: operator -> operator_info  = "operator_info"
+external operator_info_capability: operator_info -> capability  = "operator_info_capability"
+
+(* file: operator_info.rs *)
+
+external operator_info_name: operator_info -> string  = "operator_info_name"
+external operator_info_scheme: operator_info -> string  = "operator_info_scheme"
+external operator_info_root: operator_info -> string  = "operator_info_root"
 
 (* file: reader.rs *)
 
 external reader_pread: reader -> bytes -> int64 -> (int, string) Result.t  = "reader_pread"
+
+(* file: writer.rs *)
+
+external writer_write: writer -> bytes -> (unit, string) Result.t  = "writer_write"
+external writer_close: writer -> (metadata, string) Result.t  = "writer_close"
diff --git a/bindings/ocaml/src/operator/_type.rs b/bindings/ocaml/src/operator/_type.rs
index 50bb7a5b1..10d715ba7 100644
--- a/bindings/ocaml/src/operator/_type.rs
+++ b/bindings/ocaml/src/operator/_type.rs
@@ -27,6 +27,14 @@ ocaml::custom!(Operator);
 pub struct Reader(pub(crate) od::blocking::Reader);
 ocaml::custom!(Reader);
 
+#[ocaml::sig]
+pub struct Writer(pub(crate) od::blocking::Writer);
+ocaml::custom!(Writer);
+
+#[ocaml::sig]
+pub struct Lister(pub(crate) od::blocking::Lister);
+ocaml::custom!(Lister);
+
 #[ocaml::sig]
 pub struct Metadata(pub(crate) od::Metadata);
 ocaml::custom!(Metadata);
@@ -34,3 +42,16 @@ ocaml::custom!(Metadata);
 #[ocaml::sig]
 pub struct Entry(pub(crate) od::Entry);
 ocaml::custom!(Entry);
+
+#[ocaml::sig]
+pub struct OperatorInfo(pub(crate) od::OperatorInfo);
+ocaml::custom!(OperatorInfo);
+
+#[ocaml::sig]
+pub struct Capability(pub(crate) od::Capability);
+ocaml::custom!(Capability);
+
+#[ocaml::sig]
+#[allow(dead_code)]
+pub struct PresignedRequest(pub(crate) od::raw::PresignedRequest);
+ocaml::custom!(PresignedRequest);
diff --git a/bindings/ocaml/src/operator/capability.rs b/bindings/ocaml/src/operator/capability.rs
new file mode 100644
index 000000000..d0f84eb71
--- /dev/null
+++ b/bindings/ocaml/src/operator/capability.rs
@@ -0,0 +1,114 @@
+// 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.
+
+use super::*;
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_stat(cap: &Capability) -> bool {
+    cap.0.stat
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_read(cap: &Capability) -> bool {
+    cap.0.read
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_write(cap: &Capability) -> bool {
+    cap.0.write
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_create_dir(cap: &Capability) -> bool {
+    cap.0.create_dir
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_delete(cap: &Capability) -> bool {
+    cap.0.delete
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_copy(cap: &Capability) -> bool {
+    cap.0.copy
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_rename(cap: &Capability) -> bool {
+    cap.0.rename
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_list(cap: &Capability) -> bool {
+    cap.0.list
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_list_with_limit(cap: &Capability) -> bool {
+    cap.0.list_with_limit
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_list_with_start_after(cap: &Capability) -> bool {
+    cap.0.list_with_start_after
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_list_with_recursive(cap: &Capability) -> bool {
+    cap.0.list_with_recursive
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_presign(cap: &Capability) -> bool {
+    cap.0.presign
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_presign_read(cap: &Capability) -> bool {
+    cap.0.presign_read
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_presign_stat(cap: &Capability) -> bool {
+    cap.0.presign_stat
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_presign_write(cap: &Capability) -> bool {
+    cap.0.presign_write
+}
+
+#[ocaml::func]
+#[ocaml::sig("capability -> bool ")]
+pub fn capability_shared(cap: &Capability) -> bool {
+    cap.0.shared
+}
diff --git a/bindings/ocaml/src/operator/_type.rs b/bindings/ocaml/src/operator/lister.rs
similarity index 59%
copy from bindings/ocaml/src/operator/_type.rs
copy to bindings/ocaml/src/operator/lister.rs
index 50bb7a5b1..16dbf2a07 100644
--- a/bindings/ocaml/src/operator/_type.rs
+++ b/bindings/ocaml/src/operator/lister.rs
@@ -15,22 +15,14 @@
 // specific language governing permissions and limitations
 // under the License.
 
-// For ocaml-rs, the build order in the same build group is the order of the file names.
-// In order to use the type in the function in the generated ocaml file, it must be defined on the first generated file.
 use super::*;
 
-#[ocaml::sig]
-pub struct Operator(pub(crate) od::blocking::Operator);
-ocaml::custom!(Operator);
-
-#[ocaml::sig]
-pub struct Reader(pub(crate) od::blocking::Reader);
-ocaml::custom!(Reader);
-
-#[ocaml::sig]
-pub struct Metadata(pub(crate) od::Metadata);
-ocaml::custom!(Metadata);
-
-#[ocaml::sig]
-pub struct Entry(pub(crate) od::Entry);
-ocaml::custom!(Entry);
+#[ocaml::func]
+#[ocaml::sig("lister -> (entry option, string) Result.t ")]
+pub fn lister_next(lister: &mut Lister) -> Result<Option<ocaml::Pointer<Entry>>, String> {
+    match lister.0.next() {
+        Some(Ok(entry)) => Ok(Some(Entry(entry).into())),
+        Some(Err(e)) => Err(e.to_string()),
+        None => Ok(None),
+    }
+}
diff --git a/bindings/ocaml/src/operator/mod.rs b/bindings/ocaml/src/operator/mod.rs
index 9cbb51b40..420709966 100644
--- a/bindings/ocaml/src/operator/mod.rs
+++ b/bindings/ocaml/src/operator/mod.rs
@@ -16,9 +16,13 @@
 // under the License.
 
 mod _type;
+mod capability;
 mod entry;
+mod lister;
 mod metadata;
+mod operator_info;
 mod reader;
+mod writer;
 
 use _type::*;
 
@@ -53,6 +57,15 @@ pub fn blocking_list(
     )
 }
 
+#[ocaml::func]
+#[ocaml::sig("operator -> string -> (lister, string) Result.t ")]
+pub fn blocking_lister(
+    operator: &mut Operator,
+    path: String,
+) -> Result<ocaml::Pointer<Lister>, String> {
+    map_res_error(operator.0.lister(path.as_str())).map(|op| Lister(op).into())
+}
+
 #[ocaml::func]
 #[ocaml::sig("operator -> string -> (metadata, string) Result.t ")]
 pub fn blocking_stat(
@@ -99,6 +112,15 @@ pub fn blocking_write(
     map_res_error(operator.0.write(path.as_str(), bs).map(|_| ()))
 }
 
+#[ocaml::func]
+#[ocaml::sig("operator -> string -> (writer, string) Result.t ")]
+pub fn blocking_writer(
+    operator: &mut Operator,
+    path: String,
+) -> Result<ocaml::Pointer<Writer>, String> {
+    map_res_error(operator.0.writer(path.as_str())).map(|op| Writer(op).into())
+}
+
 #[ocaml::func]
 #[ocaml::sig("operator -> string -> string -> (unit, string) Result.t ")]
 pub fn blocking_copy(operator: &mut Operator, from: String, to: String) -> Result<(), String> {
@@ -128,3 +150,21 @@ pub fn blocking_remove(operator: &mut Operator, path: Vec<String>) -> Result<(),
 pub fn blocking_remove_all(operator: &mut Operator, path: String) -> Result<(), String> {
     map_res_error(operator.0.remove_all(path.as_str()))
 }
+
+#[ocaml::func]
+#[ocaml::sig("operator -> (unit, string) Result.t ")]
+pub fn blocking_check(operator: &mut Operator) -> Result<(), String> {
+    map_res_error(operator.0.check())
+}
+
+#[ocaml::func]
+#[ocaml::sig("operator -> operator_info ")]
+pub fn operator_info(operator: &mut Operator) -> ocaml::Pointer<OperatorInfo> {
+    OperatorInfo(operator.0.info()).into()
+}
+
+#[ocaml::func]
+#[ocaml::sig("operator_info -> capability ")]
+pub fn operator_info_capability(info: &mut OperatorInfo) -> ocaml::Pointer<Capability> {
+    Capability(info.0.full_capability()).into()
+}
diff --git a/bindings/ocaml/src/operator/_type.rs b/bindings/ocaml/src/operator/operator_info.rs
similarity index 59%
copy from bindings/ocaml/src/operator/_type.rs
copy to bindings/ocaml/src/operator/operator_info.rs
index 50bb7a5b1..ee35511b2 100644
--- a/bindings/ocaml/src/operator/_type.rs
+++ b/bindings/ocaml/src/operator/operator_info.rs
@@ -15,22 +15,22 @@
 // specific language governing permissions and limitations
 // under the License.
 
-// For ocaml-rs, the build order in the same build group is the order of the file names.
-// In order to use the type in the function in the generated ocaml file, it must be defined on the first generated file.
 use super::*;
 
-#[ocaml::sig]
-pub struct Operator(pub(crate) od::blocking::Operator);
-ocaml::custom!(Operator);
+#[ocaml::func]
+#[ocaml::sig("operator_info -> string ")]
+pub fn operator_info_name(info: &OperatorInfo) -> String {
+    info.0.name().to_string()
+}
 
-#[ocaml::sig]
-pub struct Reader(pub(crate) od::blocking::Reader);
-ocaml::custom!(Reader);
+#[ocaml::func]
+#[ocaml::sig("operator_info -> string ")]
+pub fn operator_info_scheme(info: &OperatorInfo) -> String {
+    info.0.scheme().to_string()
+}
 
-#[ocaml::sig]
-pub struct Metadata(pub(crate) od::Metadata);
-ocaml::custom!(Metadata);
-
-#[ocaml::sig]
-pub struct Entry(pub(crate) od::Entry);
-ocaml::custom!(Entry);
+#[ocaml::func]
+#[ocaml::sig("operator_info -> string ")]
+pub fn operator_info_root(info: &OperatorInfo) -> String {
+    info.0.root().to_string()
+}
diff --git a/bindings/ocaml/src/operator/_type.rs b/bindings/ocaml/src/operator/writer.rs
similarity index 59%
copy from bindings/ocaml/src/operator/_type.rs
copy to bindings/ocaml/src/operator/writer.rs
index 50bb7a5b1..e78793bc1 100644
--- a/bindings/ocaml/src/operator/_type.rs
+++ b/bindings/ocaml/src/operator/writer.rs
@@ -15,22 +15,16 @@
 // specific language governing permissions and limitations
 // under the License.
 
-// For ocaml-rs, the build order in the same build group is the order of the file names.
-// In order to use the type in the function in the generated ocaml file, it must be defined on the first generated file.
 use super::*;
 
-#[ocaml::sig]
-pub struct Operator(pub(crate) od::blocking::Operator);
-ocaml::custom!(Operator);
+#[ocaml::func]
+#[ocaml::sig("writer -> bytes -> (unit, string) Result.t ")]
+pub fn writer_write(writer: &mut Writer, bs: &'static [u8]) -> Result<(), String> {
+    map_res_error(writer.0.write(bs))
+}
 
-#[ocaml::sig]
-pub struct Reader(pub(crate) od::blocking::Reader);
-ocaml::custom!(Reader);
-
-#[ocaml::sig]
-pub struct Metadata(pub(crate) od::Metadata);
-ocaml::custom!(Metadata);
-
-#[ocaml::sig]
-pub struct Entry(pub(crate) od::Entry);
-ocaml::custom!(Entry);
+#[ocaml::func]
+#[ocaml::sig("writer -> (metadata, string) Result.t ")]
+pub fn writer_close(writer: &mut Writer) -> Result<ocaml::Pointer<Metadata>, String> {
+    map_res_error(writer.0.close()).map(|m| Metadata(m).into())
+}
diff --git a/bindings/ocaml/test/test.ml b/bindings/ocaml/test/test.ml
index 8e0f12fbd..909fe9915 100644
--- a/bindings/ocaml/test/test.ml
+++ b/bindings/ocaml/test/test.ml
@@ -28,8 +28,10 @@ let new_test_block_operator test_ctxt : operator =
   let cfgs = [ ("root", bracket_tmpdir test_ctxt) ] in
   test_check_result (Operator.new_operator "fs" cfgs)
 
+(** Basic operator creation tests *)
 let test_new_block_operator _ = ignore new_test_block_operator
 
+(** Test directory creation and recursive removal *)
 let test_create_dir_and_remove_all test_ctxt =
   let bo = new_test_block_operator test_ctxt in
   ignore (test_check_result (Operator.create_dir bo "/testdir/"));
@@ -41,6 +43,7 @@ let test_create_dir_and_remove_all test_ctxt =
        (Operator.write bo "/testdir/bar" (Bytes.of_string "foo")));
   ignore (test_check_result (Operator.remove_all bo "/testdir/"))
 
+(** Test basic write and read operations *)
 let test_block_write_and_read test_ctxt =
   let bo = new_test_block_operator test_ctxt in
   ignore
@@ -50,6 +53,7 @@ let test_block_write_and_read test_ctxt =
   assert_equal "helloworld"
     (data |> Array.to_seq |> Bytes.of_seq |> Bytes.to_string)
 
+(** Test reader functionality *)
 let test_operator_reader test_ctxt =
   let bo = new_test_block_operator test_ctxt in
   ignore
@@ -61,6 +65,24 @@ let test_operator_reader test_ctxt =
   assert_equal 5 i;
   assert_equal "world" (Bytes.to_string data)
 
+(** Test writer functionality *)
+let test_operator_writer test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+  let writer = Operator.writer bo "test_writer_file" |> test_check_result in
+  ignore
+    (test_check_result
+       (Operator.Writer.write writer (Bytes.of_string "Hello ")));
+  ignore
+    (test_check_result
+       (Operator.Writer.write writer (Bytes.of_string "World!")));
+  ignore (test_check_result (Operator.Writer.close writer));
+
+  (* Verify the content was written correctly *)
+  let data = test_check_result (Operator.read bo "test_writer_file") in
+  assert_equal "Hello World!"
+    (data |> Array.to_seq |> Bytes.of_seq |> Bytes.to_string)
+
+(** Test metadata functionality *)
 let test_operator_stat test_ctxt =
   let bo = new_test_block_operator test_ctxt in
   ignore
@@ -72,6 +94,7 @@ let test_operator_stat test_ctxt =
   assert_equal 10L (Operator.Metadata.content_length metadata);
   ()
 
+(** Test list functionality *)
 let test_list test_ctxt =
   let bo = new_test_block_operator test_ctxt in
   ignore (test_check_result (Operator.create_dir bo "/testdir/"));
@@ -89,15 +112,274 @@ let test_list test_ctxt =
   assert_equal 3 (Array.length array);
   ()
 
+(** Test streaming lister functionality *)
+let test_lister test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+  ignore (test_check_result (Operator.create_dir bo "/testdir/"));
+  ignore
+    (test_check_result
+       (Operator.write bo "/testdir/file1" (Bytes.of_string "content1")));
+  ignore
+    (test_check_result
+       (Operator.write bo "/testdir/file2" (Bytes.of_string "content2")));
+
+  let lister = Operator.lister bo "testdir/" |> test_check_result in
+  let entries = ref [] in
+
+  (* Collect all entries using the lister *)
+  let rec collect_entries () =
+    match Operator.Lister.next lister with
+    | Ok (Some entry) ->
+        entries := Operator.Entry.name entry :: !entries;
+        collect_entries ()
+    | Ok None -> ()
+    | Error err -> assert_failure err
+  in
+  collect_entries ();
+
+  let actual = Array.of_list (List.rev !entries) in
+  let expected = [| "testdir/"; "file1"; "file2" |] in
+  List.iter (Array.sort compare) [ expected; actual ];
+  assert_equal expected actual;
+  assert_equal 3 (Array.length actual)
+
+(** Test operator info functionality *)
+let test_operator_info test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+  let info = Operator.info bo in
+
+  (* Test basic info properties *)
+  let scheme = Operator.OperatorInfo.scheme info in
+  assert_equal "fs" scheme;
+
+  let root = Operator.OperatorInfo.root info in
+  assert_bool "Root should not be empty" (String.length root > 0);
+
+  (* Test capability information *)
+  let cap = Operator.OperatorInfo.capability info in
+  assert_equal true (Operator.Capability.read cap);
+  assert_equal true (Operator.Capability.write cap);
+  assert_equal true (Operator.Capability.stat cap);
+  assert_equal true (Operator.Capability.list cap);
+  assert_equal true (Operator.Capability.create_dir cap);
+  assert_equal true (Operator.Capability.delete cap);
+
+  (* Filesystem should support these operations *)
+  assert_equal true (Operator.Capability.copy cap);
+  assert_equal true (Operator.Capability.rename cap)
+
+(** Test file existence checking *)
+let test_is_exist test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+
+  (* Test non-existent file *)
+  let exists = Operator.is_exist bo "nonexistent.txt" |> test_check_result in
+  assert_equal false exists;
+
+  (* Create a file and test it exists *)
+  ignore
+    (test_check_result
+       (Operator.write bo "testfile.txt" (Bytes.of_string "test")));
+  let exists = Operator.is_exist bo "testfile.txt" |> test_check_result in
+  assert_equal true exists
+
+(** Test file copy operation *)
+let test_copy test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+
+  (* Create source file *)
+  let content = "Hello, copy test!" in
+  ignore
+    (test_check_result
+       (Operator.write bo "source.txt" (Bytes.of_string content)));
+
+  (* Copy the file *)
+  ignore (test_check_result (Operator.copy bo "source.txt" "destination.txt"));
+
+  (* Verify both files exist and have same content *)
+  let source_data = test_check_result (Operator.read bo "source.txt") in
+  let dest_data = test_check_result (Operator.read bo "destination.txt") in
+
+  let source_str =
+    source_data |> Array.to_seq |> Bytes.of_seq |> Bytes.to_string
+  in
+  let dest_str = dest_data |> Array.to_seq |> Bytes.of_seq |> Bytes.to_string in
+
+  assert_equal content source_str;
+  assert_equal content dest_str
+
+(** Test file rename operation *)
+let test_rename test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+
+  (* Create source file *)
+  let content = "Hello, rename test!" in
+  ignore
+    (test_check_result
+       (Operator.write bo "old_name.txt" (Bytes.of_string content)));
+
+  (* Rename the file *)
+  ignore (test_check_result (Operator.rename bo "old_name.txt" "new_name.txt"));
+
+  (* Verify old file doesn't exist and new file has correct content *)
+  let old_exists = Operator.is_exist bo "old_name.txt" |> test_check_result in
+  let new_exists = Operator.is_exist bo "new_name.txt" |> test_check_result in
+
+  assert_equal false old_exists;
+  assert_equal true new_exists;
+
+  let new_data = test_check_result (Operator.read bo "new_name.txt") in
+  let new_str = new_data |> Array.to_seq |> Bytes.of_seq |> Bytes.to_string in
+  assert_equal content new_str
+
+(** Test delete operation *)
+let test_delete test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+
+  (* Create a file *)
+  ignore
+    (test_check_result
+       (Operator.write bo "to_delete.txt" (Bytes.of_string "delete me")));
+
+  (* Verify it exists *)
+  let exists_before =
+    Operator.is_exist bo "to_delete.txt" |> test_check_result
+  in
+  assert_equal true exists_before;
+
+  (* Delete it *)
+  ignore (test_check_result (Operator.delete bo "to_delete.txt"));
+
+  (* Verify it's gone *)
+  let exists_after =
+    Operator.is_exist bo "to_delete.txt" |> test_check_result
+  in
+  assert_equal false exists_after
+
+(** Test remove (batch delete) operation *)
+let test_remove test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+
+  (* Create multiple files *)
+  let files = [| "file1.txt"; "file2.txt"; "file3.txt" |] in
+  Array.iter
+    (fun file ->
+      ignore
+        (test_check_result
+           (Operator.write bo file (Bytes.of_string ("content of " ^ file)))))
+    files;
+
+  (* Verify all files exist *)
+  Array.iter
+    (fun file ->
+      let exists = Operator.is_exist bo file |> test_check_result in
+      assert_equal true exists)
+    files;
+
+  (* Remove all files *)
+  ignore (test_check_result (Operator.remove bo files));
+
+  (* Verify all files are gone *)
+  Array.iter
+    (fun file ->
+      let exists = Operator.is_exist bo file |> test_check_result in
+      assert_equal false exists)
+    files
+
+(** Test operator check functionality *)
+let test_check test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+
+  (* Check should succeed for a valid operator *)
+  match Operator.check bo with
+  | Ok () -> () (* Success is expected *)
+  | Error err -> assert_failure ("Check failed: " ^ err)
+
+(** Test multiple operations together *)
+let test_complex_workflow test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+
+  (* Create directory structure *)
+  ignore (test_check_result (Operator.create_dir bo "project/"));
+  ignore (test_check_result (Operator.create_dir bo "project/src/"));
+  ignore (test_check_result (Operator.create_dir bo "project/docs/"));
+
+  (* Create some files using writer *)
+  let readme_writer =
+    Operator.writer bo "project/README.md" |> test_check_result
+  in
+  ignore
+    (test_check_result
+       (Operator.Writer.write readme_writer (Bytes.of_string "# My Project\n")));
+  ignore
+    (test_check_result
+       (Operator.Writer.write readme_writer
+          (Bytes.of_string "This is a test project.\n")));
+  ignore (test_check_result (Operator.Writer.close readme_writer));
+
+  (* Create a source file *)
+  ignore
+    (test_check_result
+       (Operator.write bo "project/src/main.ml"
+          (Bytes.of_string "let () = print_endline \"Hello, World!\"")));
+
+  (* List the project directory *)
+  let entries = Operator.list bo "project/" |> test_check_result in
+  assert_equal 4 (Array.length entries);
+
+  (* project/, src/, docs/, README.md *)
+
+  (* Verify file contents *)
+  let readme_data = test_check_result (Operator.read bo "project/README.md") in
+  let readme_str =
+    readme_data |> Array.to_seq |> Bytes.of_seq |> Bytes.to_string
+  in
+  let contains_substring s sub =
+    let len_s = String.length s in
+    let len_sub = String.length sub in
+    let rec check i =
+      if i > len_s - len_sub then false
+      else if String.sub s i len_sub = sub then true
+      else check (i + 1)
+    in
+    if len_sub = 0 then true else check 0
+  in
+  assert_bool "README should contain project info"
+    (contains_substring readme_str "My Project");
+
+  (* Copy the main file *)
+  ignore
+    (test_check_result
+       (Operator.copy bo "project/src/main.ml" "project/src/backup.ml"));
+
+  (* Check capabilities *)
+  let info = Operator.info bo in
+  let cap = Operator.OperatorInfo.capability info in
+  assert_equal true (Operator.Capability.write cap);
+  assert_equal true (Operator.Capability.copy cap);
+
+  (* Clean up *)
+  ignore (test_check_result (Operator.remove_all bo "project/"))
+
 let suite =
-  "suite"
+  "OCaml OpenDAL Test Suite"
   >::: [
          "test_new_block_operator" >:: test_new_block_operator;
          "test_create_dir_and_remove_all" >:: test_create_dir_and_remove_all;
          "test_block_write_and_read" >:: test_block_write_and_read;
          "test_operator_reader" >:: test_operator_reader;
+         "test_operator_writer" >:: test_operator_writer;
          "test_operator_stat" >:: test_operator_stat;
          "test_list" >:: test_list;
+         "test_lister" >:: test_lister;
+         "test_operator_info" >:: test_operator_info;
+         "test_is_exist" >:: test_is_exist;
+         "test_copy" >:: test_copy;
+         "test_rename" >:: test_rename;
+         "test_delete" >:: test_delete;
+         "test_remove" >:: test_remove;
+         "test_check" >:: test_check;
+         "test_complex_workflow" >:: test_complex_workflow;
        ]
 
 let () = run_test_tt_main suite