Skip to main content

Attach and Present Files with Blob Reference

Evidence status: This recipe follows the current Java source example. See Latest Versions for installable packages.

Use the optional BlobReferenceModule when a business record needs an attachment that lives longer than a download link. An invoice PDF, support-case attachment, or product image stays associated with the record through a stable BlobRef. File bytes live in storage; the record persists the reference, not the bytes or a signed URL.

1. Define the file field​

Give the business entity a file field and an explicit format policy. For example, a KSML asset can accept PDF documents:

<root name="case_files" org="example">
<asset name="string()" document="application.pdf" />
</root>

The model controls the generated field and permitted formats. The host still provides an object-storage adapter, a Blob catalog, upload/read/binding authorization, and retention policy. Install BlobReferenceModule explicitly with those services, run the catalog migration as a host action, and validate module readiness before serving requests. Installing the module by itself does not upload a file or migrate a database.

2. Upload, then save the business record​

Start an upload for the exact entity field and the current actor. Send bytes through the configured storage provider and complete the upload to obtain a BlobRef. The file is pending until the business save binds it to the field. The following Java excerpt shows the order of operations; actor, runtime, and asset come from the application's configured host and business model:

var upload = actor.capability(BlobRuntime.class).upload();
var session = upload.begin(actor,
new BlobFieldTarget("Asset", null, "document"),
new BlobRuntime.Uploads.FileInfo("invoice.pdf", null, null),
"upload-invoice-1042");
upload.write(actor, session.uploadId(), pdfInputStream);
BlobRef file = upload.complete(actor, session.uploadId());

asset.updateProperty("document", file);
asset.setComment("attach invoice PDF");
runtime.saveGraph(actor, asset);

The example uses a new asset, hence the null target ID. For an existing record, supply its actual target ID and let the policy check that field and actor. Use your generated application's normal audited save API where it provides one. The module binds the reference with the business graph transaction; a failed save does not create a successful business binding.

3. Prepare a view for an authorized reader​

Load the business record in the reader's trusted context, then prepare its view after business work is complete:

var prepared = runtime.prepareView(
actor, Map.of("data", asset), ViewOptions.afterCommit());

The prepared response contains a complete temporary URL for each visible, authorized file field and the storage provider's actual expiry. Pass that URL to a download or preview control. Do not concatenate a URL prefix, persist the URL as the field value, or submit it later as a file selection. The entity's document field remains a BlobRef even if the prepared response shows a URL. On a later read, prepare a new view to get a fresh URL.

A presigned storage URL grants access until it expires. TeaQL checks authorization when issuing or refreshing it; if your policy requires an identity check on every download, use a storage gateway that performs that check.

4. Replace, clear, and retain​

To replace a file, upload and complete the new selection, then save the new BlobRef to the same field. To remove it, clear the field through a normal authorized mutation. The binding follows the business transaction in both cases. Configure a maintenance job and retention policy for unbound objects; do not delete an object while another live binding still references it.

If the file field appears inside an editable business document, use Secure Document Reference for the document round trip. Its validated file selection is accepted into this same Blob binding path. A displayed download URL is never a selection token.