Skip to content

JavaScript: Attach a file

attachments.fromFile(args) copies a picked file into the client's content-addressed sandbox, queues the upload, and writes the reference into the row's column as one local update. The bytes travel out of band; the row only ever holds the reference.

Examples#

Basic#

// src/todo-list.ts
import { kizunasync } from './kizunasync'

export async function attachImage(todoId: string, pickedFileUri: string): Promise<string> {
  const attached = await kizunasync.attachments.fromFile({
    table: 'todos',
    column: 'image_path',
    pk: todoId,
    uri: pickedFileUri,
    mediaType: 'image/jpeg',
  })

  return attached.localUri
}

The function returns localUri, which renders the sandbox copy at once, long before the bytes reach Storage. By then fromFile has written attached.ref into image_path, so the app makes no update of its own after the call. The object key derives from a column of the row, so the row must exist before the file is picked.

Insert the row first with Insert data.

Parameters#

NameTypeRequiredDescription
args.tablestringYesA table that declares this attachment column on Define config.
args.columnstringYesThe column that will hold the reference, declared with attachment().
args.pkstringYesThe primary key of the row the file belongs to. The row must already exist locally.
args.uristringYesWhere to read the file from. The file store fetches it, so on the web this is a blob: or http: object URL, and on a native client the platform file URI.
args.mediaTypestringNoThe content type to record and upload with. Default: the content type the file store reported when it read the file.

Returns#

Promise<TFromFileResult>, settled once the bytes are in the sandbox, the queue row is written, and the row's column holds the reference.

NameTypeRequiredDescription
refstring—The Storage object key, built as <owner>/<pk>/<uploadId>.<ext>. The owner is the value of the row's owner column, so the key is identical on every device. The extension comes from the content type, and is bin for a type outside the known image and PDF set.
sha256string—The hash of the imported bytes, recorded so a peer can verify its download.
sizenumber—The imported byte count.
mediaTypestring | null—The content type recorded for the upload.
localUristring—A renderable URI for the sandbox copy, available immediately. It is not promised to outlive the file.

The queue row is created in state queued with direction upload, and the column update enters the outbox like any other local write. Nothing leaves the device here: the bytes move on the next Sync, after the outbox has drained, so the server row already carries the reference when the object lands.

Errors#

This method throws a typed TEngineError for the catalog codes below, and a plain error otherwise.

CodeCondition
ATTACHMENT_PORTS_MISSINGThe app client was built without both the fileStore and transfer ports, so it has no attachment queue, and every attachment method fails this way. Initializing refuses that combination outright when a table declares an attachment column.
ATTACHMENT_ROW_GONENo local row has that primary key. Insert the row before picking a file.
ATTACHMENT_OWNER_MISSINGThe row's owner column is empty or is not a string, so no object key can be derived. On a table whose owner column is its byOwner bucket, Insert data fills that column once a session has reached the client, so on such a table the code points at a row inserted before then, or at one that set the column to null.
LOCAL_CONSTRAINTThe owner value or the primary key is not a uuid, which the object key needs.
STORE_UNAVAILABLEThe file store cannot open its sandbox: a browser with no OPFS for Browser file store, or a platform with no document directory for Expo: File store.
(the local write's error)The update that writes the reference onto the row failed. The queued upload is evicted, so no job waits on a reference no row carries, and the write's own error is rethrown.
(untagged)The column is not declared as an attachment column on that table, or the file store could not read the URI.

On an app client built with attachment ports, the call opens the engine when it is the client's first use. When that open fails, on this call or an earlier one, the call rejects with the open's typed error, such as ENGINE_UNAVAILABLE or STORE_BUSY, which Initializing lists.

Notes#

The queue holds the upload back until the row's column names this exact reference, which fromFile writes before it returns. A later write that changes the column before the upload runs turns the queue entry orphaned, and Vacuum attachments collects it. Writing a new file to the same column supersedes the previous object the same way.

The upload runs through the transfer port. On Supabase that is Create the Storage transfer, which sends a file at or under 6 MiB as a single standard upload and anything larger as a resumable one. Media and attachments walks the whole path, including the Storage policies the bucket needs.

Once the reference is on the row, React: useAttachment follows the upload and hands the component the URI to render, so the preview above and the synced file are the same element.

A peer sometimes deletes the row first. The local tombstone then shadows the key, so nothing uploads the file and the object turns orphaned instead.

Found a problem? Edit this page on GitHub