npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@powersync/attachments

v2.2.0

Published

An PowerSync library to manage attachments for TypeScript and React Native apps

Downloads

594

Readme

@powersync/attachments

A PowerSync library to manage attachments in React Native and JavaScript/TypeScript apps.

Installation

yarn

yarn add @powersync/attachments

pnpm

pnpm add @powersync/attachments

npm

npm install @powersync/attachments

Usage

The AttachmentQueue class is used to manage and sync attachments in your app.

Example

In this example, the user captures photos when checklist items are completed as part of an inspection workflow.

The schema for the checklist table:

const AppSchema = new Schema([
  new Table({
    name: 'checklists',
    columns: [
      new Column({ name: 'photo_id', type: ColumnType.TEXT }),
      new Column({ name: 'description', type: ColumnType.TEXT }),
      new Column({ name: 'completed', type: ColumnType.INTEGER }),
      new Column({ name: 'completed_at', type: ColumnType.TEXT }),
      new Column({ name: 'completed_by', type: ColumnType.TEXT })
    ],
    indexes: [
      new Index({
        name: 'inspections',
        columns: [new IndexedColumn({ name: 'checklist_id' })]
      })
    ]
  })
]);

Steps to implement

  1. Create a new class AttachmentQueue that extends AbstractAttachmentQueue from @powersync/attachments.
import { AbstractAttachmentQueue } from '@powersync/attachments';

export class AttachmentQueue extends AbstractAttachmentQueue {}
  1. Implement onAttachmentIdsChange, which takes in a callback to handle an array of string values of IDs that relate to attachments in your app. We recommend using PowerSync's watch query to return the all IDs of attachments in your app.

    In this example, we query all photos that have been captured as part of an inspection and map these to an array of string values.

import { AbstractAttachmentQueue } from '@powersync/attachments';

export class AttachmentQueue extends AbstractAttachmentQueue {
  onAttachmentIdsChange(onUpdate) {
    this.powersync.watch('SELECT photo_id as id FROM checklists WHERE photo_id IS NOT NULL', [], {
      onResult: (result) => onUpdate(result.rows?._array.map((r) => r.id) ?? [])
    });
  }
}
  1. Implement newAttachmentRecord to return an object that represents the attachment record in your app.

    In this example we always work with JPEG images, but you can use any media type that is supported by your app and storage solution. Note: we are set the state to QUEUED_UPLOAD when creating a new photo record which assumes that the photo data is already on the device.

import { AbstractAttachmentQueue } from '@powersync/attachments';

export class AttachmentQueue extends AbstractAttachmentQueue {
  // ...
  async newAttachmentRecord(record) {
    const photoId = record?.id ?? uuid();
    const filename = record?.filename ?? `${photoId}.jpg`;
    return {
      id: photoId,
      filename,
      media_type: 'image/jpeg',
      state: AttachmentState.QUEUED_UPLOAD,
      ...record
    };
  }
}
  1. Add an AttachmentTable to your app's PowerSync Schema:
import { AttachmentTable } from '@powersync/attachments';

const AppSchema = new Schema([
  // ... other tables
  new AttachmentTable()
]);

In addition to Table options, the AttachmentTable can optionally be configured with the following options:

| Option | Description | Default | | ------------------- | ------------------------------------------------------------------------------- | ----------------------------- | | name | The name of the table | attachments | | additionalColumns | An array of addition Column objects added to the default columns in the table | See below for default columns |

The default columns in AttachmentTable:

| Column Name | Type | Description | | ------------ | --------- | ----------------------------------------------------------------- | | id | TEXT | The ID of the attachment record | | filename | TEXT | The filename of the attachment | | media_type | TEXT | The media type of the attachment | | state | INTEGER | The state of the attachment, one of AttachmentState enum values | | timestamp | INTEGER | The timestamp of last update to the attachment record | | size | INTEGER | The size of the attachment in bytes |

  1. To instantiate an AttachmentQueue, one needs to provide an instance of AbstractPowerSyncDatabase from PowerSync and an instance of StorageAdapter. See the StorageAdapter interface definition here.

  2. Instantiate a new AttachmentQueue and call init() to start syncing attachments. Our example, uses a StorageAdapter that integrates with Supabase Storage.

