diff --git a/.changeset/rude-schools-mix.md b/.changeset/rude-schools-mix.md new file mode 100644 index 000000000..f84d6567e --- /dev/null +++ b/.changeset/rude-schools-mix.md @@ -0,0 +1,5 @@ +--- +'@powersync/common': minor +--- + +[Attachments] Added `withAttachmentContext` helper method which exposes an `AttachmentContext` for custom attachment logic. 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/etc/common.api.md b/packages/common/etc/common.api.md index 4d34fd885..e01008179 100644 --- a/packages/common/etc/common.api.md +++ b/packages/common/etc/common.api.md @@ -373,15 +373,13 @@ 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; + readonly archivedCacheLimit: number; // (undocumented) clearQueue(): Promise; - db: AbstractPowerSyncDatabase; + readonly db: AbstractPowerSyncDatabase; // (undocumented) deleteArchivedAttachments(callback?: (attachments: AttachmentRecord[]) => Promise): Promise; deleteAttachment(attachmentId: string): Promise; @@ -390,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; } @@ -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..d419180f8 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,20 +9,21 @@ 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 */ - 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. diff --git a/packages/common/src/attachments/AttachmentQueue.ts b/packages/common/src/attachments/AttachmentQueue.ts index a3ed4f991..b467ed18c 100644 --- a/packages/common/src/attachments/AttachmentQueue.ts +++ b/packages/common/src/attachments/AttachmentQueue.ts @@ -1,15 +1,16 @@ 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 { AttachmentContext } from './AttachmentContext.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 @@ -342,6 +343,20 @@ 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 { + /** + * 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';