Skip to content

Conversation

joshua-journey-apps
Copy link

This a port of the Swift, Dart and Kotlin attachment's API to the JS SDK.

This addresses the following issues #715 and #714 by adding the meta_data column to the table and persisting it in the attachments table.

It is currently still a work-in-progress so there are a couple things that still needs to be done:

  • Update demos
  • Improve testing
  • Update the readme
  • Implement sync throttling
  • Implement clearing archived attachments
  • Removing some holdovers from the previous implementation

Copy link

changeset-bot bot commented Oct 7, 2025

⚠️ No Changeset found

Latest commit: 958675a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Collaborator

@stevensJourney stevensJourney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a good improvement so far. I've added some comments for some items I spotted.

this.logger = logger;
}

watchActiveAttachments(onUpdate: () => void): AbortController {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AttachmentContext here is typically an exclusive access context (provided by the AttachmentsService). The context provides general methods for reading and writing AttachmentRecords in a safe context.
The watchActiveAttachments method does fall in to the category of reading Attachment records, but it does not typically require being executed in an exclusive context. It's usually only called once when the sync starts for initialisation. Furthermore, the handler in onUpdate typically will request an AttachmentContext in order to process the active attachments, which can produce a rather interesting code flow.
For consistency with other SDKs, I'd recommend moving this to the AttachmentService.

onUpdate should probably be an async handler.

Alternatively, we could use the Differentially Watched queries here. That would have the advantage of only triggering when there are actual changes to the result set. It also could make closing/aborting and listening for changes simpler.

watchActiveAttachments(): DifferentialWatchedQuery<AttachmentRecord> 

import { AbstractPowerSyncDatabase, ILogger, Transaction } from '@powersync/common';
import { AttachmentRecord, AttachmentState, attachmentFromSql } from './Schema.js';

export class AttachmentContext {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a reminder to add doc comments to all public classes and interfaces

import { AttachmentRecord, AttachmentState } from './Schema.js';
import { SyncErrorHandler } from './SyncErrorHandler.js';

export class StorageService {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, this should be called SyncingService

import { SyncErrorHandler } from './SyncErrorHandler.js';

export class StorageService {
context: AttachmentContext;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SyncingService is usually instantiated when the attachment queue is initialised. The SyncingService then dynamically requests a lock on an AttachmentContext via the AttachmentService. This implementation should receive a reference to an AttachmentService here.


async downloadAttachment(attachment: AttachmentRecord): Promise<AttachmentRecord> {
try {
const fileBlob = await this.remoteStorage.downloadFile(attachment);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have the local storage adapter support writing the same type as returned from the remote downloadFile method without a conversion here.

export class NodeFileSystemAdapter implements LocalStorageAdapter {
async initialize(): Promise<void> {
// const dir = this.getUserStorageDirectory();
const dir = path.resolve('./user_data');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe allow the base directory to be specified as a constructor param and store it for use later.


const config: UserConfigExport = {
plugins: [topLevelAwait()],
worker: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative could be to use the Node.js SDK for tests instead. That should require less config here.

watchAttachments,
logger,
tableName = ATTACHMENT_TABLE,
syncInterval = 30 * 1000,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically indicate the millisecond unit in the variable name

Suggested change
syncInterval = 30 * 1000,
syncIntervalMs= 30 * 1000,

metaData,
id
}: {
data: ArrayBuffer | Blob | string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to export this to it's own dedicated type instead of inlining.

mediaType?: string;
metaData?: string;
id?: string;
}): Promise<AttachmentRecord> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updateHook callback used in other SDKs is important and should be specified here.

That hooks allows the saveFile operation to also associate the newly created attachment record with relational data. Failing to associate the record could result in racy archiving of the newly created record.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants