Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rude-schools-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/common': minor
---

[Attachments] Added `withAttachmentContext` helper method which exposes an `AttachmentContext` for custom attachment logic.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dist
.output
*.tsbuildinfo
.vscode
.devcontainer
.DS_STORE
.idea
.fleet
Expand Down
13 changes: 6 additions & 7 deletions packages/common/etc/common.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,15 +373,13 @@ export interface ArrayQueryDefinition<RowType = unknown> {
// @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<void>;
db: AbstractPowerSyncDatabase;
readonly db: AbstractPowerSyncDatabase;
// (undocumented)
deleteArchivedAttachments(callback?: (attachments: AttachmentRecord[]) => Promise<void>): Promise<boolean>;
deleteAttachment(attachmentId: string): Promise<void>;
Expand All @@ -390,9 +388,9 @@ export class AttachmentContext {
// (undocumented)
getAttachment(id: string): Promise<AttachmentRecord | undefined>;
getAttachments(): Promise<AttachmentRecord[]>;
logger: ILogger;
readonly logger: ILogger;
saveAttachments(attachments: AttachmentRecord[]): Promise<void>;
tableName: string;
readonly tableName: string;
upsertAttachment(attachment: AttachmentRecord, context: Transaction): Promise<void>;
}

Expand Down Expand Up @@ -454,6 +452,7 @@ export class AttachmentQueue implements AttachmentQueue {
readonly syncThrottleDuration: number;
readonly tableName: string;
verifyAttachments(): Promise<void>;
withAttachmentContext<T>(callback: (context: AttachmentContext) => Promise<T>): Promise<T>;
}

// @alpha
Expand Down
13 changes: 7 additions & 6 deletions packages/common/src/attachments/AttachmentContext.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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.
Expand Down
21 changes: 18 additions & 3 deletions packages/common/src/attachments/AttachmentQueue.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<T>(callback: (context: AttachmentContext) => Promise<T>): Promise<T> {
/**
* 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.
*
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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';
Expand Down
Loading