How to combine records and attachments

Use one xitdb-clj swap! to publish an attachment and update its application record. This guide assumes an application with xitdb-clj and xitstash on the classpath. See the tested adapter revisions.

Select a separate map for content

Load the library and adapter:

(require '[ol.xitstash :as objects]
         '[xitdb.common :as xc]
         '[xitdb.db :as xdb])
(import '[io.github.radarroark.xitdb ReadHashMap]
        '[java.io Closeable InputStream])

Create an :objects map beside the application’s :status. Inside swap!, unwrap that map and take its native cursor:

(with-open [^Closeable db       (xdb/xit-db :memory)
            ^Closeable prepared (objects/prepare (byte-array [1 2 3]))]
  (reset! db {:objects {} :status :draft})
  (swap! db
    (fn [state]
      (let [store (.cursor ^ReadHashMap (xc/unwrap (:objects state)))]
        (objects/initialize! store)
        (objects/put! store prepared)
        (objects/create! store [{:name "attachment.bin"
                                 :digest (:digest prepared)}]))
      (assoc state :status :published)))
  (let [store (.cursor ^ReadHashMap (xc/unwrap (:objects @db)))]
    (with-open [^InputStream input (objects/open store (:digest prepared))]
      {:status (:status @db)
       :path   (objects/path store 1)
       :bytes  (vec (.readAllBytes input))})))

Result:

{:status :published :path ["attachment.bin"] :bytes [1 2 3]}

If a write fails, let the exception leave swap! so both the application change and the binary change abort. Do not catch the error inside the callback and commit a partial result.

In xitdb-clj :compat mode, use string keys for your application maps.

Keep the object store separate from collection operations

Use xc/unwrap and .cursor to reach the native subtree. xdb-cursor is a path view, not the native cursor required by xitstash.

Do not call xdb/materialize on the object-store subtree or edit its internal keys with assoc. Use ordinary collection operations on application data beside it.

Coordinate concurrent access, close and compaction. For direct native access, use the database monitor or an application lock that prevents overlap. The example is single-threaded.

Read history and compact

Use xdb/deref-at to get an older application value, then select its object-store subtree. Keep its streams within the database lifetime.

Close affected readers before calling xdb/compact outside swap!. It creates a new database containing the latest value and at most one history entry. It does not replace the original file.

Validate and replace that file using the application’s lifecycle policy. See How to delete content and reclaim space.