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.