this.storage = this.supabaseConnector.storage;
this.powersync = factory.getInstance();

this.attachmentQueue = new AttachmentQueue({
  powersync: this.powersync,
  storage: this.storage
});

// Initialize and connect PowerSync ...
// Then initialize the attachment queue
await this.attachmentQueue.init();
  1. Finally, to create an attachment and add it to the queue, call saveToQueue().

    In our example we added a savePhoto() method to our AttachmentQueue class, that does this:

export class AttachmentQueue extends AbstractAttachmentQueue {
  // ...
  async savePhoto(base64Data) {
    const photoAttachment = await this.newAttachmentRecord();
    photoAttachment.local_uri = this.getLocalFilePathSuffix(photoAttachment.filename);

    const localFilePathUri = this.getLocalUri(photoAttachment.local_uri);

    await this.storage.writeFile(localFilePathUri, base64Data, { encoding: 'base64' });

    return this.saveToQueue(photoAttachment);
  }
}

Implementation details

Attachment State

The AttachmentQueue class manages attachments in your app by tracking their state.

The state of an attachment can be one of the following:

| State | Description | | ----------------- | ----------------------------------------------------------------------------- | | QUEUED_SYNC | Check if the attachment needs to be uploaded or downloaded | | QUEUED_UPLOAD | The attachment has been queued for upload to the cloud storage | | QUEUED_DOWNLOAD | The attachment has been queued for download from the cloud storage | | SYNCED | The attachment has been synced | | ARCHIVED | The attachment has been orphaned, i.e. the associated record has been deleted |

Initial sync

Upon initializing the AttachmentQueue, an initial sync of attachments will take place if the performInitialSync is set to true. Any AttachmentRecord with id in first set of IDs retrieved from the watch query will be marked as QUEUED_SYNC, and these records will be rechecked to see if they need to be uploaded or downloaded.

Syncing attachments

The AttachmentQueue sets up two watch queries on the attachments table, one for records in QUEUED_UPLOAD state and one for QUEUED_DOWNLOAD state.

In addition to watching for changes, the AttachmentQueue also triggers a sync every few seconds. This will retry any failed uploads/downloads, in particular after the app was offline.

By default, this is every 30 seconds, but can be configured by setting syncInterval in the AttachmentQueue constructor options, or disabled by setting the interval to 0.

Uploading

  • An AttachmentRecord is created or updated with a state of QUEUED_UPLOAD.
  • The AttachmentQueue picks this up and upon successful upload to Supabase, sets the state to SYNCED.
  • If the upload is not successful, the record remains in QUEUED_UPLOAD state and uploading will be retried when syncing triggers again.

Downloading

  • An AttachmentRecord is created or updated with QUEUED_DOWNLOAD state.
  • The watch query adds the id into a queue of IDs to download and triggers the download process
  • This checks whether the photo is already on the device and if so, skips downloading.
  • If the photo is not on the device, it is downloaded from cloud storage.
  • Writes file to the user's local storage.
  • If this is successful, update the AttachmentRecord state to SYNCED.
  • If any of these fail, the download is retried in the next sync trigger.

Deleting attachments

When an attachment is deleted by a user action or cache expiration:

  • Related AttachmentRecord is removed from attachments table.
  • Local file (if exists) is deleted.
  • File on cloud storage is deleted.

Expire Cache

When PowerSync removes a record, as a result of coming back online or conflict resolution for instance:

  • Any associated AttachmentRecord is orphaned.
  • On the next sync trigger, the AttachmentQueue sets all records that are orphaned to ARCHIVED state.
  • By default, the AttachmentQueue only keeps the last 100 attachment records and then expires the rest.
  • This can be configured by setting cacheLimit in the AttachmentQueue constructor options.