From bdc47d40a611c0e7d23887415e3cc3d607a44572 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 9 Jun 2026 06:58:39 +0000 Subject: [PATCH 1/4] fix: expose AttachmentQueue's attachmentService member --- .changeset/rude-schools-mix.md | 5 +++++ .gitignore | 1 + packages/common/src/attachments/AttachmentQueue.ts | 8 ++++---- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .changeset/rude-schools-mix.md diff --git a/.changeset/rude-schools-mix.md b/.changeset/rude-schools-mix.md new file mode 100644 index 000000000..49ee63b70 --- /dev/null +++ b/.changeset/rude-schools-mix.md @@ -0,0 +1,5 @@ +--- +'@powersync/common': patch +--- + +[Attachments] Make `AttachmentQueue.attachmentService` available for classes extending the base class. This allows for custom saveFile implementations. diff --git a/.gitignore b/.gitignore index f57d67a29..422e3144e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist .output *.tsbuildinfo .vscode +.devcontainer .DS_STORE .idea .fleet diff --git a/packages/common/src/attachments/AttachmentQueue.ts b/packages/common/src/attachments/AttachmentQueue.ts index a3ed4f991..67eaf2d48 100644 --- a/packages/common/src/attachments/AttachmentQueue.ts +++ b/packages/common/src/attachments/AttachmentQueue.ts @@ -1,15 +1,15 @@ import { AbstractPowerSyncDatabase } from '../client/AbstractPowerSyncDatabase.js'; import { DEFAULT_WATCH_THROTTLE_MS } from '../client/watched/WatchedQuery.js'; import { DifferentialWatchedQuery } from '../client/watched/processors/DifferentialQueryProcessor.js'; -import { ILogger } from '../utils/Logger.js'; import { Transaction } from '../db/DBAdapter.js'; +import { ILogger } from '../utils/Logger.js'; +import { AttachmentErrorHandler } from './AttachmentErrorHandler.js'; +import { AttachmentService } from './AttachmentService.js'; import { AttachmentData, LocalStorageAdapter } from './LocalStorageAdapter.js'; import { RemoteStorageAdapter } from './RemoteStorageAdapter.js'; import { ATTACHMENT_TABLE, AttachmentRecord, AttachmentState } from './Schema.js'; import { SyncingService } from './SyncingService.js'; import { WatchedAttachmentItem } from './WatchedAttachmentItem.js'; -import { AttachmentService } from './AttachmentService.js'; -import { AttachmentErrorHandler } from './AttachmentErrorHandler.js'; /** * AttachmentQueue manages the lifecycle and synchronization of attachments @@ -70,7 +70,7 @@ export class AttachmentQueue implements AttachmentQueue { readonly archivedCacheLimit: number; /** Service for managing attachment-related database operations */ - private readonly attachmentService: AttachmentService; + protected readonly attachmentService: AttachmentService; /** PowerSync database instance */ private readonly db: AbstractPowerSyncDatabase; From 98ccec9e51b16de056d16e15a8cb01f3ffd12b35 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 9 Jun 2026 07:22:57 +0000 Subject: [PATCH 2/4] update API --- .changeset/rude-schools-mix.md | 4 ++-- packages/common/etc/common.api.md | 5 ++--- .../common/src/attachments/AttachmentContext.ts | 5 +++-- packages/common/src/attachments/AttachmentQueue.ts | 13 ++++++++++++- packages/common/src/index.ts | 4 ++-- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.changeset/rude-schools-mix.md b/.changeset/rude-schools-mix.md index 49ee63b70..f84d6567e 100644 --- a/.changeset/rude-schools-mix.md +++ b/.changeset/rude-schools-mix.md @@ -1,5 +1,5 @@ --- -'@powersync/common': patch +'@powersync/common': minor --- -[Attachments] Make `AttachmentQueue.attachmentService` available for classes extending the base class. This allows for custom saveFile implementations. +[Attachments] Added `withAttachmentContext` helper method which exposes an `AttachmentContext` for custom attachment logic. diff --git a/packages/common/etc/common.api.md b/packages/common/etc/common.api.md index 4d34fd885..040d84c25 100644 --- a/packages/common/etc/common.api.md +++ b/packages/common/etc/common.api.md @@ -373,9 +373,7 @@ export interface ArrayQueryDefinition { // @alpha export const ATTACHMENT_TABLE = "attachments"; -// Warning: (ae-internal-missing-underscore) The name "AttachmentContext" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal +// @alpha export class AttachmentContext { constructor(db: AbstractPowerSyncDatabase, tableName: string | undefined, logger: ILogger, archivedCacheLimit: number); archivedCacheLimit: number; @@ -454,6 +452,7 @@ export class AttachmentQueue implements AttachmentQueue { readonly syncThrottleDuration: number; readonly tableName: string; verifyAttachments(): Promise; + withAttachmentContext(callback: (context: AttachmentContext) => Promise): Promise; } // @alpha diff --git a/packages/common/src/attachments/AttachmentContext.ts b/packages/common/src/attachments/AttachmentContext.ts index 47080fd68..8a71dde98 100644 --- a/packages/common/src/attachments/AttachmentContext.ts +++ b/packages/common/src/attachments/AttachmentContext.ts @@ -1,6 +1,6 @@ import { AbstractPowerSyncDatabase } from '../client/AbstractPowerSyncDatabase.js'; -import { ILogger } from '../utils/Logger.js'; import { Transaction } from '../db/DBAdapter.js'; +import { ILogger } from '../utils/Logger.js'; import { AttachmentRecord, AttachmentState, attachmentFromSql } from './Schema.js'; /** @@ -9,7 +9,8 @@ import { AttachmentRecord, AttachmentState, attachmentFromSql } from './Schema.j * Provides methods to query, insert, update, and delete attachment records with * proper transaction management through PowerSync. * - * @internal + * @experimental + * @alpha */ export class AttachmentContext { /** PowerSync database instance for executing queries */ diff --git a/packages/common/src/attachments/AttachmentQueue.ts b/packages/common/src/attachments/AttachmentQueue.ts index 67eaf2d48..c1cec99a0 100644 --- a/packages/common/src/attachments/AttachmentQueue.ts +++ b/packages/common/src/attachments/AttachmentQueue.ts @@ -3,6 +3,7 @@ import { DEFAULT_WATCH_THROTTLE_MS } from '../client/watched/WatchedQuery.js'; import { DifferentialWatchedQuery } from '../client/watched/processors/DifferentialQueryProcessor.js'; import { Transaction } from '../db/DBAdapter.js'; import { ILogger } from '../utils/Logger.js'; +import { AttachmentContext } from './AttachmentContext.js'; import { AttachmentErrorHandler } from './AttachmentErrorHandler.js'; import { AttachmentService } from './AttachmentService.js'; import { AttachmentData, LocalStorageAdapter } from './LocalStorageAdapter.js'; @@ -70,7 +71,7 @@ export class AttachmentQueue implements AttachmentQueue { readonly archivedCacheLimit: number; /** Service for managing attachment-related database operations */ - protected readonly attachmentService: AttachmentService; + private readonly attachmentService: AttachmentService; /** PowerSync database instance */ private readonly db: AbstractPowerSyncDatabase; @@ -342,6 +343,16 @@ export class AttachmentQueue implements AttachmentQueue { } } + /** + * Provides an {@link AttachmentContext} to a callback. + */ + withAttachmentContext(callback: (context: AttachmentContext) => Promise): Promise { + /** + * AttachmentService is internal and private in this class. + * We only need to expose its locking and context functionality for extending classes. + */ + return this.attachmentService.withContext(callback); + } /** * Saves a file to local storage and queues it for upload to remote storage. * diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index d2364d5a4..bae700802 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,4 +1,4 @@ -export * from './attachments/AttachmentContext.js'; // TODO: Remove (internal) +export * from './attachments/AttachmentContext.js'; export * from './attachments/AttachmentErrorHandler.js'; export * from './attachments/AttachmentQueue.js'; export * from './attachments/AttachmentService.js'; @@ -35,7 +35,7 @@ export * from './db/DBAdapter.js'; export * from './db/schema/Column.js'; export * from './db/schema/Index.js'; export * from './db/schema/IndexedColumn.js'; -export { RawTableType, PendingStatementParameter, PendingStatement } from './db/schema/RawTable.js'; +export { PendingStatement, PendingStatementParameter, RawTableType } from './db/schema/RawTable.js'; export * from './db/schema/Schema.js'; export * from './db/schema/Table.js'; export * from './db/schema/TableV2.js'; From a8d25d039ad6dc88aa969a3af3979849a5ec2428 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 9 Jun 2026 07:33:25 +0000 Subject: [PATCH 3/4] Make AttachmentContext members readonly --- packages/common/etc/common.api.md | 8 ++++---- packages/common/src/attachments/AttachmentContext.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/common/etc/common.api.md b/packages/common/etc/common.api.md index 040d84c25..e01008179 100644 --- a/packages/common/etc/common.api.md +++ b/packages/common/etc/common.api.md @@ -376,10 +376,10 @@ export const ATTACHMENT_TABLE = "attachments"; // @alpha export class AttachmentContext { constructor(db: AbstractPowerSyncDatabase, tableName: string | undefined, logger: ILogger, archivedCacheLimit: number); - archivedCacheLimit: number; + readonly archivedCacheLimit: number; // (undocumented) clearQueue(): Promise; - db: AbstractPowerSyncDatabase; + readonly db: AbstractPowerSyncDatabase; // (undocumented) deleteArchivedAttachments(callback?: (attachments: AttachmentRecord[]) => Promise): Promise; deleteAttachment(attachmentId: string): Promise; @@ -388,9 +388,9 @@ export class AttachmentContext { // (undocumented) getAttachment(id: string): Promise; getAttachments(): Promise; - logger: ILogger; + readonly logger: ILogger; saveAttachments(attachments: AttachmentRecord[]): Promise; - tableName: string; + readonly tableName: string; upsertAttachment(attachment: AttachmentRecord, context: Transaction): Promise; } diff --git a/packages/common/src/attachments/AttachmentContext.ts b/packages/common/src/attachments/AttachmentContext.ts index 8a71dde98..d419180f8 100644 --- a/packages/common/src/attachments/AttachmentContext.ts +++ b/packages/common/src/attachments/AttachmentContext.ts @@ -14,16 +14,16 @@ import { AttachmentRecord, AttachmentState, attachmentFromSql } from './Schema.j */ export class AttachmentContext { /** PowerSync database instance for executing queries */ - db: AbstractPowerSyncDatabase; + readonly db: AbstractPowerSyncDatabase; /** Name of the database table storing attachment records */ - tableName: string; + readonly tableName: string; /** Logger instance for diagnostic information */ - logger: ILogger; + readonly logger: ILogger; /** Maximum number of archived attachments to keep before cleanup */ - archivedCacheLimit: number = 100; + readonly archivedCacheLimit: number = 100; /** * Creates a new AttachmentContext instance. From 0bedefec65cc73a2705a2b4aae1d4e7cf16c5a3f Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Tue, 9 Jun 2026 07:33:55 +0000 Subject: [PATCH 4/4] cleanup --- packages/common/src/attachments/AttachmentQueue.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/common/src/attachments/AttachmentQueue.ts b/packages/common/src/attachments/AttachmentQueue.ts index c1cec99a0..b467ed18c 100644 --- a/packages/common/src/attachments/AttachmentQueue.ts +++ b/packages/common/src/attachments/AttachmentQueue.ts @@ -345,6 +345,10 @@ export class AttachmentQueue implements AttachmentQueue { /** * Provides an {@link AttachmentContext} to a callback. + * + * The callback runs while the attachment queue mutex is held. Do not call + * other {@link AttachmentQueue} methods from within the callback, as they may + * attempt to acquire the same mutex and block indefinitely. */ withAttachmentContext(callback: (context: AttachmentContext) => Promise): Promise { /**